use focusouts from unmanaged windows (windows which have just closed)
[dana/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        Ben 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 "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "mouse.h"
35 #include "mainloop.h"
36 #include "framerender.h"
37 #include "focus.h"
38 #include "moveresize.h"
39 #include "group.h"
40 #include "stacking.h"
41 #include "extensions.h"
42
43 #include <X11/Xlib.h>
44 #include <X11/keysym.h>
45 #include <X11/Xatom.h>
46 #include <glib.h>
47
48 #ifdef HAVE_SYS_SELECT_H
49 #  include <sys/select.h>
50 #endif
51 #ifdef HAVE_SIGNAL_H
52 #  include <signal.h>
53 #endif
54 #ifdef XKB
55 #  include <X11/XKBlib.h>
56 #endif
57
58 #ifdef USE_SM
59 #include <X11/ICE/ICElib.h>
60 #endif
61
62 typedef struct
63 {
64     gboolean ignored;
65 } ObEventData;
66
67 typedef struct
68 {
69     ObClient *client;
70     Time time;
71 } ObFocusDelayData;
72
73 static void event_process(const XEvent *e, gpointer data);
74 static void event_handle_root(XEvent *e);
75 static void event_handle_menu(XEvent *e);
76 static void event_handle_dock(ObDock *s, XEvent *e);
77 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
78 static void event_handle_client(ObClient *c, XEvent *e);
79 static void event_handle_group(ObGroup *g, XEvent *e);
80
81 static void focus_delay_dest(gpointer data);
82 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
83 static gboolean focus_delay_func(gpointer data);
84 static void focus_delay_client_dest(ObClient *client, gpointer data);
85
86 static gboolean menu_hide_delay_func(gpointer data);
87
88 /* The time for the current event being processed */
89 Time event_curtime = CurrentTime;
90
91 /*! The value of the mask for the NumLock modifier */
92 guint NumLockMask;
93 /*! The value of the mask for the ScrollLock modifier */
94 guint ScrollLockMask;
95 /*! The key codes for the modifier keys */
96 static XModifierKeymap *modmap;
97 /*! Table of the constant modifier masks */
98 static const gint mask_table[] = {
99     ShiftMask, LockMask, ControlMask, Mod1Mask,
100     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
101 };
102 static gint mask_table_size;
103
104 static guint ignore_enter_focus = 0;
105
106 static gboolean menu_can_hide;
107
108 #ifdef USE_SM
109 static void ice_handler(gint fd, gpointer conn)
110 {
111     Bool b;
112     IceProcessMessages(conn, NULL, &b);
113 }
114
115 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
116                       IcePointer *watch_data)
117 {
118     static gint fd = -1;
119
120     if (opening) {
121         fd = IceConnectionNumber(conn);
122         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
123     } else {
124         ob_main_loop_fd_remove(ob_main_loop, fd);
125         fd = -1;
126     }
127 }
128 #endif
129
130 void event_startup(gboolean reconfig)
131 {
132     if (reconfig) return;
133
134     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
135      
136     /* get lock masks that are defined by the display (not constant) */
137     modmap = XGetModifierMapping(ob_display);
138     g_assert(modmap);
139     if (modmap && modmap->max_keypermod > 0) {
140         size_t cnt;
141         const size_t size = mask_table_size * modmap->max_keypermod;
142         /* get the values of the keyboard lock modifiers
143            Note: Caps lock is not retrieved the same way as Scroll and Num
144            lock since it doesn't need to be. */
145         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
146         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
147                                                      XK_Scroll_Lock);
148
149         for (cnt = 0; cnt < size; ++cnt) {
150             if (! modmap->modifiermap[cnt]) continue;
151
152             if (num_lock == modmap->modifiermap[cnt])
153                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
154             if (scroll_lock == modmap->modifiermap[cnt])
155                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
156         }
157     }
158
159     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
160
161 #ifdef USE_SM
162     IceAddConnectionWatch(ice_watch, NULL);
163 #endif
164
165     client_add_destructor(focus_delay_client_dest, NULL);
166 }
167
168 void event_shutdown(gboolean reconfig)
169 {
170     if (reconfig) return;
171
172 #ifdef USE_SM
173     IceRemoveConnectionWatch(ice_watch, NULL);
174 #endif
175
176     client_remove_destructor(focus_delay_client_dest);
177     XFreeModifiermap(modmap);
178 }
179
180 static Window event_get_window(XEvent *e)
181 {
182     Window window;
183
184     /* pick a window */
185     switch (e->type) {
186     case SelectionClear:
187         window = RootWindow(ob_display, ob_screen);
188         break;
189     case MapRequest:
190         window = e->xmap.window;
191         break;
192     case UnmapNotify:
193         window = e->xunmap.window;
194         break;
195     case DestroyNotify:
196         window = e->xdestroywindow.window;
197         break;
198     case ConfigureRequest:
199         window = e->xconfigurerequest.window;
200         break;
201     case ConfigureNotify:
202         window = e->xconfigure.window;
203         break;
204     default:
205 #ifdef XKB
206         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
207             switch (((XkbAnyEvent*)e)->xkb_type) {
208             case XkbBellNotify:
209                 window = ((XkbBellNotifyEvent*)e)->window;
210             default:
211                 window = None;
212             }
213         } else
214 #endif
215             window = e->xany.window;
216     }
217     return window;
218 }
219
220 static void event_set_curtime(XEvent *e)
221 {
222     Time t = CurrentTime;
223
224     /* grab the lasttime and hack up the state */
225     switch (e->type) {
226     case ButtonPress:
227     case ButtonRelease:
228         t = e->xbutton.time;
229         break;
230     case KeyPress:
231         t = e->xkey.time;
232         break;
233     case KeyRelease:
234         t = e->xkey.time;
235         break;
236     case MotionNotify:
237         t = e->xmotion.time;
238         break;
239     case PropertyNotify:
240         t = e->xproperty.time;
241         break;
242     case EnterNotify:
243     case LeaveNotify:
244         t = e->xcrossing.time;
245         break;
246     default:
247         /* if more event types are anticipated, get their timestamp
248            explicitly */
249         break;
250     }
251
252     event_curtime = t;
253 }
254
255 #define STRIP_MODS(s) \
256         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
257         /* kill off the Button1Mask etc, only want the modifiers */ \
258         s &= (ControlMask | ShiftMask | Mod1Mask | \
259               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
260
261 static void event_hack_mods(XEvent *e)
262 {
263 #ifdef XKB
264     XkbStateRec xkb_state;
265 #endif
266     KeyCode *kp;
267     gint i, k;
268
269     switch (e->type) {
270     case ButtonPress:
271     case ButtonRelease:
272         STRIP_MODS(e->xbutton.state);
273         break;
274     case KeyPress:
275         STRIP_MODS(e->xkey.state);
276         break;
277     case KeyRelease:
278         STRIP_MODS(e->xkey.state);
279         /* remove from the state the mask of the modifier being released, if
280            it is a modifier key being released (this is a little ugly..) */
281 #ifdef XKB
282         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
283             e->xkey.state = xkb_state.compat_state;
284             break;
285         }
286 #endif
287         kp = modmap->modifiermap;
288         for (i = 0; i < mask_table_size; ++i) {
289             for (k = 0; k < modmap->max_keypermod; ++k) {
290                 if (*kp == e->xkey.keycode) { /* found the keycode */
291                     /* remove the mask for it */
292                     e->xkey.state &= ~mask_table[i];
293                     /* cause the first loop to break; */
294                     i = mask_table_size;
295                     break; /* get outta here! */
296                 }
297                 ++kp;
298             }
299         }
300         break;
301     case MotionNotify:
302         STRIP_MODS(e->xmotion.state);
303         /* compress events */
304         {
305             XEvent ce;
306             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
307                                           e->type, &ce)) {
308                 e->xmotion.x_root = ce.xmotion.x_root;
309                 e->xmotion.y_root = ce.xmotion.y_root;
310             }
311         }
312         break;
313     }
314 }
315
316 static gboolean wanted_focusevent(XEvent *e)
317 {
318     gint mode = e->xfocus.mode;
319     gint detail = e->xfocus.detail;
320     Window win = e->xany.window;
321
322     if (e->type == FocusIn) {
323
324         /* These are ones we never want.. */
325
326         /* This means focus was given by a keyboard/mouse grab. */
327         if (mode == NotifyGrab)
328             return FALSE;
329         /* This means focus was given back from a keyboard/mouse grab. */
330         if (mode == NotifyUngrab)
331             return FALSE;
332
333         /* These are the ones we want.. */
334
335         if (win == RootWindow(ob_display, ob_screen)) {
336             /* This means focus reverted off of a client */
337             if (detail == NotifyPointerRoot || detail == NotifyDetailNone ||
338                 detail == NotifyInferior)
339                 return TRUE;
340             else
341                 return FALSE;
342         }
343
344         /* This means focus moved from the root window to a client */
345         if (detail == NotifyVirtual)
346             return TRUE;
347         /* This means focus moved from one client to another */
348         if (detail == NotifyNonlinearVirtual)
349             return TRUE;
350
351         /* Otherwise.. */
352         return FALSE;
353     } else {
354         g_assert(e->type == FocusOut);
355
356
357         /* These are ones we never want.. */
358
359         /* This means focus was taken by a keyboard/mouse grab. */
360         if (mode == NotifyGrab)
361             return FALSE;
362
363         /* Focus left the root window revertedto state */
364         if (win == RootWindow(ob_display, ob_screen))
365             return FALSE;
366
367         /* These are the ones we want.. */
368
369         /* This means focus moved from a client to the root window */
370         if (detail == NotifyVirtual)
371             return TRUE;
372         /* This means focus moved from one client to another */
373         if (detail == NotifyNonlinearVirtual)
374             return TRUE;
375
376         /* Otherwise.. */
377         return FALSE;
378     }
379 }
380
381 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
382 {
383     return e->type == FocusIn && wanted_focusevent(e);
384 }
385
386 static gboolean event_ignore(XEvent *e, ObClient *client)
387 {
388     switch(e->type) {
389     case FocusIn:
390         if (!wanted_focusevent(e))
391             return TRUE;
392         break;
393     case FocusOut:
394         if (!wanted_focusevent(e))
395             return TRUE;
396         break;
397     }
398     return FALSE;
399 }
400
401 static void event_process(const XEvent *ec, gpointer data)
402 {
403     Window window;
404     ObGroup *group = NULL;
405     ObClient *client = NULL;
406     ObDock *dock = NULL;
407     ObDockApp *dockapp = NULL;
408     ObWindow *obwin = NULL;
409     XEvent ee, *e;
410     ObEventData *ed = data;
411
412     /* make a copy we can mangle */
413     ee = *ec;
414     e = &ee;
415
416     window = event_get_window(e);
417     if (!(e->type == PropertyNotify &&
418           (group = g_hash_table_lookup(group_map, &window))))
419         if ((obwin = g_hash_table_lookup(window_map, &window))) {
420             switch (obwin->type) {
421             case Window_Dock:
422                 dock = WINDOW_AS_DOCK(obwin);
423                 break;
424             case Window_DockApp:
425                 dockapp = WINDOW_AS_DOCKAPP(obwin);
426                 break;
427             case Window_Client:
428                 client = WINDOW_AS_CLIENT(obwin);
429                 break;
430             case Window_Menu:
431             case Window_Internal:
432                 /* not to be used for events */
433                 g_assert_not_reached();
434                 break;
435             }
436         }
437
438     event_set_curtime(e);
439     event_hack_mods(e);
440     if (event_ignore(e, client)) {
441         if (ed)
442             ed->ignored = TRUE;
443         return;
444     } else if (ed)
445             ed->ignored = FALSE;
446
447     /* deal with it in the kernel */
448
449     if (menu_frame_visible &&
450         (e->type == EnterNotify || e->type == LeaveNotify))
451     {
452         /* crossing events for menu */
453         event_handle_menu(e);
454     } else if (e->type == FocusIn) {
455         if (client && client != focus_client) {
456             frame_adjust_focus(client->frame, TRUE);
457             focus_set_client(client);
458             client_calc_layer(client);
459         }
460     } else if (e->type == FocusOut) {
461         gboolean nomove = FALSE;
462         XEvent ce;
463
464         ob_debug_type(OB_DEBUG_FOCUS, "FocusOut Event\n");
465
466         /* Look for the followup FocusIn */
467         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
468             /* There is no FocusIn, this means focus went to a window that
469                is not being managed, or a window on another screen. */
470             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a black hole !\n");
471             /* nothing is focused */
472             focus_set_client(NULL);
473         } else if (ce.xany.window == e->xany.window) {
474             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
475             /* If focus didn't actually move anywhere, there is nothing to do*/
476             nomove = TRUE;
477         } else if (ce.xfocus.detail == NotifyPointerRoot ||
478                    ce.xfocus.detail == NotifyDetailNone ||
479                    ce.xfocus.detail == NotifyInferior) {
480             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to root\n");
481             /* Focus has been reverted to the root window or nothing
482                FocusOut events come after UnmapNotify, so we don't need to
483                worry about focusing an invalid window
484              */
485             focus_fallback(TRUE);
486         } else {
487             /* Focus did move, so process the FocusIn event */
488             ObEventData ed = { .ignored = FALSE };
489             event_process(&ce, &ed);
490             if (ed.ignored) {
491                 /* The FocusIn was ignored, this means it was on a window
492                    that isn't a client. */
493                 ob_debug_type(OB_DEBUG_FOCUS,
494                               "Focus went to an unmanaged window 0x%x !\n",
495                               ce.xfocus.window);
496                 focus_fallback(TRUE);
497             }
498         }
499
500         if (client && !nomove) {
501             frame_adjust_focus(client->frame, FALSE);
502             /* focus_set_client has already been called for sure */
503             client_calc_layer(client);
504         }
505     } else if (group)
506         event_handle_group(group, e);
507     else if (client)
508         event_handle_client(client, e);
509     else if (dockapp)
510         event_handle_dockapp(dockapp, e);
511     else if (dock)
512         event_handle_dock(dock, e);
513     else if (window == RootWindow(ob_display, ob_screen))
514         event_handle_root(e);
515     else if (e->type == MapRequest)
516         client_manage(window);
517     else if (e->type == ConfigureRequest) {
518         /* unhandled configure requests must be used to configure the
519            window directly */
520         XWindowChanges xwc;
521
522         xwc.x = e->xconfigurerequest.x;
523         xwc.y = e->xconfigurerequest.y;
524         xwc.width = e->xconfigurerequest.width;
525         xwc.height = e->xconfigurerequest.height;
526         xwc.border_width = e->xconfigurerequest.border_width;
527         xwc.sibling = e->xconfigurerequest.above;
528         xwc.stack_mode = e->xconfigurerequest.detail;
529        
530         /* we are not to be held responsible if someone sends us an
531            invalid request! */
532         xerror_set_ignore(TRUE);
533         XConfigureWindow(ob_display, window,
534                          e->xconfigurerequest.value_mask, &xwc);
535         xerror_set_ignore(FALSE);
536     }
537
538     /* user input (action-bound) events */
539     if (e->type == ButtonPress || e->type == ButtonRelease ||
540         e->type == MotionNotify || e->type == KeyPress ||
541         e->type == KeyRelease)
542     {
543         if (menu_frame_visible)
544             event_handle_menu(e);
545         else {
546             if (!keyboard_process_interactive_grab(e, &client)) {
547                 if (moveresize_in_progress) {
548                     moveresize_event(e);
549
550                     /* make further actions work on the client being
551                        moved/resized */
552                     client = moveresize_client;
553                 }
554
555                 menu_can_hide = FALSE;
556                 ob_main_loop_timeout_add(ob_main_loop,
557                                          config_menu_hide_delay * 1000,
558                                          menu_hide_delay_func,
559                                          NULL, g_direct_equal, NULL);
560
561                 if (e->type == ButtonPress || e->type == ButtonRelease ||
562                     e->type == MotionNotify) {
563                     mouse_event(client, e);
564                 } else if (e->type == KeyPress) {
565                     keyboard_event((focus_cycle_target ? focus_cycle_target :
566                                     client), e);
567                 }
568             }
569         }
570     }
571     /* if something happens and it's not from an XEvent, then we don't know
572        the time */
573     event_curtime = CurrentTime;
574 }
575
576 static void event_handle_root(XEvent *e)
577 {
578     Atom msgtype;
579      
580     switch(e->type) {
581     case SelectionClear:
582         ob_debug("Another WM has requested to replace us. Exiting.\n");
583         ob_exit_replace();
584         break;
585
586     case ClientMessage:
587         if (e->xclient.format != 32) break;
588
589         msgtype = e->xclient.message_type;
590         if (msgtype == prop_atoms.net_current_desktop) {
591             guint d = e->xclient.data.l[0];
592             if (d < screen_num_desktops) {
593                 event_curtime = e->xclient.data.l[1];
594                 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime);
595                 screen_set_desktop(d);
596             }
597         } else if (msgtype == prop_atoms.net_number_of_desktops) {
598             guint d = e->xclient.data.l[0];
599             if (d > 0)
600                 screen_set_num_desktops(d);
601         } else if (msgtype == prop_atoms.net_showing_desktop) {
602             screen_show_desktop(e->xclient.data.l[0] != 0);
603         } else if (msgtype == prop_atoms.ob_control) {
604             if (e->xclient.data.l[0] == 1)
605                 ob_reconfigure();
606             else if (e->xclient.data.l[0] == 2)
607                 ob_restart();
608         }
609         break;
610     case PropertyNotify:
611         if (e->xproperty.atom == prop_atoms.net_desktop_names)
612             screen_update_desktop_names();
613         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
614             screen_update_layout();
615         break;
616     case ConfigureNotify:
617 #ifdef XRANDR
618         XRRUpdateConfiguration(e);
619 #endif
620         screen_resize();
621         break;
622     default:
623         ;
624     }
625 }
626
627 static void event_handle_group(ObGroup *group, XEvent *e)
628 {
629     GSList *it;
630
631     g_assert(e->type == PropertyNotify);
632
633     for (it = group->members; it; it = g_slist_next(it))
634         event_handle_client(it->data, e);
635 }
636
637 void event_enter_client(ObClient *client)
638 {
639     g_assert(config_focus_follow);
640
641     if (client_normal(client) && client_can_focus(client)) {
642         if (config_focus_delay) {
643             ObFocusDelayData *data;
644
645             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
646
647             data = g_new(ObFocusDelayData, 1);
648             data->client = client;
649             data->time = event_curtime;
650
651             ob_main_loop_timeout_add(ob_main_loop,
652                                      config_focus_delay,
653                                      focus_delay_func,
654                                      data, focus_delay_cmp, focus_delay_dest);
655         } else {
656             ObFocusDelayData data;
657             data.client = client;
658             data.time = event_curtime;
659             focus_delay_func(&data);
660         }
661     }
662 }
663
664 static void event_handle_client(ObClient *client, XEvent *e)
665 {
666     XEvent ce;
667     Atom msgtype;
668     gint i=0;
669     ObFrameContext con;
670      
671     switch (e->type) {
672     case VisibilityNotify:
673         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
674         break;
675     case ButtonPress:
676     case ButtonRelease:
677         /* Wheel buttons don't draw because they are an instant click, so it
678            is a waste of resources to go drawing it. */
679         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
680             con = frame_context(client, e->xbutton.window);
681             con = mouse_button_frame_context(con, e->xbutton.button);
682             switch (con) {
683             case OB_FRAME_CONTEXT_MAXIMIZE:
684                 client->frame->max_press = (e->type == ButtonPress);
685                 framerender_frame(client->frame);
686                 break;
687             case OB_FRAME_CONTEXT_CLOSE:
688                 client->frame->close_press = (e->type == ButtonPress);
689                 framerender_frame(client->frame);
690                 break;
691             case OB_FRAME_CONTEXT_ICONIFY:
692                 client->frame->iconify_press = (e->type == ButtonPress);
693                 framerender_frame(client->frame);
694                 break;
695             case OB_FRAME_CONTEXT_ALLDESKTOPS:
696                 client->frame->desk_press = (e->type == ButtonPress);
697                 framerender_frame(client->frame);
698                 break; 
699             case OB_FRAME_CONTEXT_SHADE:
700                 client->frame->shade_press = (e->type == ButtonPress);
701                 framerender_frame(client->frame);
702                 break;
703             default:
704                 /* nothing changes with clicks for any other contexts */
705                 break;
706             }
707         }
708         break;
709     case LeaveNotify:
710         con = frame_context(client, e->xcrossing.window);
711         switch (con) {
712         case OB_FRAME_CONTEXT_MAXIMIZE:
713             client->frame->max_hover = FALSE;
714             frame_adjust_state(client->frame);
715             break;
716         case OB_FRAME_CONTEXT_ALLDESKTOPS:
717             client->frame->desk_hover = FALSE;
718             frame_adjust_state(client->frame);
719             break;
720         case OB_FRAME_CONTEXT_SHADE:
721             client->frame->shade_hover = FALSE;
722             frame_adjust_state(client->frame);
723             break;
724         case OB_FRAME_CONTEXT_ICONIFY:
725             client->frame->iconify_hover = FALSE;
726             frame_adjust_state(client->frame);
727             break;
728         case OB_FRAME_CONTEXT_CLOSE:
729             client->frame->close_hover = FALSE;
730             frame_adjust_state(client->frame);
731             break;
732         case OB_FRAME_CONTEXT_FRAME:
733             if (keyboard_interactively_grabbed())
734                 break;
735             if (config_focus_follow && config_focus_delay)
736                 ob_main_loop_timeout_remove_data(ob_main_loop,
737                                                  focus_delay_func,
738                                                  client, FALSE);
739             break;
740         default:
741             break;
742         }
743         break;
744     case EnterNotify:
745     {
746         gboolean nofocus = FALSE;
747
748         if (ignore_enter_focus) {
749             ignore_enter_focus--;
750             nofocus = TRUE;
751         }
752
753         con = frame_context(client, e->xcrossing.window);
754         switch (con) {
755         case OB_FRAME_CONTEXT_MAXIMIZE:
756             client->frame->max_hover = TRUE;
757             frame_adjust_state(client->frame);
758             break;
759         case OB_FRAME_CONTEXT_ALLDESKTOPS:
760             client->frame->desk_hover = TRUE;
761             frame_adjust_state(client->frame);
762             break;
763         case OB_FRAME_CONTEXT_SHADE:
764             client->frame->shade_hover = TRUE;
765             frame_adjust_state(client->frame);
766             break;
767         case OB_FRAME_CONTEXT_ICONIFY:
768             client->frame->iconify_hover = TRUE;
769             frame_adjust_state(client->frame);
770             break;
771         case OB_FRAME_CONTEXT_CLOSE:
772             client->frame->close_hover = TRUE;
773             frame_adjust_state(client->frame);
774             break;
775         case OB_FRAME_CONTEXT_FRAME:
776             if (keyboard_interactively_grabbed())
777                 break;
778             if (e->xcrossing.mode == NotifyGrab ||
779                 e->xcrossing.mode == NotifyUngrab)
780             {
781                 ob_debug_type(OB_DEBUG_FOCUS,
782                               "%sNotify mode %d detail %d on %lx IGNORED\n",
783                               (e->type == EnterNotify ? "Enter" : "Leave"),
784                               e->xcrossing.mode,
785                               e->xcrossing.detail, client?client->window:0);
786             } else {
787                 ob_debug_type(OB_DEBUG_FOCUS,
788                               "%sNotify mode %d detail %d on %lx, "
789                               "focusing window: %d\n",
790                               (e->type == EnterNotify ? "Enter" : "Leave"),
791                               e->xcrossing.mode,
792                               e->xcrossing.detail, (client?client->window:0),
793                               !nofocus);
794                 if (!nofocus && config_focus_follow)
795                     event_enter_client(client);
796             }
797             break;
798         default:
799             break;
800         }
801         break;
802     }
803     case ConfigureRequest:
804         /* compress these */
805         while (XCheckTypedWindowEvent(ob_display, client->window,
806                                       ConfigureRequest, &ce)) {
807             ++i;
808             /* XXX if this causes bad things.. we can compress config req's
809                with the same mask. */
810             e->xconfigurerequest.value_mask |=
811                 ce.xconfigurerequest.value_mask;
812             if (ce.xconfigurerequest.value_mask & CWX)
813                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
814             if (ce.xconfigurerequest.value_mask & CWY)
815                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
816             if (ce.xconfigurerequest.value_mask & CWWidth)
817                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
818             if (ce.xconfigurerequest.value_mask & CWHeight)
819                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
820             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
821                 e->xconfigurerequest.border_width =
822                     ce.xconfigurerequest.border_width;
823             if (ce.xconfigurerequest.value_mask & CWStackMode)
824                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
825         }
826
827         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
828         if (client->iconic || client->shaded) return;
829
830         /* resize, then move, as specified in the EWMH section 7.7 */
831         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
832                                                CWX | CWY |
833                                                CWBorderWidth)) {
834             gint x, y, w, h;
835             ObCorner corner;
836
837             if (e->xconfigurerequest.value_mask & CWBorderWidth)
838                 client->border_width = e->xconfigurerequest.border_width;
839
840             x = (e->xconfigurerequest.value_mask & CWX) ?
841                 e->xconfigurerequest.x : client->area.x;
842             y = (e->xconfigurerequest.value_mask & CWY) ?
843                 e->xconfigurerequest.y : client->area.y;
844             w = (e->xconfigurerequest.value_mask & CWWidth) ?
845                 e->xconfigurerequest.width : client->area.width;
846             h = (e->xconfigurerequest.value_mask & CWHeight) ?
847                 e->xconfigurerequest.height : client->area.height;
848
849             {
850                 gint newx = x;
851                 gint newy = y;
852                 gint fw = w +
853                      client->frame->size.left + client->frame->size.right;
854                 gint fh = h +
855                      client->frame->size.top + client->frame->size.bottom;
856                 /* make this rude for size-only changes but not for position
857                    changes.. */
858                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
859                                    (e->xconfigurerequest.value_mask & CWY));
860
861                 client_find_onscreen(client, &newx, &newy, fw, fh,
862                                      !moving);
863                 if (e->xconfigurerequest.value_mask & CWX)
864                     x = newx;
865                 if (e->xconfigurerequest.value_mask & CWY)
866                     y = newy;
867             }
868
869             switch (client->gravity) {
870             case NorthEastGravity:
871             case EastGravity:
872                 corner = OB_CORNER_TOPRIGHT;
873                 break;
874             case SouthWestGravity:
875             case SouthGravity:
876                 corner = OB_CORNER_BOTTOMLEFT;
877                 break;
878             case SouthEastGravity:
879                 corner = OB_CORNER_BOTTOMRIGHT;
880                 break;
881             default:     /* NorthWest, Static, etc */
882                 corner = OB_CORNER_TOPLEFT;
883             }
884
885             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
886                                   TRUE);
887         }
888
889         if (e->xconfigurerequest.value_mask & CWStackMode) {
890             switch (e->xconfigurerequest.detail) {
891             case Below:
892             case BottomIf:
893                 /* Apps are so rude. And this is totally disconnected from
894                    activation/focus. Bleh. */
895                 /*client_lower(client);*/
896                 break;
897
898             case Above:
899             case TopIf:
900             default:
901                 /* Apps are so rude. And this is totally disconnected from
902                    activation/focus. Bleh. */
903                 /*client_raise(client);*/
904                 break;
905             }
906         }
907         break;
908     case UnmapNotify:
909         if (client->ignore_unmaps) {
910             client->ignore_unmaps--;
911             break;
912         }
913         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
914                  "ignores left %d\n",
915                  client->window, e->xunmap.event, e->xunmap.from_configure,
916                  client->ignore_unmaps);
917         client_unmanage(client);
918         break;
919     case DestroyNotify:
920         ob_debug("DestroyNotify for window 0x%x\n", client->window);
921         client_unmanage(client);
922         break;
923     case ReparentNotify:
924         /* this is when the client is first taken captive in the frame */
925         if (e->xreparent.parent == client->frame->plate) break;
926
927         /*
928           This event is quite rare and is usually handled in unmapHandler.
929           However, if the window is unmapped when the reparent event occurs,
930           the window manager never sees it because an unmap event is not sent
931           to an already unmapped window.
932         */
933
934         /* we don't want the reparent event, put it back on the stack for the
935            X server to deal with after we unmanage the window */
936         XPutBackEvent(ob_display, e);
937      
938         ob_debug("ReparentNotify for window 0x%x\n", client->window);
939         client_unmanage(client);
940         break;
941     case MapRequest:
942         ob_debug("MapRequest for 0x%lx\n", client->window);
943         if (!client->iconic) break; /* this normally doesn't happen, but if it
944                                        does, we don't want it!
945                                        it can happen now when the window is on
946                                        another desktop, but we still don't
947                                        want it! */
948         client_activate(client, FALSE, TRUE);
949         break;
950     case ClientMessage:
951         /* validate cuz we query stuff off the client here */
952         if (!client_validate(client)) break;
953
954         if (e->xclient.format != 32) return;
955
956         msgtype = e->xclient.message_type;
957         if (msgtype == prop_atoms.wm_change_state) {
958             /* compress changes into a single change */
959             while (XCheckTypedWindowEvent(ob_display, client->window,
960                                           e->type, &ce)) {
961                 /* XXX: it would be nice to compress ALL messages of a
962                    type, not just messages in a row without other
963                    message types between. */
964                 if (ce.xclient.message_type != msgtype) {
965                     XPutBackEvent(ob_display, &ce);
966                     break;
967                 }
968                 e->xclient = ce.xclient;
969             }
970             client_set_wm_state(client, e->xclient.data.l[0]);
971         } else if (msgtype == prop_atoms.net_wm_desktop) {
972             /* compress changes into a single change */
973             while (XCheckTypedWindowEvent(ob_display, client->window,
974                                           e->type, &ce)) {
975                 /* XXX: it would be nice to compress ALL messages of a
976                    type, not just messages in a row without other
977                    message types between. */
978                 if (ce.xclient.message_type != msgtype) {
979                     XPutBackEvent(ob_display, &ce);
980                     break;
981                 }
982                 e->xclient = ce.xclient;
983             }
984             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
985                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
986                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
987                                    FALSE);
988         } else if (msgtype == prop_atoms.net_wm_state) {
989             /* can't compress these */
990             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
991                      (e->xclient.data.l[0] == 0 ? "Remove" :
992                       e->xclient.data.l[0] == 1 ? "Add" :
993                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
994                      e->xclient.data.l[1], e->xclient.data.l[2],
995                      client->window);
996             client_set_state(client, e->xclient.data.l[0],
997                              e->xclient.data.l[1], e->xclient.data.l[2]);
998         } else if (msgtype == prop_atoms.net_close_window) {
999             ob_debug("net_close_window for 0x%lx\n", client->window);
1000             client_close(client);
1001         } else if (msgtype == prop_atoms.net_active_window) {
1002             ob_debug("net_active_window for 0x%lx source=%s\n",
1003                      client->window,
1004                      (e->xclient.data.l[0] == 0 ? "unknown" :
1005                       (e->xclient.data.l[0] == 1 ? "application" :
1006                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1007             /* XXX make use of data.l[2] ! */
1008             event_curtime = e->xclient.data.l[1];
1009             client_activate(client, FALSE,
1010                             (e->xclient.data.l[0] == 0 ||
1011                              e->xclient.data.l[0] == 2));
1012         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1013             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1014                      client->window, e->xclient.data.l[2]);
1015             if ((Atom)e->xclient.data.l[2] ==
1016                 prop_atoms.net_wm_moveresize_size_topleft ||
1017                 (Atom)e->xclient.data.l[2] ==
1018                 prop_atoms.net_wm_moveresize_size_top ||
1019                 (Atom)e->xclient.data.l[2] ==
1020                 prop_atoms.net_wm_moveresize_size_topright ||
1021                 (Atom)e->xclient.data.l[2] ==
1022                 prop_atoms.net_wm_moveresize_size_right ||
1023                 (Atom)e->xclient.data.l[2] ==
1024                 prop_atoms.net_wm_moveresize_size_right ||
1025                 (Atom)e->xclient.data.l[2] ==
1026                 prop_atoms.net_wm_moveresize_size_bottomright ||
1027                 (Atom)e->xclient.data.l[2] ==
1028                 prop_atoms.net_wm_moveresize_size_bottom ||
1029                 (Atom)e->xclient.data.l[2] ==
1030                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1031                 (Atom)e->xclient.data.l[2] ==
1032                 prop_atoms.net_wm_moveresize_size_left ||
1033                 (Atom)e->xclient.data.l[2] ==
1034                 prop_atoms.net_wm_moveresize_move ||
1035                 (Atom)e->xclient.data.l[2] ==
1036                 prop_atoms.net_wm_moveresize_size_keyboard ||
1037                 (Atom)e->xclient.data.l[2] ==
1038                 prop_atoms.net_wm_moveresize_move_keyboard) {
1039
1040                 moveresize_start(client, e->xclient.data.l[0],
1041                                  e->xclient.data.l[1], e->xclient.data.l[3],
1042                                  e->xclient.data.l[2]);
1043             }
1044             else if ((Atom)e->xclient.data.l[2] ==
1045                      prop_atoms.net_wm_moveresize_cancel)
1046                 moveresize_end(TRUE);
1047         } else if (msgtype == prop_atoms.net_moveresize_window) {
1048             gint oldg = client->gravity;
1049             gint tmpg, x, y, w, h;
1050
1051             if (e->xclient.data.l[0] & 0xff)
1052                 tmpg = e->xclient.data.l[0] & 0xff;
1053             else
1054                 tmpg = oldg;
1055
1056             if (e->xclient.data.l[0] & 1 << 8)
1057                 x = e->xclient.data.l[1];
1058             else
1059                 x = client->area.x;
1060             if (e->xclient.data.l[0] & 1 << 9)
1061                 y = e->xclient.data.l[2];
1062             else
1063                 y = client->area.y;
1064             if (e->xclient.data.l[0] & 1 << 10)
1065                 w = e->xclient.data.l[3];
1066             else
1067                 w = client->area.width;
1068             if (e->xclient.data.l[0] & 1 << 11)
1069                 h = e->xclient.data.l[4];
1070             else
1071                 h = client->area.height;
1072             client->gravity = tmpg;
1073
1074             {
1075                 gint newx = x;
1076                 gint newy = y;
1077                 gint fw = w +
1078                      client->frame->size.left + client->frame->size.right;
1079                 gint fh = h +
1080                      client->frame->size.top + client->frame->size.bottom;
1081                 client_find_onscreen(client, &newx, &newy, fw, fh,
1082                                      client_normal(client));
1083                 if (e->xclient.data.l[0] & 1 << 8)
1084                     x = newx;
1085                 if (e->xclient.data.l[0] & 1 << 9)
1086                     y = newy;
1087             }
1088
1089             client_configure(client, OB_CORNER_TOPLEFT,
1090                              x, y, w, h, FALSE, TRUE);
1091
1092             client->gravity = oldg;
1093         }
1094         break;
1095     case PropertyNotify:
1096         /* validate cuz we query stuff off the client here */
1097         if (!client_validate(client)) break;
1098   
1099         /* compress changes to a single property into a single change */
1100         while (XCheckTypedWindowEvent(ob_display, client->window,
1101                                       e->type, &ce)) {
1102             Atom a, b;
1103
1104             /* XXX: it would be nice to compress ALL changes to a property,
1105                not just changes in a row without other props between. */
1106
1107             a = ce.xproperty.atom;
1108             b = e->xproperty.atom;
1109
1110             if (a == b)
1111                 continue;
1112             if ((a == prop_atoms.net_wm_name ||
1113                  a == prop_atoms.wm_name ||
1114                  a == prop_atoms.net_wm_icon_name ||
1115                  a == prop_atoms.wm_icon_name)
1116                 &&
1117                 (b == prop_atoms.net_wm_name ||
1118                  b == prop_atoms.wm_name ||
1119                  b == prop_atoms.net_wm_icon_name ||
1120                  b == prop_atoms.wm_icon_name)) {
1121                 continue;
1122             }
1123             if (a == prop_atoms.net_wm_icon &&
1124                 b == prop_atoms.net_wm_icon)
1125                 continue;
1126
1127             XPutBackEvent(ob_display, &ce);
1128             break;
1129         }
1130
1131         msgtype = e->xproperty.atom;
1132         if (msgtype == XA_WM_NORMAL_HINTS) {
1133             client_update_normal_hints(client);
1134             /* normal hints can make a window non-resizable */
1135             client_setup_decor_and_functions(client);
1136         } else if (msgtype == XA_WM_HINTS) {
1137             client_update_wmhints(client);
1138         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1139             client_update_transient_for(client);
1140             client_get_type(client);
1141             /* type may have changed, so update the layer */
1142             client_calc_layer(client);
1143             client_setup_decor_and_functions(client);
1144         } else if (msgtype == prop_atoms.net_wm_name ||
1145                    msgtype == prop_atoms.wm_name ||
1146                    msgtype == prop_atoms.net_wm_icon_name ||
1147                    msgtype == prop_atoms.wm_icon_name) {
1148             client_update_title(client);
1149         } else if (msgtype == prop_atoms.wm_class) {
1150             client_update_class(client);
1151         } else if (msgtype == prop_atoms.wm_protocols) {
1152             client_update_protocols(client);
1153             client_setup_decor_and_functions(client);
1154         }
1155         else if (msgtype == prop_atoms.net_wm_strut) {
1156             client_update_strut(client);
1157         }
1158         else if (msgtype == prop_atoms.net_wm_icon) {
1159             client_update_icons(client);
1160         }
1161         else if (msgtype == prop_atoms.net_wm_user_time) {
1162             client_update_user_time(client);
1163         }
1164         else if (msgtype == prop_atoms.sm_client_id) {
1165             client_update_sm_client_id(client);
1166         }
1167     default:
1168         ;
1169 #ifdef SHAPE
1170         if (extensions_shape && e->type == extensions_shape_event_basep) {
1171             client->shaped = ((XShapeEvent*)e)->shaped;
1172             frame_adjust_shape(client->frame);
1173         }
1174 #endif
1175     }
1176 }
1177
1178 static void event_handle_dock(ObDock *s, XEvent *e)
1179 {
1180     switch (e->type) {
1181     case ButtonPress:
1182         if (e->xbutton.button == 1)
1183             stacking_raise(DOCK_AS_WINDOW(s));
1184         else if (e->xbutton.button == 2)
1185             stacking_lower(DOCK_AS_WINDOW(s));
1186         break;
1187     case EnterNotify:
1188         dock_hide(FALSE);
1189         break;
1190     case LeaveNotify:
1191         dock_hide(TRUE);
1192         break;
1193     }
1194 }
1195
1196 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1197 {
1198     switch (e->type) {
1199     case MotionNotify:
1200         dock_app_drag(app, &e->xmotion);
1201         break;
1202     case UnmapNotify:
1203         if (app->ignore_unmaps) {
1204             app->ignore_unmaps--;
1205             break;
1206         }
1207         dock_remove(app, TRUE);
1208         break;
1209     case DestroyNotify:
1210         dock_remove(app, FALSE);
1211         break;
1212     case ReparentNotify:
1213         dock_remove(app, FALSE);
1214         break;
1215     case ConfigureNotify:
1216         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1217         break;
1218     }
1219 }
1220
1221 ObMenuFrame* find_active_menu()
1222 {
1223     GList *it;
1224     ObMenuFrame *ret = NULL;
1225
1226     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1227         ret = it->data;
1228         if (ret->selected)
1229             break;
1230         ret = NULL;
1231     }
1232     return ret;
1233 }
1234
1235 ObMenuFrame* find_active_or_last_menu()
1236 {
1237     ObMenuFrame *ret = NULL;
1238
1239     ret = find_active_menu();
1240     if (!ret && menu_frame_visible)
1241         ret = menu_frame_visible->data;
1242     return ret;
1243 }
1244
1245 static void event_handle_menu(XEvent *ev)
1246 {
1247     ObMenuFrame *f;
1248     ObMenuEntryFrame *e;
1249
1250     switch (ev->type) {
1251     case ButtonRelease:
1252         if (menu_can_hide) {
1253             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1254                                             ev->xbutton.y_root)))
1255                 menu_entry_frame_execute(e, ev->xbutton.state,
1256                                          ev->xbutton.time);
1257             else
1258                 menu_frame_hide_all();
1259         }
1260         break;
1261     case EnterNotify:
1262         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1263             if (e->ignore_enters)
1264                 --e->ignore_enters;
1265             else
1266                 menu_frame_select(e->frame, e);
1267         }
1268         break;
1269     case LeaveNotify:
1270         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1271             (f = find_active_menu()) && f->selected == e &&
1272             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1273         {
1274             menu_frame_select(e->frame, NULL);
1275         }
1276     case MotionNotify:   
1277         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1278                                         ev->xmotion.y_root)))
1279             menu_frame_select(e->frame, e);   
1280         break;
1281     case KeyPress:
1282         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1283             menu_frame_hide_all();
1284         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1285             ObMenuFrame *f;
1286             if ((f = find_active_menu()))
1287                 menu_entry_frame_execute(f->selected, ev->xkey.state,
1288                                          ev->xkey.time);
1289         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1290             ObMenuFrame *f;
1291             if ((f = find_active_or_last_menu()) && f->parent)
1292                 menu_frame_select(f, NULL);
1293         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1294             ObMenuFrame *f;
1295             if ((f = find_active_or_last_menu()) && f->child)
1296                 menu_frame_select_next(f->child);
1297         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1298             ObMenuFrame *f;
1299             if ((f = find_active_or_last_menu()))
1300                 menu_frame_select_previous(f);
1301         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1302             ObMenuFrame *f;
1303             if ((f = find_active_or_last_menu()))
1304                 menu_frame_select_next(f);
1305         }
1306         break;
1307     }
1308 }
1309
1310 static gboolean menu_hide_delay_func(gpointer data)
1311 {
1312     menu_can_hide = TRUE;
1313     return FALSE; /* no repeat */
1314 }
1315
1316 static void focus_delay_dest(gpointer data)
1317 {
1318     g_free(data);
1319 }
1320
1321 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1322 {
1323     const ObFocusDelayData *f1 = d1;
1324     return f1->client == d2;
1325 }
1326
1327 static gboolean focus_delay_func(gpointer data)
1328 {
1329     ObFocusDelayData *d = data;
1330     Time old = event_curtime;
1331
1332     event_curtime = d->time;
1333     if (focus_client != d->client) {
1334         if (client_focus(d->client) && config_focus_raise)
1335             client_raise(d->client);
1336     }
1337     event_curtime = old;
1338     return FALSE; /* no repeat */
1339 }
1340
1341 static void focus_delay_client_dest(ObClient *client, gpointer data)
1342 {
1343     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1344                                      client, FALSE);
1345 }
1346
1347 void event_halt_focus_delay()
1348 {
1349     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1350 }
1351
1352 void event_ignore_queued_enters()
1353 {
1354     GSList *saved = NULL, *it;
1355     XEvent *e;
1356                 
1357     XSync(ob_display, FALSE);
1358
1359     /* count the events */
1360     while (TRUE) {
1361         e = g_new(XEvent, 1);
1362         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1363             ObWindow *win;
1364             
1365             win = g_hash_table_lookup(window_map, &e->xany.window);
1366             if (win && WINDOW_IS_CLIENT(win))
1367                 ++ignore_enter_focus;
1368             
1369             saved = g_slist_append(saved, e);
1370         } else {
1371             g_free(e);
1372             break;
1373         }
1374     }
1375     /* put the events back */
1376     for (it = saved; it; it = g_slist_next(it)) {
1377         XPutBackEvent(ob_display, it->data);
1378         g_free(it->data);
1379     }
1380     g_slist_free(saved);
1381 }
1382
1383 gboolean event_time_after(Time t1, Time t2)
1384 {
1385     g_assert(t1 != CurrentTime);
1386     g_assert(t2 != CurrentTime);
1387
1388     /*
1389       Timestamp values wrap around (after about 49.7 days). The server, given
1390       its current time is represented by timestamp T, always interprets
1391       timestamps from clients by treating half of the timestamp space as being
1392       later in time than T.
1393       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1394     */
1395
1396     /* TIME_HALF is half of the number space of a Time type variable */
1397 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1398
1399     if (t2 >= TIME_HALF)
1400         /* t2 is in the second half so t1 might wrap around and be smaller than
1401            t2 */
1402         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1403     else
1404         /* t2 is in the first half so t1 has to come after it */
1405         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1406 }