Make ObPrompt windows get managed as clients, and make them able to reconfigure as...
[mikachu/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "actions.h"
26 #include "client.h"
27 #include "xerror.h"
28 #include "prop.h"
29 #include "config.h"
30 #include "screen.h"
31 #include "frame.h"
32 #include "grab.h"
33 #include "menu.h"
34 #include "prompt.h"
35 #include "menuframe.h"
36 #include "keyboard.h"
37 #include "modkeys.h"
38 #include "mouse.h"
39 #include "mainloop.h"
40 #include "focus.h"
41 #include "focus_cycle.h"
42 #include "moveresize.h"
43 #include "group.h"
44 #include "stacking.h"
45 #include "extensions.h"
46 #include "translate.h"
47 #include "ping.h"
48
49 #include <X11/Xlib.h>
50 #include <X11/Xatom.h>
51 #include <glib.h>
52
53 #ifdef HAVE_SYS_SELECT_H
54 #  include <sys/select.h>
55 #endif
56 #ifdef HAVE_SIGNAL_H
57 #  include <signal.h>
58 #endif
59 #ifdef HAVE_UNISTD_H
60 #  include <unistd.h> /* for usleep() */
61 #endif
62 #ifdef XKB
63 #  include <X11/XKBlib.h>
64 #endif
65
66 #ifdef USE_SM
67 #include <X11/ICE/ICElib.h>
68 #endif
69
70 typedef struct
71 {
72     gboolean ignored;
73 } ObEventData;
74
75 typedef struct
76 {
77     ObClient *client;
78     Time time;
79     gulong serial;
80 } ObFocusDelayData;
81
82 typedef struct
83 {
84     gulong start; /* inclusive */
85     gulong end;   /* inclusive */
86 } ObSerialRange;
87
88 static void event_process(const XEvent *e, gpointer data);
89 static void event_handle_root(XEvent *e);
90 static gboolean event_handle_menu_keyboard(XEvent *e);
91 static gboolean event_handle_menu(XEvent *e);
92 static void event_handle_dock(ObDock *s, XEvent *e);
93 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
94 static void event_handle_client(ObClient *c, XEvent *e);
95 static void event_handle_user_input(ObClient *client, XEvent *e);
96 static gboolean is_enter_focus_event_ignored(gulong serial);
97 static void event_ignore_enter_range(gulong start, gulong end);
98
99 static void focus_delay_dest(gpointer data);
100 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
101 static gboolean focus_delay_func(gpointer data);
102 static void focus_delay_client_dest(ObClient *client, gpointer data);
103
104 Time event_curtime = CurrentTime;
105 Time event_last_user_time = CurrentTime;
106 /*! The serial of the current X event */
107
108 static gulong event_curserial;
109 static gboolean focus_left_screen = FALSE;
110 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
111 static GSList *ignore_serials = NULL;
112
113 #ifdef USE_SM
114 static void ice_handler(gint fd, gpointer conn)
115 {
116     Bool b;
117     IceProcessMessages(conn, NULL, &b);
118 }
119
120 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
121                       IcePointer *watch_data)
122 {
123     static gint fd = -1;
124
125     if (opening) {
126         fd = IceConnectionNumber(conn);
127         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
128     } else {
129         ob_main_loop_fd_remove(ob_main_loop, fd);
130         fd = -1;
131     }
132 }
133 #endif
134
135 void event_startup(gboolean reconfig)
136 {
137     if (reconfig) return;
138
139     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
140
141 #ifdef USE_SM
142     IceAddConnectionWatch(ice_watch, NULL);
143 #endif
144
145     client_add_destroy_notify(focus_delay_client_dest, NULL);
146 }
147
148 void event_shutdown(gboolean reconfig)
149 {
150     if (reconfig) return;
151
152 #ifdef USE_SM
153     IceRemoveConnectionWatch(ice_watch, NULL);
154 #endif
155
156     client_remove_destroy_notify(focus_delay_client_dest);
157 }
158
159 static Window event_get_window(XEvent *e)
160 {
161     Window window;
162
163     /* pick a window */
164     switch (e->type) {
165     case SelectionClear:
166         window = RootWindow(ob_display, ob_screen);
167         break;
168     case MapRequest:
169         window = e->xmap.window;
170         break;
171     case UnmapNotify:
172         window = e->xunmap.window;
173         break;
174     case DestroyNotify:
175         window = e->xdestroywindow.window;
176         break;
177     case ConfigureRequest:
178         window = e->xconfigurerequest.window;
179         break;
180     case ConfigureNotify:
181         window = e->xconfigure.window;
182         break;
183     default:
184 #ifdef XKB
185         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
186             switch (((XkbAnyEvent*)e)->xkb_type) {
187             case XkbBellNotify:
188                 window = ((XkbBellNotifyEvent*)e)->window;
189             default:
190                 window = None;
191             }
192         } else
193 #endif
194 #ifdef SYNC
195         if (extensions_sync &&
196             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
197         {
198             window = None;
199         } else
200 #endif
201             window = e->xany.window;
202     }
203     return window;
204 }
205
206 static void event_set_curtime(XEvent *e)
207 {
208     Time t = CurrentTime;
209
210     /* grab the lasttime and hack up the state */
211     switch (e->type) {
212     case ButtonPress:
213     case ButtonRelease:
214         t = e->xbutton.time;
215         break;
216     case KeyPress:
217         t = e->xkey.time;
218         break;
219     case KeyRelease:
220         t = e->xkey.time;
221         break;
222     case MotionNotify:
223         t = e->xmotion.time;
224         break;
225     case PropertyNotify:
226         t = e->xproperty.time;
227         break;
228     case EnterNotify:
229     case LeaveNotify:
230         t = e->xcrossing.time;
231         break;
232     default:
233 #ifdef SYNC
234         if (extensions_sync &&
235             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
236         {
237             t = ((XSyncAlarmNotifyEvent*)e)->time;
238         }
239 #endif
240         /* if more event types are anticipated, get their timestamp
241            explicitly */
242         break;
243     }
244
245     /* watch that if we get an event earlier than the last specified user_time,
246        which can happen if the clock goes backwards, we erase the last
247        specified user_time */
248     if (t && event_last_user_time && event_time_after(event_last_user_time, t))
249         event_last_user_time = CurrentTime;
250
251     event_curtime = t;
252 }
253
254 static void event_hack_mods(XEvent *e)
255 {
256 #ifdef XKB
257     XkbStateRec xkb_state;
258 #endif
259
260     switch (e->type) {
261     case ButtonPress:
262     case ButtonRelease:
263         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
264         break;
265     case KeyPress:
266         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
267         break;
268     case KeyRelease:
269 #ifdef XKB
270         /* If XKB is present, then the modifiers are all strange from its
271            magic.  Our X core protocol stuff won't work, so we use this to
272            find what the modifier state is instead. */
273         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success)
274             e->xkey.state =
275                 modkeys_only_modifier_masks(xkb_state.compat_state);
276         else
277 #endif
278         {
279             e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
280             /* remove from the state the mask of the modifier key being
281                released, if it is a modifier key being released that is */
282             e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
283         }
284         break;
285     case MotionNotify:
286         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
287         /* compress events */
288         {
289             XEvent ce;
290             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
291                                           e->type, &ce)) {
292                 e->xmotion.x = ce.xmotion.x;
293                 e->xmotion.y = ce.xmotion.y;
294                 e->xmotion.x_root = ce.xmotion.x_root;
295                 e->xmotion.y_root = ce.xmotion.y_root;
296             }
297         }
298         break;
299     }
300 }
301
302 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
303 {
304     gint mode = e->xfocus.mode;
305     gint detail = e->xfocus.detail;
306     Window win = e->xany.window;
307
308     if (e->type == FocusIn) {
309         /* These are ones we never want.. */
310
311         /* This means focus was given by a keyboard/mouse grab. */
312         if (mode == NotifyGrab)
313             return FALSE;
314         /* This means focus was given back from a keyboard/mouse grab. */
315         if (mode == NotifyUngrab)
316             return FALSE;
317
318         /* These are the ones we want.. */
319
320         if (win == RootWindow(ob_display, ob_screen)) {
321             /* If looking for a focus in on a client, then always return
322                FALSE for focus in's to the root window */
323             if (in_client_only)
324                 return FALSE;
325             /* This means focus reverted off of a client */
326             else if (detail == NotifyPointerRoot ||
327                      detail == NotifyDetailNone ||
328                      detail == NotifyInferior ||
329                      /* This means focus got here from another screen */
330                      detail == NotifyNonlinear)
331                 return TRUE;
332             else
333                 return FALSE;
334         }
335
336         /* It was on a client, was it a valid one?
337            It's possible to get a FocusIn event for a client that was managed
338            but has disappeared.
339         */
340         if (in_client_only) {
341             ObWindow *w = g_hash_table_lookup(window_map, &e->xfocus.window);
342             if (!w || !WINDOW_IS_CLIENT(w))
343                 return FALSE;
344         }
345         else {
346             /* This means focus reverted to parent from the client (this
347                happens often during iconify animation) */
348             if (detail == NotifyInferior)
349                 return TRUE;
350         }
351
352         /* This means focus moved from the root window to a client */
353         if (detail == NotifyVirtual)
354             return TRUE;
355         /* This means focus moved from one client to another */
356         if (detail == NotifyNonlinearVirtual)
357             return TRUE;
358
359         /* Otherwise.. */
360         return FALSE;
361     } else {
362         g_assert(e->type == FocusOut);
363
364         /* These are ones we never want.. */
365
366         /* This means focus was taken by a keyboard/mouse grab. */
367         if (mode == NotifyGrab)
368             return FALSE;
369         /* This means focus was grabbed on a window and it was released. */
370         if (mode == NotifyUngrab)
371             return FALSE;
372
373         /* Focus left the root window revertedto state */
374         if (win == RootWindow(ob_display, ob_screen))
375             return FALSE;
376
377         /* These are the ones we want.. */
378
379         /* This means focus moved from a client to the root window */
380         if (detail == NotifyVirtual)
381             return TRUE;
382         /* This means focus moved from one client to another */
383         if (detail == NotifyNonlinearVirtual)
384             return TRUE;
385
386         /* Otherwise.. */
387         return FALSE;
388     }
389 }
390
391 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
392 {
393     return e->type == FocusIn && wanted_focusevent(e, FALSE);
394 }
395
396 static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
397 {
398     return e->type == FocusIn && wanted_focusevent(e, TRUE);
399 }
400
401 static void print_focusevent(XEvent *e)
402 {
403     gint mode = e->xfocus.mode;
404     gint detail = e->xfocus.detail;
405     Window win = e->xany.window;
406     const gchar *modestr, *detailstr;
407
408     switch (mode) {
409     case NotifyNormal:       modestr="NotifyNormal";       break;
410     case NotifyGrab:         modestr="NotifyGrab";         break;
411     case NotifyUngrab:       modestr="NotifyUngrab";       break;
412     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
413     }
414     switch (detail) {
415     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
416     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
417     case NotifyInferior:    detailstr="NotifyInferior";    break;
418     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
419     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
420     case NotifyPointer:     detailstr="NotifyPointer";     break;
421     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
422     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
423     }
424
425     if (mode == NotifyGrab || mode == NotifyUngrab)
426         return;
427
428     g_assert(modestr);
429     g_assert(detailstr);
430     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
431                   (e->xfocus.type == FocusIn ? "In" : "Out"),
432                   win,
433                   modestr, detailstr);
434
435 }
436
437 static gboolean event_ignore(XEvent *e, ObClient *client)
438 {
439     switch(e->type) {
440     case FocusIn:
441         print_focusevent(e);
442         if (!wanted_focusevent(e, FALSE))
443             return TRUE;
444         break;
445     case FocusOut:
446         print_focusevent(e);
447         if (!wanted_focusevent(e, FALSE))
448             return TRUE;
449         break;
450     }
451     return FALSE;
452 }
453
454 static void event_process(const XEvent *ec, gpointer data)
455 {
456     Window window;
457     ObClient *client = NULL;
458     ObDock *dock = NULL;
459     ObDockApp *dockapp = NULL;
460     ObWindow *obwin = NULL;
461     XEvent ee, *e;
462     ObEventData *ed = data;
463     ObPrompt *prompt = NULL;
464
465     /* make a copy we can mangle */
466     ee = *ec;
467     e = &ee;
468
469     window = event_get_window(e);
470     if ((obwin = g_hash_table_lookup(window_map, &window))) {
471         switch (obwin->type) {
472         case Window_Dock:
473             dock = WINDOW_AS_DOCK(obwin);
474             break;
475         case Window_DockApp:
476             dockapp = WINDOW_AS_DOCKAPP(obwin);
477             break;
478         case Window_Client:
479             client = WINDOW_AS_CLIENT(obwin);
480             break;
481         case Window_Menu:
482             /* not to be used for events */
483             g_assert_not_reached();
484             break;
485         case Window_Internal:
486             /* we don't do anything with events directly on these windows */
487             break;
488         case Window_Prompt:
489             prompt = WINDOW_AS_PROMPT(obwin);
490             break;
491         }
492     }
493
494     event_set_curtime(e);
495     event_curserial = e->xany.serial;
496     event_hack_mods(e);
497     if (event_ignore(e, client)) {
498         if (ed)
499             ed->ignored = TRUE;
500         return;
501     } else if (ed)
502             ed->ignored = FALSE;
503
504     /* deal with it in the kernel */
505
506     if (menu_frame_visible &&
507         (e->type == EnterNotify || e->type == LeaveNotify))
508     {
509         /* crossing events for menu */
510         event_handle_menu(e);
511     } else if (e->type == FocusIn) {
512         if (client &&
513             e->xfocus.detail == NotifyInferior)
514         {
515             ob_debug_type(OB_DEBUG_FOCUS,
516                           "Focus went to the frame window");
517
518             focus_left_screen = FALSE;
519
520             focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
521
522             /* We don't get a FocusOut for this case, because it's just moving
523                from our Inferior up to us. This happens when iconifying a
524                window with RevertToParent focus */
525             frame_adjust_focus(client->frame, FALSE);
526             /* focus_set_client(NULL) has already been called */
527         }
528         else if (e->xfocus.detail == NotifyPointerRoot ||
529                  e->xfocus.detail == NotifyDetailNone ||
530                  e->xfocus.detail == NotifyInferior ||
531                  e->xfocus.detail == NotifyNonlinear)
532         {
533             XEvent ce;
534
535             ob_debug_type(OB_DEBUG_FOCUS,
536                           "Focus went to root or pointer root/none\n");
537
538             if (e->xfocus.detail == NotifyInferior ||
539                 e->xfocus.detail == NotifyNonlinear)
540             {
541                 focus_left_screen = FALSE;
542             }
543
544             /* If another FocusIn is in the queue then don't fallback yet. This
545                fixes the fun case of:
546                window map -> send focusin
547                window unmap -> get focusout
548                window map -> send focusin
549                get first focus out -> fall back to something (new window
550                  hasn't received focus yet, so something else) -> send focusin
551                which means the "something else" is the last thing to get a
552                focusin sent to it, so the new window doesn't end up with focus.
553
554                But if the other focus in is something like PointerRoot then we
555                still want to fall back.
556             */
557             if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,
558                               NULL))
559             {
560                 XPutBackEvent(ob_display, &ce);
561                 ob_debug_type(OB_DEBUG_FOCUS,
562                               "  but another FocusIn is coming\n");
563             } else {
564                 /* Focus has been reverted.
565
566                    FocusOut events come after UnmapNotify, so we don't need to
567                    worry about focusing an invalid window
568                 */
569
570                 if (!focus_left_screen)
571                     focus_fallback(FALSE, config_focus_under_mouse,
572                                    TRUE, TRUE);
573             }
574         }
575         else if (!client)
576         {
577             ob_debug_type(OB_DEBUG_FOCUS,
578                           "Focus went to a window that is already gone\n");
579
580             /* If you send focus to a window and then it disappears, you can
581                get the FocusIn for it, after it is unmanaged.
582                Just wait for the next FocusOut/FocusIn pair, but make note that
583                the window that was focused no longer is. */
584             focus_set_client(NULL);
585         }
586         else if (client != focus_client) {
587             focus_left_screen = FALSE;
588             frame_adjust_focus(client->frame, TRUE);
589             focus_set_client(client);
590             client_calc_layer(client);
591             client_bring_helper_windows(client);
592         }
593     } else if (e->type == FocusOut) {
594         XEvent ce;
595
596         /* Look for the followup FocusIn */
597         if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) {
598             /* There is no FocusIn, this means focus went to a window that
599                is not being managed, or a window on another screen. */
600             Window win, root;
601             gint i;
602             guint u;
603             xerror_set_ignore(TRUE);
604             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
605                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
606                 root != RootWindow(ob_display, ob_screen))
607             {
608                 ob_debug_type(OB_DEBUG_FOCUS,
609                               "Focus went to another screen !\n");
610                 focus_left_screen = TRUE;
611             }
612             else
613                 ob_debug_type(OB_DEBUG_FOCUS,
614                               "Focus went to a black hole !\n");
615             xerror_set_ignore(FALSE);
616             /* nothing is focused */
617             focus_set_client(NULL);
618         } else {
619             /* Focus moved, so process the FocusIn event */
620             ObEventData ed = { .ignored = FALSE };
621             event_process(&ce, &ed);
622             if (ed.ignored) {
623                 /* The FocusIn was ignored, this means it was on a window
624                    that isn't a client. */
625                 ob_debug_type(OB_DEBUG_FOCUS,
626                               "Focus went to an unmanaged window 0x%x !\n",
627                               ce.xfocus.window);
628                 focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE);
629             }
630         }
631
632         if (client && client != focus_client) {
633             frame_adjust_focus(client->frame, FALSE);
634             /* focus_set_client(NULL) has already been called in this
635                section or by focus_fallback */
636         }
637     }
638     else if (client)
639         event_handle_client(client, e);
640     else if (dockapp)
641         event_handle_dockapp(dockapp, e);
642     else if (dock)
643         event_handle_dock(dock, e);
644     else if (window == RootWindow(ob_display, ob_screen))
645         event_handle_root(e);
646     else if (e->type == MapRequest)
647         client_manage(window, NULL);
648     else if (e->type == MappingNotify) {
649         /* keyboard layout changes for modifier mapping changes. reload the
650            modifier map, and rebind all the key bindings as appropriate */
651         ob_debug("Kepboard map changed. Reloading keyboard bindings.\n");
652         modkeys_shutdown(TRUE);
653         modkeys_startup(TRUE);
654         keyboard_rebind();
655     }
656     else if (e->type == ClientMessage) {
657         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
658            windows that are not managed yet. */
659         if (e->xclient.message_type == prop_atoms.net_request_frame_extents) {
660             /* Pretend to manage the client, getting information used to
661                determine its decorations */
662             ObClient *c = client_fake_manage(e->xclient.window);
663             gulong vals[4];
664
665             /* set the frame extents on the window */
666             vals[0] = c->frame->size.left;
667             vals[1] = c->frame->size.right;
668             vals[2] = c->frame->size.top;
669             vals[3] = c->frame->size.bottom;
670             PROP_SETA32(e->xclient.window, net_frame_extents,
671                         cardinal, vals, 4);
672
673             /* Free the pretend client */
674             client_fake_unmanage(c);
675         }
676     }
677     else if (e->type == ConfigureRequest) {
678         /* unhandled configure requests must be used to configure the
679            window directly */
680         XWindowChanges xwc;
681
682         xwc.x = e->xconfigurerequest.x;
683         xwc.y = e->xconfigurerequest.y;
684         xwc.width = e->xconfigurerequest.width;
685         xwc.height = e->xconfigurerequest.height;
686         xwc.border_width = e->xconfigurerequest.border_width;
687         xwc.sibling = e->xconfigurerequest.above;
688         xwc.stack_mode = e->xconfigurerequest.detail;
689
690         /* we are not to be held responsible if someone sends us an
691            invalid request! */
692         xerror_set_ignore(TRUE);
693         XConfigureWindow(ob_display, window,
694                          e->xconfigurerequest.value_mask, &xwc);
695         xerror_set_ignore(FALSE);
696     }
697 #ifdef SYNC
698     else if (extensions_sync &&
699         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
700     {
701         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
702         if (se->alarm == moveresize_alarm && moveresize_in_progress)
703             moveresize_event(e);
704     }
705 #endif
706
707     if (e->type == ButtonPress || e->type == ButtonRelease) {
708         /* If the button press was on some non-root window, or was physically
709            on the root window, then process it */
710         if (window != RootWindow(ob_display, ob_screen) ||
711             e->xbutton.subwindow == None)
712         {
713             event_handle_user_input(client, e);
714         }
715         /* Otherwise only process it if it was physically on an openbox
716            internal window */
717         else {
718             ObWindow *w;
719
720             if ((w = g_hash_table_lookup(window_map, &e->xbutton.subwindow)) &&
721                 WINDOW_IS_INTERNAL(w))
722             {
723                 event_handle_user_input(client, e);
724             }
725         }
726     }
727     else if (e->type == KeyPress || e->type == KeyRelease ||
728              e->type == MotionNotify)
729         event_handle_user_input(client, e);
730
731     /* if something happens and it's not from an XEvent, then we don't know
732        the time */
733     event_curtime = CurrentTime;
734     event_curserial = 0;
735 }
736
737 static void event_handle_root(XEvent *e)
738 {
739     Atom msgtype;
740
741     switch(e->type) {
742     case SelectionClear:
743         ob_debug("Another WM has requested to replace us. Exiting.\n");
744         ob_exit_replace();
745         break;
746
747     case ClientMessage:
748         if (e->xclient.format != 32) break;
749
750         msgtype = e->xclient.message_type;
751         if (msgtype == prop_atoms.net_current_desktop) {
752             guint d = e->xclient.data.l[0];
753             if (d < screen_num_desktops) {
754                 event_curtime = e->xclient.data.l[1];
755                 if (event_curtime == 0)
756                     ob_debug_type(OB_DEBUG_APP_BUGS,
757                                   "_NET_CURRENT_DESKTOP message is missing "
758                                   "a timestamp\n");
759                 screen_set_desktop(d, TRUE);
760             }
761         } else if (msgtype == prop_atoms.net_number_of_desktops) {
762             guint d = e->xclient.data.l[0];
763             if (d > 0 && d <= 1000)
764                 screen_set_num_desktops(d);
765         } else if (msgtype == prop_atoms.net_showing_desktop) {
766             screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
767         } else if (msgtype == prop_atoms.ob_control) {
768             ob_debug("OB_CONTROL: %d\n", e->xclient.data.l[0]);
769             if (e->xclient.data.l[0] == 1)
770                 ob_reconfigure();
771             else if (e->xclient.data.l[0] == 2)
772                 ob_restart();
773             else if (e->xclient.data.l[0] == 3)
774                 ob_exit(0);
775         } else if (msgtype == prop_atoms.wm_protocols) {
776             if ((Atom)e->xclient.data.l[0] == prop_atoms.net_wm_ping)
777                 ping_got_pong(e->xclient.data.l[1]);
778         }
779         break;
780     case PropertyNotify:
781         if (e->xproperty.atom == prop_atoms.net_desktop_names) {
782             ob_debug("UPDATE DESKTOP NAMES\n");
783             screen_update_desktop_names();
784         }
785         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
786             screen_update_layout();
787         break;
788     case ConfigureNotify:
789 #ifdef XRANDR
790         XRRUpdateConfiguration(e);
791 #endif
792         screen_resize();
793         break;
794     default:
795         ;
796     }
797 }
798
799 void event_enter_client(ObClient *client)
800 {
801     g_assert(config_focus_follow);
802
803     if (is_enter_focus_event_ignored(event_curserial)) {
804         ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n"
805                       "on client 0x%x", event_curserial, client->window);
806         return;
807     }
808
809     if (client_enter_focusable(client) && client_can_focus(client)) {
810         if (config_focus_delay) {
811             ObFocusDelayData *data;
812
813             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
814
815             data = g_new(ObFocusDelayData, 1);
816             data->client = client;
817             data->time = event_curtime;
818             data->serial = event_curserial;
819
820             ob_main_loop_timeout_add(ob_main_loop,
821                                      config_focus_delay * 1000,
822                                      focus_delay_func,
823                                      data, focus_delay_cmp, focus_delay_dest);
824         } else {
825             ObFocusDelayData data;
826             data.client = client;
827             data.time = event_curtime;
828             data.serial = event_curserial;
829             focus_delay_func(&data);
830         }
831     }
832 }
833
834 static void event_handle_client(ObClient *client, XEvent *e)
835 {
836     XEvent ce;
837     Atom msgtype;
838     ObFrameContext con;
839     static gint px = -1, py = -1;
840     static guint pb = 0;
841
842     switch (e->type) {
843     case ButtonPress:
844         /* save where the press occured for the first button pressed */
845         if (!pb) {
846             pb = e->xbutton.button;
847             px = e->xbutton.x;
848             py = e->xbutton.y;
849         }
850     case ButtonRelease:
851         /* Wheel buttons don't draw because they are an instant click, so it
852            is a waste of resources to go drawing it.
853            if the user is doing an intereactive thing, or has a menu open then
854            the mouse is grabbed (possibly) and if we get these events we don't
855            want to deal with them
856         */
857         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
858             !grab_on_keyboard())
859         {
860             /* use where the press occured */
861             con = frame_context(client, e->xbutton.window, px, py);
862             con = mouse_button_frame_context(con, e->xbutton.button,
863                                              e->xbutton.state);
864
865             if (e->type == ButtonRelease && e->xbutton.button == pb)
866                 pb = 0, px = py = -1;
867
868             switch (con) {
869             case OB_FRAME_CONTEXT_MAXIMIZE:
870                 client->frame->max_press = (e->type == ButtonPress);
871                 frame_adjust_state(client->frame);
872                 break;
873             case OB_FRAME_CONTEXT_CLOSE:
874                 client->frame->close_press = (e->type == ButtonPress);
875                 frame_adjust_state(client->frame);
876                 break;
877             case OB_FRAME_CONTEXT_ICONIFY:
878                 client->frame->iconify_press = (e->type == ButtonPress);
879                 frame_adjust_state(client->frame);
880                 break;
881             case OB_FRAME_CONTEXT_ALLDESKTOPS:
882                 client->frame->desk_press = (e->type == ButtonPress);
883                 frame_adjust_state(client->frame);
884                 break;
885             case OB_FRAME_CONTEXT_SHADE:
886                 client->frame->shade_press = (e->type == ButtonPress);
887                 frame_adjust_state(client->frame);
888                 break;
889             default:
890                 /* nothing changes with clicks for any other contexts */
891                 break;
892             }
893         }
894         break;
895     case MotionNotify:
896         /* when there is a grab on the pointer, we won't get enter/leave
897            notifies, but we still get motion events */
898         if (grab_on_pointer()) break;
899
900         con = frame_context(client, e->xmotion.window,
901                             e->xmotion.x, e->xmotion.y);
902         switch (con) {
903         case OB_FRAME_CONTEXT_TITLEBAR:
904         case OB_FRAME_CONTEXT_TLCORNER:
905         case OB_FRAME_CONTEXT_TRCORNER:
906             /* we've left the button area inside the titlebar */
907             if (client->frame->max_hover || client->frame->desk_hover ||
908                 client->frame->shade_hover || client->frame->iconify_hover ||
909                 client->frame->close_hover)
910             {
911                 client->frame->max_hover = FALSE;
912                 client->frame->desk_hover = FALSE;
913                 client->frame->shade_hover = FALSE;
914                 client->frame->iconify_hover = FALSE;
915                 client->frame->close_hover = FALSE;
916                 frame_adjust_state(client->frame);
917             }
918             break;
919         case OB_FRAME_CONTEXT_MAXIMIZE:
920             if (!client->frame->max_hover) {
921                 client->frame->max_hover = TRUE;
922                 frame_adjust_state(client->frame);
923             }
924             break;
925         case OB_FRAME_CONTEXT_ALLDESKTOPS:
926             if (!client->frame->desk_hover) {
927                 client->frame->desk_hover = TRUE;
928                 frame_adjust_state(client->frame);
929             }
930             break;
931         case OB_FRAME_CONTEXT_SHADE:
932             if (!client->frame->shade_hover) {
933                 client->frame->shade_hover = TRUE;
934                 frame_adjust_state(client->frame);
935             }
936             break;
937         case OB_FRAME_CONTEXT_ICONIFY:
938             if (!client->frame->iconify_hover) {
939                 client->frame->iconify_hover = TRUE;
940                 frame_adjust_state(client->frame);
941             }
942             break;
943         case OB_FRAME_CONTEXT_CLOSE:
944             if (!client->frame->close_hover) {
945                 client->frame->close_hover = TRUE;
946                 frame_adjust_state(client->frame);
947             }
948             break;
949         default:
950             break;
951         }
952         break;
953     case LeaveNotify:
954         con = frame_context(client, e->xcrossing.window,
955                             e->xcrossing.x, e->xcrossing.y);
956         switch (con) {
957         case OB_FRAME_CONTEXT_TITLEBAR:
958         case OB_FRAME_CONTEXT_TLCORNER:
959         case OB_FRAME_CONTEXT_TRCORNER:
960             /* we've left the button area inside the titlebar */
961             if (client->frame->max_hover || client->frame->desk_hover ||
962                 client->frame->shade_hover || client->frame->iconify_hover ||
963                 client->frame->close_hover)
964             {
965                 client->frame->max_hover = FALSE;
966                 client->frame->desk_hover = FALSE;
967                 client->frame->shade_hover = FALSE;
968                 client->frame->iconify_hover = FALSE;
969                 client->frame->close_hover = FALSE;
970                 frame_adjust_state(client->frame);
971             }
972             break;
973         case OB_FRAME_CONTEXT_MAXIMIZE:
974             client->frame->max_hover = FALSE;
975             frame_adjust_state(client->frame);
976             break;
977         case OB_FRAME_CONTEXT_ALLDESKTOPS:
978             client->frame->desk_hover = FALSE;
979             frame_adjust_state(client->frame);
980             break;
981         case OB_FRAME_CONTEXT_SHADE:
982             client->frame->shade_hover = FALSE;
983             frame_adjust_state(client->frame);
984             break;
985         case OB_FRAME_CONTEXT_ICONIFY:
986             client->frame->iconify_hover = FALSE;
987             frame_adjust_state(client->frame);
988             break;
989         case OB_FRAME_CONTEXT_CLOSE:
990             client->frame->close_hover = FALSE;
991             frame_adjust_state(client->frame);
992             break;
993         case OB_FRAME_CONTEXT_FRAME:
994             /* When the mouse leaves an animating window, don't use the
995                corresponding enter events. Pretend like the animating window
996                doesn't even exist..! */
997             if (frame_iconify_animating(client->frame))
998                 event_end_ignore_all_enters(event_start_ignore_all_enters());
999
1000             ob_debug_type(OB_DEBUG_FOCUS,
1001                           "%sNotify mode %d detail %d on %lx\n",
1002                           (e->type == EnterNotify ? "Enter" : "Leave"),
1003                           e->xcrossing.mode,
1004                           e->xcrossing.detail, (client?client->window:0));
1005             if (grab_on_keyboard())
1006                 break;
1007             if (config_focus_follow && config_focus_delay &&
1008                 /* leave inferior events can happen when the mouse goes onto
1009                    the window's border and then into the window before the
1010                    delay is up */
1011                 e->xcrossing.detail != NotifyInferior)
1012             {
1013                 ob_main_loop_timeout_remove_data(ob_main_loop,
1014                                                  focus_delay_func,
1015                                                  client, FALSE);
1016             }
1017             break;
1018         default:
1019             break;
1020         }
1021         break;
1022     case EnterNotify:
1023     {
1024         con = frame_context(client, e->xcrossing.window,
1025                             e->xcrossing.x, e->xcrossing.y);
1026         switch (con) {
1027         case OB_FRAME_CONTEXT_MAXIMIZE:
1028             client->frame->max_hover = TRUE;
1029             frame_adjust_state(client->frame);
1030             break;
1031         case OB_FRAME_CONTEXT_ALLDESKTOPS:
1032             client->frame->desk_hover = TRUE;
1033             frame_adjust_state(client->frame);
1034             break;
1035         case OB_FRAME_CONTEXT_SHADE:
1036             client->frame->shade_hover = TRUE;
1037             frame_adjust_state(client->frame);
1038             break;
1039         case OB_FRAME_CONTEXT_ICONIFY:
1040             client->frame->iconify_hover = TRUE;
1041             frame_adjust_state(client->frame);
1042             break;
1043         case OB_FRAME_CONTEXT_CLOSE:
1044             client->frame->close_hover = TRUE;
1045             frame_adjust_state(client->frame);
1046             break;
1047         case OB_FRAME_CONTEXT_FRAME:
1048             if (grab_on_keyboard())
1049                 break;
1050             if (e->xcrossing.mode == NotifyGrab ||
1051                 e->xcrossing.mode == NotifyUngrab ||
1052                 /*ignore enters when we're already in the window */
1053                 e->xcrossing.detail == NotifyInferior)
1054             {
1055                 ob_debug_type(OB_DEBUG_FOCUS,
1056                               "%sNotify mode %d detail %d serial %lu on %lx "
1057                               "IGNORED\n",
1058                               (e->type == EnterNotify ? "Enter" : "Leave"),
1059                               e->xcrossing.mode,
1060                               e->xcrossing.detail,
1061                               e->xcrossing.serial,
1062                               client?client->window:0);
1063             }
1064             else {
1065                 ob_debug_type(OB_DEBUG_FOCUS,
1066                               "%sNotify mode %d detail %d serial %lu on %lx, "
1067                               "focusing window\n",
1068                               (e->type == EnterNotify ? "Enter" : "Leave"),
1069                               e->xcrossing.mode,
1070                               e->xcrossing.detail,
1071                               e->xcrossing.serial,
1072                               (client?client->window:0));
1073                 if (config_focus_follow)
1074                     event_enter_client(client);
1075             }
1076             break;
1077         default:
1078             break;
1079         }
1080         break;
1081     }
1082     case ConfigureRequest:
1083     {
1084         /* dont compress these unless you're going to watch for property
1085            notifies in between (these can change what the configure would
1086            do to the window).
1087            also you can't compress stacking events
1088         */
1089
1090         gint x, y, w, h;
1091         gboolean move = FALSE;
1092         gboolean resize = FALSE;
1093
1094         /* get the current area */
1095         RECT_TO_DIMS(client->area, x, y, w, h);
1096
1097         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1098                  "visibile %d\n"
1099                  "                     x %d y %d w %d h %d b %d\n",
1100                  client->title,
1101                  screen_desktop, client->wmstate, client->frame->visible,
1102                  x, y, w, h, client->border_width);
1103
1104         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1105             if (client->border_width != e->xconfigurerequest.border_width) {
1106                 client->border_width = e->xconfigurerequest.border_width;
1107
1108                 /* if the border width is changing then that is the same
1109                    as requesting a resize, but we don't actually change
1110                    the client's border, so it will change their root
1111                    coordiantes (since they include the border width) and
1112                    we need to a notify then */
1113                 move = TRUE;
1114             }
1115
1116
1117         if (e->xconfigurerequest.value_mask & CWStackMode) {
1118             ObClient *sibling = NULL;
1119             gulong ignore_start;
1120             gboolean ok = TRUE;
1121
1122             /* get the sibling */
1123             if (e->xconfigurerequest.value_mask & CWSibling) {
1124                 ObWindow *win;
1125                 win = g_hash_table_lookup(window_map,
1126                                           &e->xconfigurerequest.above);
1127                 if (win && WINDOW_IS_CLIENT(win) &&
1128                     WINDOW_AS_CLIENT(win) != client)
1129                 {
1130                     sibling = WINDOW_AS_CLIENT(win);
1131                 }
1132                 else
1133                     /* an invalid sibling was specified so don't restack at
1134                        all, it won't make sense no matter what we do */
1135                     ok = FALSE;
1136             }
1137
1138             if (ok) {
1139                 if (!config_focus_under_mouse)
1140                     ignore_start = event_start_ignore_all_enters();
1141                 stacking_restack_request(client, sibling,
1142                                          e->xconfigurerequest.detail);
1143                 if (!config_focus_under_mouse)
1144                     event_end_ignore_all_enters(ignore_start);
1145             }
1146
1147             /* a stacking change moves the window without resizing */
1148             move = TRUE;
1149         }
1150
1151         if ((e->xconfigurerequest.value_mask & CWX) ||
1152             (e->xconfigurerequest.value_mask & CWY) ||
1153             (e->xconfigurerequest.value_mask & CWWidth) ||
1154             (e->xconfigurerequest.value_mask & CWHeight))
1155         {
1156             if (e->xconfigurerequest.value_mask & CWX) {
1157                 /* don't allow clients to move shaded windows (fvwm does this)
1158                  */
1159                 if (!client->shaded)
1160                     x = e->xconfigurerequest.x;
1161                 move = TRUE;
1162             }
1163             if (e->xconfigurerequest.value_mask & CWY) {
1164                 /* don't allow clients to move shaded windows (fvwm does this)
1165                  */
1166                 if (!client->shaded)
1167                     y = e->xconfigurerequest.y;
1168                 move = TRUE;
1169             }
1170
1171             if (e->xconfigurerequest.value_mask & CWWidth) {
1172                 w = e->xconfigurerequest.width;
1173                 resize = TRUE;
1174             }
1175             if (e->xconfigurerequest.value_mask & CWHeight) {
1176                 h = e->xconfigurerequest.height;
1177                 resize = TRUE;
1178             }
1179         }
1180
1181         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1182                  "move %d resize %d\n",
1183                  e->xconfigurerequest.value_mask & CWX, x,
1184                  e->xconfigurerequest.value_mask & CWY, y,
1185                  e->xconfigurerequest.value_mask & CWWidth, w,
1186                  e->xconfigurerequest.value_mask & CWHeight, h,
1187                  move, resize);
1188
1189         /* check for broken apps moving to their root position
1190
1191            XXX remove this some day...that would be nice. right now all
1192            kde apps do this when they try activate themselves on another
1193            desktop. eg. open amarok window on desktop 1, switch to desktop
1194            2, click amarok tray icon. it will move by its decoration size.
1195         */
1196         if (x != client->area.x &&
1197             x == (client->frame->area.x + client->frame->size.left -
1198                   (gint)client->border_width) &&
1199             y != client->area.y &&
1200             y == (client->frame->area.y + client->frame->size.top -
1201                   (gint)client->border_width) &&
1202             w == client->area.width &&
1203             h == client->area.height)
1204         {
1205             ob_debug_type(OB_DEBUG_APP_BUGS,
1206                           "Application %s is trying to move via "
1207                           "ConfigureRequest to it's root window position "
1208                           "but it is not using StaticGravity\n",
1209                           client->title);
1210             /* don't move it */
1211             x = client->area.x;
1212             y = client->area.y;
1213
1214             /* they still requested a move, so don't change whether a
1215                notify is sent or not */
1216         }
1217
1218         {
1219             gint lw,lh;
1220
1221             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1222
1223             /* if x was not given, then use gravity to figure out the new
1224                x.  the reference point should not be moved */
1225             if ((e->xconfigurerequest.value_mask & CWWidth &&
1226                  !(e->xconfigurerequest.value_mask & CWX)))
1227                 client_gravity_resize_w(client, &x, client->area.width, w);
1228             /* if y was not given, then use gravity to figure out the new
1229                y.  the reference point should not be moved */
1230             if ((e->xconfigurerequest.value_mask & CWHeight &&
1231                  !(e->xconfigurerequest.value_mask & CWY)))
1232                 client_gravity_resize_h(client, &y, client->area.height,h);
1233
1234             client_find_onscreen(client, &x, &y, w, h, FALSE);
1235
1236             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d\n",
1237                      x, y, w, h);
1238             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1239         }
1240         break;
1241     }
1242     case UnmapNotify:
1243         if (client->ignore_unmaps) {
1244             client->ignore_unmaps--;
1245             break;
1246         }
1247         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1248                  "ignores left %d\n",
1249                  client->window, e->xunmap.event, e->xunmap.from_configure,
1250                  client->ignore_unmaps);
1251         client_unmanage(client);
1252         break;
1253     case DestroyNotify:
1254         ob_debug("DestroyNotify for window 0x%x\n", client->window);
1255         client_unmanage(client);
1256         break;
1257     case ReparentNotify:
1258         /* this is when the client is first taken captive in the frame */
1259         if (e->xreparent.parent == client->frame->window) break;
1260
1261         /*
1262           This event is quite rare and is usually handled in unmapHandler.
1263           However, if the window is unmapped when the reparent event occurs,
1264           the window manager never sees it because an unmap event is not sent
1265           to an already unmapped window.
1266         */
1267
1268         /* we don't want the reparent event, put it back on the stack for the
1269            X server to deal with after we unmanage the window */
1270         XPutBackEvent(ob_display, e);
1271
1272         ob_debug("ReparentNotify for window 0x%x\n", client->window);
1273         client_unmanage(client);
1274         break;
1275     case MapRequest:
1276         ob_debug("MapRequest for 0x%lx\n", client->window);
1277         if (!client->iconic) break; /* this normally doesn't happen, but if it
1278                                        does, we don't want it!
1279                                        it can happen now when the window is on
1280                                        another desktop, but we still don't
1281                                        want it! */
1282         client_activate(client, FALSE, TRUE, TRUE, TRUE);
1283         break;
1284     case ClientMessage:
1285         /* validate cuz we query stuff off the client here */
1286         if (!client_validate(client)) break;
1287
1288         if (e->xclient.format != 32) return;
1289
1290         msgtype = e->xclient.message_type;
1291         if (msgtype == prop_atoms.wm_change_state) {
1292             /* compress changes into a single change */
1293             while (XCheckTypedWindowEvent(ob_display, client->window,
1294                                           e->type, &ce)) {
1295                 /* XXX: it would be nice to compress ALL messages of a
1296                    type, not just messages in a row without other
1297                    message types between. */
1298                 if (ce.xclient.message_type != msgtype) {
1299                     XPutBackEvent(ob_display, &ce);
1300                     break;
1301                 }
1302                 e->xclient = ce.xclient;
1303             }
1304             client_set_wm_state(client, e->xclient.data.l[0]);
1305         } else if (msgtype == prop_atoms.net_wm_desktop) {
1306             /* compress changes into a single change */
1307             while (XCheckTypedWindowEvent(ob_display, client->window,
1308                                           e->type, &ce)) {
1309                 /* XXX: it would be nice to compress ALL messages of a
1310                    type, not just messages in a row without other
1311                    message types between. */
1312                 if (ce.xclient.message_type != msgtype) {
1313                     XPutBackEvent(ob_display, &ce);
1314                     break;
1315                 }
1316                 e->xclient = ce.xclient;
1317             }
1318             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1319                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1320                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1321                                    FALSE, FALSE);
1322         } else if (msgtype == prop_atoms.net_wm_state) {
1323             gulong ignore_start;
1324
1325             /* can't compress these */
1326             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1327                      (e->xclient.data.l[0] == 0 ? "Remove" :
1328                       e->xclient.data.l[0] == 1 ? "Add" :
1329                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1330                      e->xclient.data.l[1], e->xclient.data.l[2],
1331                      client->window);
1332
1333             /* ignore enter events caused by these like ob actions do */
1334             if (!config_focus_under_mouse)
1335                 ignore_start = event_start_ignore_all_enters();
1336             client_set_state(client, e->xclient.data.l[0],
1337                              e->xclient.data.l[1], e->xclient.data.l[2]);
1338             if (!config_focus_under_mouse)
1339                 event_end_ignore_all_enters(ignore_start);
1340         } else if (msgtype == prop_atoms.net_close_window) {
1341             ob_debug("net_close_window for 0x%lx\n", client->window);
1342             client_close(client);
1343         } else if (msgtype == prop_atoms.net_active_window) {
1344             ob_debug("net_active_window for 0x%lx source=%s\n",
1345                      client->window,
1346                      (e->xclient.data.l[0] == 0 ? "unknown" :
1347                       (e->xclient.data.l[0] == 1 ? "application" :
1348                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1349             /* XXX make use of data.l[2] !? */
1350             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1351                 /* don't use the user's timestamp for client_focus, cuz if it's
1352                    an old broken timestamp (happens all the time) then focus
1353                    won't move even though we're trying to move it
1354                   event_curtime = e->xclient.data.l[1];*/
1355                 if (e->xclient.data.l[1] == 0)
1356                     ob_debug_type(OB_DEBUG_APP_BUGS,
1357                                   "_NET_ACTIVE_WINDOW message for window %s is"
1358                                   " missing a timestamp\n", client->title);
1359             } else
1360                 ob_debug_type(OB_DEBUG_APP_BUGS,
1361                               "_NET_ACTIVE_WINDOW message for window %s is "
1362                               "missing source indication\n");
1363             client_activate(client, TRUE, TRUE, TRUE,
1364                             (e->xclient.data.l[0] == 0 ||
1365                              e->xclient.data.l[0] == 2));
1366         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1367             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1368                      client->window, e->xclient.data.l[2]);
1369             if ((Atom)e->xclient.data.l[2] ==
1370                 prop_atoms.net_wm_moveresize_size_topleft ||
1371                 (Atom)e->xclient.data.l[2] ==
1372                 prop_atoms.net_wm_moveresize_size_top ||
1373                 (Atom)e->xclient.data.l[2] ==
1374                 prop_atoms.net_wm_moveresize_size_topright ||
1375                 (Atom)e->xclient.data.l[2] ==
1376                 prop_atoms.net_wm_moveresize_size_right ||
1377                 (Atom)e->xclient.data.l[2] ==
1378                 prop_atoms.net_wm_moveresize_size_right ||
1379                 (Atom)e->xclient.data.l[2] ==
1380                 prop_atoms.net_wm_moveresize_size_bottomright ||
1381                 (Atom)e->xclient.data.l[2] ==
1382                 prop_atoms.net_wm_moveresize_size_bottom ||
1383                 (Atom)e->xclient.data.l[2] ==
1384                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1385                 (Atom)e->xclient.data.l[2] ==
1386                 prop_atoms.net_wm_moveresize_size_left ||
1387                 (Atom)e->xclient.data.l[2] ==
1388                 prop_atoms.net_wm_moveresize_move ||
1389                 (Atom)e->xclient.data.l[2] ==
1390                 prop_atoms.net_wm_moveresize_size_keyboard ||
1391                 (Atom)e->xclient.data.l[2] ==
1392                 prop_atoms.net_wm_moveresize_move_keyboard) {
1393
1394                 moveresize_start(client, e->xclient.data.l[0],
1395                                  e->xclient.data.l[1], e->xclient.data.l[3],
1396                                  e->xclient.data.l[2]);
1397             }
1398             else if ((Atom)e->xclient.data.l[2] ==
1399                      prop_atoms.net_wm_moveresize_cancel)
1400                 moveresize_end(TRUE);
1401         } else if (msgtype == prop_atoms.net_moveresize_window) {
1402             gint ograv, x, y, w, h;
1403
1404             ograv = client->gravity;
1405
1406             if (e->xclient.data.l[0] & 0xff)
1407                 client->gravity = e->xclient.data.l[0] & 0xff;
1408
1409             if (e->xclient.data.l[0] & 1 << 8)
1410                 x = e->xclient.data.l[1];
1411             else
1412                 x = client->area.x;
1413             if (e->xclient.data.l[0] & 1 << 9)
1414                 y = e->xclient.data.l[2];
1415             else
1416                 y = client->area.y;
1417
1418             if (e->xclient.data.l[0] & 1 << 10) {
1419                 w = e->xclient.data.l[3];
1420
1421                 /* if x was not given, then use gravity to figure out the new
1422                    x.  the reference point should not be moved */
1423                 if (!(e->xclient.data.l[0] & 1 << 8))
1424                     client_gravity_resize_w(client, &x, client->area.width, w);
1425             }
1426             else
1427                 w = client->area.width;
1428
1429             if (e->xclient.data.l[0] & 1 << 11) {
1430                 h = e->xclient.data.l[4];
1431
1432                 /* if y was not given, then use gravity to figure out the new
1433                    y.  the reference point should not be moved */
1434                 if (!(e->xclient.data.l[0] & 1 << 9))
1435                     client_gravity_resize_h(client, &y, client->area.height,h);
1436             }
1437             else
1438                 h = client->area.height;
1439
1440             ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)\n",
1441                      e->xclient.data.l[0] & 1 << 8, x,
1442                      e->xclient.data.l[0] & 1 << 9, y,
1443                      client->gravity);
1444
1445             client_find_onscreen(client, &x, &y, w, h, FALSE);
1446
1447             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1448
1449             client->gravity = ograv;
1450         } else if (msgtype == prop_atoms.net_restack_window) {
1451             if (e->xclient.data.l[0] != 2) {
1452                 ob_debug_type(OB_DEBUG_APP_BUGS,
1453                               "_NET_RESTACK_WINDOW sent for window %s with "
1454                               "invalid source indication %ld\n",
1455                               client->title, e->xclient.data.l[0]);
1456             } else {
1457                 ObClient *sibling = NULL;
1458                 if (e->xclient.data.l[1]) {
1459                     ObWindow *win = g_hash_table_lookup
1460                         (window_map, &e->xclient.data.l[1]);
1461                     if (WINDOW_IS_CLIENT(win) &&
1462                         WINDOW_AS_CLIENT(win) != client)
1463                     {
1464                         sibling = WINDOW_AS_CLIENT(win);
1465                     }
1466                     if (sibling == NULL)
1467                         ob_debug_type(OB_DEBUG_APP_BUGS,
1468                                       "_NET_RESTACK_WINDOW sent for window %s "
1469                                       "with invalid sibling 0x%x\n",
1470                                  client->title, e->xclient.data.l[1]);
1471                 }
1472                 if (e->xclient.data.l[2] == Below ||
1473                     e->xclient.data.l[2] == BottomIf ||
1474                     e->xclient.data.l[2] == Above ||
1475                     e->xclient.data.l[2] == TopIf ||
1476                     e->xclient.data.l[2] == Opposite)
1477                 {
1478                     gulong ignore_start;
1479
1480                     if (!config_focus_under_mouse)
1481                         ignore_start = event_start_ignore_all_enters();
1482                     /* just raise, don't activate */
1483                     stacking_restack_request(client, sibling,
1484                                              e->xclient.data.l[2]);
1485                     if (!config_focus_under_mouse)
1486                         event_end_ignore_all_enters(ignore_start);
1487
1488                     /* send a synthetic ConfigureNotify, cuz this is supposed
1489                        to be like a ConfigureRequest. */
1490                     client_reconfigure(client, TRUE);
1491                 } else
1492                     ob_debug_type(OB_DEBUG_APP_BUGS,
1493                                   "_NET_RESTACK_WINDOW sent for window %s "
1494                                   "with invalid detail %d\n",
1495                                   client->title, e->xclient.data.l[2]);
1496             }
1497         }
1498         break;
1499     case PropertyNotify:
1500         /* validate cuz we query stuff off the client here */
1501         if (!client_validate(client)) break;
1502
1503         /* compress changes to a single property into a single change */
1504         while (XCheckTypedWindowEvent(ob_display, client->window,
1505                                       e->type, &ce)) {
1506             Atom a, b;
1507
1508             /* XXX: it would be nice to compress ALL changes to a property,
1509                not just changes in a row without other props between. */
1510
1511             a = ce.xproperty.atom;
1512             b = e->xproperty.atom;
1513
1514             if (a == b)
1515                 continue;
1516             if ((a == prop_atoms.net_wm_name ||
1517                  a == prop_atoms.wm_name ||
1518                  a == prop_atoms.net_wm_icon_name ||
1519                  a == prop_atoms.wm_icon_name)
1520                 &&
1521                 (b == prop_atoms.net_wm_name ||
1522                  b == prop_atoms.wm_name ||
1523                  b == prop_atoms.net_wm_icon_name ||
1524                  b == prop_atoms.wm_icon_name)) {
1525                 continue;
1526             }
1527             if (a == prop_atoms.net_wm_icon &&
1528                 b == prop_atoms.net_wm_icon)
1529                 continue;
1530
1531             XPutBackEvent(ob_display, &ce);
1532             break;
1533         }
1534
1535         msgtype = e->xproperty.atom;
1536         if (msgtype == XA_WM_NORMAL_HINTS) {
1537             ob_debug("Update NORMAL hints\n");
1538             client_update_normal_hints(client);
1539             /* normal hints can make a window non-resizable */
1540             client_setup_decor_and_functions(client, FALSE);
1541
1542             /* make sure the client's sizes are within its bounds, but only
1543                reconfigure the window if it needs to. emacs will update its
1544                normal hints every time it receives a conigurenotify */
1545             client_reconfigure(client, FALSE);
1546         } else if (msgtype == XA_WM_HINTS) {
1547             client_update_wmhints(client);
1548         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1549             client_update_transient_for(client);
1550             client_get_type_and_transientness(client);
1551             /* type may have changed, so update the layer */
1552             client_calc_layer(client);
1553             client_setup_decor_and_functions(client, TRUE);
1554         } else if (msgtype == prop_atoms.net_wm_name ||
1555                    msgtype == prop_atoms.wm_name ||
1556                    msgtype == prop_atoms.net_wm_icon_name ||
1557                    msgtype == prop_atoms.wm_icon_name) {
1558             client_update_title(client);
1559         } else if (msgtype == prop_atoms.wm_protocols) {
1560             client_update_protocols(client);
1561             client_setup_decor_and_functions(client, TRUE);
1562         }
1563         else if (msgtype == prop_atoms.net_wm_strut) {
1564             client_update_strut(client);
1565         }
1566         else if (msgtype == prop_atoms.net_wm_strut_partial) {
1567             client_update_strut(client);
1568         }
1569         else if (msgtype == prop_atoms.net_wm_icon) {
1570             client_update_icons(client);
1571         }
1572         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1573             client_update_icon_geometry(client);
1574         }
1575         else if (msgtype == prop_atoms.net_wm_user_time) {
1576             guint32 t;
1577             if (client == focus_client &&
1578                 PROP_GET32(client->window, net_wm_user_time, cardinal, &t) &&
1579                 t && !event_time_after(t, e->xproperty.time) &&
1580                 (!event_last_user_time ||
1581                  event_time_after(t, event_last_user_time)))
1582             {
1583                 event_last_user_time = t;
1584             }
1585         }
1586 #ifdef SYNC
1587         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1588             client_update_sync_request_counter(client);
1589         }
1590 #endif
1591         break;
1592     case ColormapNotify:
1593         client_update_colormap(client, e->xcolormap.colormap);
1594         break;
1595     default:
1596         ;
1597 #ifdef SHAPE
1598         if (extensions_shape && e->type == extensions_shape_event_basep) {
1599             client->shaped = ((XShapeEvent*)e)->shaped;
1600             frame_adjust_shape(client->frame);
1601         }
1602 #endif
1603     }
1604 }
1605
1606 static void event_handle_dock(ObDock *s, XEvent *e)
1607 {
1608     switch (e->type) {
1609     case ButtonPress:
1610         if (e->xbutton.button == 1)
1611             stacking_raise(DOCK_AS_WINDOW(s));
1612         else if (e->xbutton.button == 2)
1613             stacking_lower(DOCK_AS_WINDOW(s));
1614         break;
1615     case EnterNotify:
1616         dock_hide(FALSE);
1617         break;
1618     case LeaveNotify:
1619         /* don't hide when moving into a dock app */
1620         if (e->xcrossing.detail != NotifyInferior)
1621             dock_hide(TRUE);
1622         break;
1623     }
1624 }
1625
1626 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1627 {
1628     switch (e->type) {
1629     case MotionNotify:
1630         dock_app_drag(app, &e->xmotion);
1631         break;
1632     case UnmapNotify:
1633         if (app->ignore_unmaps) {
1634             app->ignore_unmaps--;
1635             break;
1636         }
1637         dock_remove(app, TRUE);
1638         break;
1639     case DestroyNotify:
1640         dock_remove(app, FALSE);
1641         break;
1642     case ReparentNotify:
1643         dock_remove(app, FALSE);
1644         break;
1645     case ConfigureNotify:
1646         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1647         break;
1648     }
1649 }
1650
1651 static ObMenuFrame* find_active_menu(void)
1652 {
1653     GList *it;
1654     ObMenuFrame *ret = NULL;
1655
1656     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1657         ret = it->data;
1658         if (ret->selected)
1659             break;
1660         ret = NULL;
1661     }
1662     return ret;
1663 }
1664
1665 static ObMenuFrame* find_active_or_last_menu(void)
1666 {
1667     ObMenuFrame *ret = NULL;
1668
1669     ret = find_active_menu();
1670     if (!ret && menu_frame_visible)
1671         ret = menu_frame_visible->data;
1672     return ret;
1673 }
1674
1675 static gboolean event_handle_menu_keyboard(XEvent *ev)
1676 {
1677     guint keycode, state;
1678     gunichar unikey;
1679     ObMenuFrame *frame;
1680     gboolean ret = FALSE;
1681
1682     keycode = ev->xkey.keycode;
1683     state = ev->xkey.state;
1684     unikey = translate_unichar(keycode);
1685
1686     frame = find_active_or_last_menu();
1687     if (frame == NULL)
1688         g_assert_not_reached(); /* there is no active menu */
1689
1690     /* Allow control while going thru the menu */
1691     else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1692         frame->got_press = TRUE;
1693
1694         if (keycode == ob_keycode(OB_KEY_ESCAPE)) {
1695             menu_frame_hide_all();
1696             ret = TRUE;
1697         }
1698
1699         else if (keycode == ob_keycode(OB_KEY_LEFT)) {
1700             /* Left goes to the parent menu */
1701             menu_frame_select(frame, NULL, TRUE);
1702             ret = TRUE;
1703         }
1704
1705         else if (keycode == ob_keycode(OB_KEY_RIGHT)) {
1706             /* Right goes to the selected submenu */
1707             if (frame->child) menu_frame_select_next(frame->child);
1708             ret = TRUE;
1709         }
1710
1711         else if (keycode == ob_keycode(OB_KEY_UP)) {
1712             menu_frame_select_previous(frame);
1713             ret = TRUE;
1714         }
1715
1716         else if (keycode == ob_keycode(OB_KEY_DOWN)) {
1717             menu_frame_select_next(frame);
1718             ret = TRUE;
1719         }
1720     }
1721
1722     /* Use KeyRelease events for running things so that the key release doesn't
1723        get sent to the focused application.
1724
1725        Allow ControlMask only, and don't bother if the menu is empty */
1726     else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1727              frame->entries && frame->got_press)
1728     {
1729         if (keycode == ob_keycode(OB_KEY_RETURN)) {
1730             /* Enter runs the active item or goes into the submenu.
1731                Control-Enter runs it without closing the menu. */
1732             if (frame->child)
1733                 menu_frame_select_next(frame->child);
1734             else if (frame->selected)
1735                 menu_entry_frame_execute(frame->selected, state);
1736
1737             ret = TRUE;
1738         }
1739
1740         /* keyboard accelerator shortcuts. (if it was a valid key) */
1741         else if (unikey != 0) {
1742             GList *start;
1743             GList *it;
1744             ObMenuEntryFrame *found = NULL;
1745             guint num_found = 0;
1746
1747             /* start after the selected one */
1748             start = frame->entries;
1749             if (frame->selected) {
1750                 for (it = start; frame->selected != it->data;
1751                      it = g_list_next(it))
1752                     g_assert(it != NULL); /* nothing was selected? */
1753                 /* next with wraparound */
1754                 start = g_list_next(it);
1755                 if (start == NULL) start = frame->entries;
1756             }
1757
1758             it = start;
1759             do {
1760                 ObMenuEntryFrame *e = it->data;
1761                 gunichar entrykey = 0;
1762
1763                 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1764                     entrykey = e->entry->data.normal.shortcut;
1765                 else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1766                     entrykey = e->entry->data.submenu.submenu->shortcut;
1767
1768                 if (unikey == entrykey) {
1769                     if (found == NULL) found = e;
1770                     ++num_found;
1771                 }
1772
1773                 /* next with wraparound */
1774                 it = g_list_next(it);
1775                 if (it == NULL) it = frame->entries;
1776             } while (it != start);
1777
1778             if (found) {
1779                 if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1780                     num_found == 1)
1781                 {
1782                     menu_frame_select(frame, found, TRUE);
1783                     usleep(50000); /* highlight the item for a short bit so the
1784                                       user can see what happened */
1785                     menu_entry_frame_execute(found, state);
1786                 } else {
1787                     menu_frame_select(frame, found, TRUE);
1788                     if (num_found == 1)
1789                         menu_frame_select_next(frame->child);
1790                 }
1791
1792                 ret = TRUE;
1793             }
1794         }
1795     }
1796
1797     return ret;
1798 }
1799
1800 static gboolean event_handle_menu(XEvent *ev)
1801 {
1802     ObMenuFrame *f;
1803     ObMenuEntryFrame *e;
1804     gboolean ret = TRUE;
1805
1806     switch (ev->type) {
1807     case ButtonRelease:
1808         if (menu_hide_delay_reached() &&
1809             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1810         {
1811             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1812                                             ev->xbutton.y_root)))
1813             {
1814                 menu_frame_select(e->frame, e, TRUE);
1815                 menu_entry_frame_execute(e, ev->xbutton.state);
1816             }
1817             else
1818                 menu_frame_hide_all();
1819         }
1820         break;
1821     case EnterNotify:
1822         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1823             if (e->ignore_enters)
1824                 --e->ignore_enters;
1825             else if (!(f = find_active_menu()) ||
1826                      f == e->frame ||
1827                      f->parent == e->frame ||
1828                      f->child == e->frame)
1829                 menu_frame_select(e->frame, e, FALSE);
1830         }
1831         break;
1832     case LeaveNotify:
1833         /*ignore leaves when we're already in the window */
1834         if (ev->xcrossing.detail == NotifyInferior)
1835             break;
1836
1837         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1838             (f = find_active_menu()) && f->selected == e &&
1839             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1840         {
1841             menu_frame_select(e->frame, NULL, FALSE);
1842         }
1843         break;
1844     case MotionNotify:
1845         if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1846                                         ev->xmotion.y_root)))
1847             if (!(f = find_active_menu()) ||
1848                 f == e->frame ||
1849                 f->parent == e->frame ||
1850                 f->child == e->frame)
1851                 menu_frame_select(e->frame, e, FALSE);
1852         break;
1853     case KeyPress:
1854     case KeyRelease:
1855         ret = event_handle_menu_keyboard(ev);
1856         break;
1857     }
1858     return ret;
1859 }
1860
1861 static void event_handle_user_input(ObClient *client, XEvent *e)
1862 {
1863     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1864              e->type == MotionNotify || e->type == KeyPress ||
1865              e->type == KeyRelease);
1866
1867     if (menu_frame_visible) {
1868         if (event_handle_menu(e))
1869             /* don't use the event if the menu used it, but if the menu
1870                didn't use it and it's a keypress that is bound, it will
1871                close the menu and be used */
1872             return;
1873     }
1874
1875     /* if the keyboard interactive action uses the event then dont
1876        use it for bindings. likewise is moveresize uses the event. */
1877     if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1878         if (moveresize_in_progress)
1879             /* make further actions work on the client being
1880                moved/resized */
1881             client = moveresize_client;
1882
1883         if (e->type == ButtonPress ||
1884             e->type == ButtonRelease ||
1885             e->type == MotionNotify)
1886         {
1887             /* the frame may not be "visible" but they can still click on it
1888                in the case where it is animating before disappearing */
1889             if (!client || !frame_iconify_animating(client->frame))
1890                 mouse_event(client, e);
1891         } else
1892             keyboard_event((focus_cycle_target ? focus_cycle_target :
1893                             (client ? client : focus_client)), e);
1894     }
1895 }
1896
1897 static void focus_delay_dest(gpointer data)
1898 {
1899     g_free(data);
1900 }
1901
1902 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1903 {
1904     const ObFocusDelayData *f1 = d1;
1905     return f1->client == d2;
1906 }
1907
1908 static gboolean focus_delay_func(gpointer data)
1909 {
1910     ObFocusDelayData *d = data;
1911     Time old = event_curtime;
1912
1913     /* don't move focus and kill the menu or the move/resize */
1914     if (menu_frame_visible || moveresize_in_progress) return FALSE;
1915
1916     event_curtime = d->time;
1917     event_curserial = d->serial;
1918     if (client_focus(d->client) && config_focus_raise)
1919         stacking_raise(CLIENT_AS_WINDOW(d->client));
1920     event_curtime = old;
1921     return FALSE; /* no repeat */
1922 }
1923
1924 static void focus_delay_client_dest(ObClient *client, gpointer data)
1925 {
1926     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1927                                      client, FALSE);
1928 }
1929
1930 void event_halt_focus_delay(void)
1931 {
1932     /* ignore all enter events up till the event which caused this to occur */
1933     if (event_curserial) event_ignore_enter_range(1, event_curserial);
1934     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1935 }
1936
1937 gulong event_start_ignore_all_enters(void)
1938 {
1939     return NextRequest(ob_display);
1940 }
1941
1942 static void event_ignore_enter_range(gulong start, gulong end)
1943 {
1944     ObSerialRange *r;
1945
1946     g_assert(start != 0);
1947     g_assert(end != 0);
1948
1949     r = g_new(ObSerialRange, 1);
1950     r->start = start;
1951     r->end = end;
1952     ignore_serials = g_slist_prepend(ignore_serials, r);
1953
1954     ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu\n",
1955                   r->start, r->end);
1956
1957     /* increment the serial so we don't ignore events we weren't meant to */
1958     PROP_ERASE(screen_support_win, motif_wm_hints);
1959 }
1960
1961 void event_end_ignore_all_enters(gulong start)
1962 {
1963     /* Use (NextRequest-1) so that we ignore up to the current serial only.
1964        Inside event_ignore_enter_range, we increment the serial by one, but if
1965        we ignore that serial too, then any enter events generated by mouse
1966        movement will be ignored until we create some further network traffic.
1967        Instead ignore up to NextRequest-1, then when we increment the serial,
1968        we will be *past* the range of ignored serials */
1969     event_ignore_enter_range(start, NextRequest(ob_display)-1);
1970 }
1971
1972 static gboolean is_enter_focus_event_ignored(gulong serial)
1973 {
1974     GSList *it, *next;
1975
1976     for (it = ignore_serials; it; it = next) {
1977         ObSerialRange *r = it->data;
1978
1979         next = g_slist_next(it);
1980
1981         if ((glong)(serial - r->end) > 0) {
1982             /* past the end */
1983             ignore_serials = g_slist_delete_link(ignore_serials, it);
1984             g_free(r);
1985         }
1986         else if ((glong)(serial - r->start) >= 0)
1987             return TRUE;
1988     }
1989     return FALSE;
1990 }
1991
1992 void event_cancel_all_key_grabs(void)
1993 {
1994     if (actions_interactive_act_running()) {
1995         actions_interactive_cancel_act();
1996         ob_debug("KILLED interactive action\n");
1997     }
1998     else if (menu_frame_visible) {
1999         menu_frame_hide_all();
2000         ob_debug("KILLED open menus\n");
2001     }
2002     else if (moveresize_in_progress) {
2003         moveresize_end(TRUE);
2004         ob_debug("KILLED interactive moveresize\n");
2005     }
2006     else if (grab_on_keyboard()) {
2007         ungrab_keyboard();
2008         ob_debug("KILLED active grab on keyboard\n");
2009     }
2010     else
2011         ungrab_passive_key();
2012
2013     XSync(ob_display, FALSE);
2014 }
2015
2016 gboolean event_time_after(Time t1, Time t2)
2017 {
2018     g_assert(t1 != CurrentTime);
2019     g_assert(t2 != CurrentTime);
2020
2021     /*
2022       Timestamp values wrap around (after about 49.7 days). The server, given
2023       its current time is represented by timestamp T, always interprets
2024       timestamps from clients by treating half of the timestamp space as being
2025       later in time than T.
2026       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2027     */
2028
2029     /* TIME_HALF is half of the number space of a Time type variable */
2030 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
2031
2032     if (t2 >= TIME_HALF)
2033         /* t2 is in the second half so t1 might wrap around and be smaller than
2034            t2 */
2035         return t1 >= t2 || t1 < (t2 + TIME_HALF);
2036     else
2037         /* t2 is in the first half so t1 has to come after it */
2038         return t1 >= t2 && t1 < (t2 + TIME_HALF);
2039 }
2040
2041 Time event_get_server_time(void)
2042 {
2043     /* Generate a timestamp */
2044     XEvent event;
2045
2046     XChangeProperty(ob_display, screen_support_win,
2047                     prop_atoms.wm_class, prop_atoms.string,
2048                     8, PropModeAppend, NULL, 0);
2049     XWindowEvent(ob_display, screen_support_win, PropertyChangeMask, &event);
2050     return event.xproperty.time;
2051 }