FocusOut events come after UnmapNotify, so we don't need to worry about focusing...
[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        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         /* This means focus reverted off of a client */
352         if (detail == NotifyInferior)
353             return TRUE;
354
355         /* Otherwise.. */
356         return FALSE;
357     } else {
358         g_assert(e->type == FocusOut);
359
360
361         /* These are ones we never want.. */
362
363         /* This means focus was taken by a keyboard/mouse grab. */
364         if (mode == NotifyGrab)
365             return FALSE;
366
367         /* Focus left the root window revertedto state */
368         if (win == RootWindow(ob_display, ob_screen))
369             return FALSE;
370
371         /* These are the ones we want.. */
372
373         /* This means focus moved from a client to the root window */
374         if (detail == NotifyVirtual)
375             return TRUE;
376         /* This means focus moved from one client to another */
377         if (detail == NotifyNonlinearVirtual)
378             return TRUE;
379
380         /* Otherwise.. */
381         return FALSE;
382     }
383 }
384
385 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
386 {
387     return e->type == FocusIn && wanted_focusevent(e);
388 }
389
390 static gboolean event_ignore(XEvent *e, ObClient *client)
391 {
392     switch(e->type) {
393     case FocusIn:
394     case FocusOut:
395         if (!wanted_focusevent(e))
396             return TRUE;
397         break;
398     }
399     return FALSE;
400 }
401
402 static void event_process(const XEvent *ec, gpointer data)
403 {
404     Window window;
405     ObGroup *group = NULL;
406     ObClient *client = NULL;
407     ObDock *dock = NULL;
408     ObDockApp *dockapp = NULL;
409     ObWindow *obwin = NULL;
410     XEvent ee, *e;
411     ObEventData *ed = data;
412
413     /* make a copy we can mangle */
414     ee = *ec;
415     e = &ee;
416
417     window = event_get_window(e);
418     if (!(e->type == PropertyNotify &&
419           (group = g_hash_table_lookup(group_map, &window))))
420         if ((obwin = g_hash_table_lookup(window_map, &window))) {
421             switch (obwin->type) {
422             case Window_Dock:
423                 dock = WINDOW_AS_DOCK(obwin);
424                 break;
425             case Window_DockApp:
426                 dockapp = WINDOW_AS_DOCKAPP(obwin);
427                 break;
428             case Window_Client:
429                 client = WINDOW_AS_CLIENT(obwin);
430                 break;
431             case Window_Menu:
432             case Window_Internal:
433                 /* not to be used for events */
434                 g_assert_not_reached();
435                 break;
436             }
437         }
438
439     event_set_curtime(e);
440     event_hack_mods(e);
441     if (event_ignore(e, client)) {
442         if (ed)
443             ed->ignored = TRUE;
444         return;
445     } else if (ed)
446             ed->ignored = FALSE;
447
448     /* deal with it in the kernel */
449
450     if (menu_frame_visible &&
451         (e->type == EnterNotify || e->type == LeaveNotify))
452     {
453         /* crossing events for menu */
454         event_handle_menu(e);
455     } else if (e->type == FocusIn) {
456         if (e->xfocus.detail == NotifyPointerRoot ||
457                    e->xfocus.detail == NotifyDetailNone) {
458             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to root\n");
459             /* Focus has been reverted to the root window or nothing
460                FocusOut events come after UnmapNotify, so we don't need to
461                worry about focusing an invalid window
462              */
463             focus_fallback(TRUE);
464         } else if (e->xfocus.detail == NotifyInferior) {
465             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to parent\n");
466             /* Focus has been reverted to parent, which is our frame window,
467                or the root window
468                FocusOut events come after UnmapNotify, so we don't need to
469                worry about focusing an invalid window
470             */
471             focus_fallback(TRUE);
472         } else if (client && client != focus_client) {
473             frame_adjust_focus(client->frame, TRUE);
474             client_calc_layer(client);
475             focus_set_client(client);
476         }
477     } else if (e->type == FocusOut) {
478         gboolean nomove = FALSE;
479         XEvent ce;
480
481         ob_debug_type(OB_DEBUG_FOCUS, "FocusOut Event\n");
482
483         /* Look for the followup FocusIn */
484         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
485             /* There is no FocusIn, this means focus went to a window that
486                is not being managed, or a window on another screen. */
487             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a black hole !\n");
488             /* nothing is focused */
489             focus_set_client(NULL);
490         } else if (ce.xany.window == e->xany.window) {
491             /* If focus didn't actually move anywhere, there is nothing to do*/
492             nomove = TRUE;
493         } else {
494             /* Focus did move, so process the FocusIn event */
495             ObEventData ed = { .ignored = FALSE };
496             event_process(&ce, &ed);
497             if (ed.ignored) {
498                 /* The FocusIn was ignored, this means it was on a window
499                    that isn't a client. */
500                 ob_debug_type(OB_DEBUG_FOCUS,
501                               "Focus went to an unmanaged window 0x%x !\n",
502                               ce.xfocus.window);
503                 focus_fallback(TRUE);
504             }
505         }
506
507         if (client && !nomove) {
508             frame_adjust_focus(client->frame, FALSE);
509             client_calc_layer(client);
510         }
511     } else if (group)
512         event_handle_group(group, e);
513     else if (client)
514         event_handle_client(client, e);
515     else if (dockapp)
516         event_handle_dockapp(dockapp, e);
517     else if (dock)
518         event_handle_dock(dock, e);
519     else if (window == RootWindow(ob_display, ob_screen))
520         event_handle_root(e);
521     else if (e->type == MapRequest)
522         client_manage(window);
523     else if (e->type == ConfigureRequest) {
524         /* unhandled configure requests must be used to configure the
525            window directly */
526         XWindowChanges xwc;
527
528         xwc.x = e->xconfigurerequest.x;
529         xwc.y = e->xconfigurerequest.y;
530         xwc.width = e->xconfigurerequest.width;
531         xwc.height = e->xconfigurerequest.height;
532         xwc.border_width = e->xconfigurerequest.border_width;
533         xwc.sibling = e->xconfigurerequest.above;
534         xwc.stack_mode = e->xconfigurerequest.detail;
535        
536         /* we are not to be held responsible if someone sends us an
537            invalid request! */
538         xerror_set_ignore(TRUE);
539         XConfigureWindow(ob_display, window,
540                          e->xconfigurerequest.value_mask, &xwc);
541         xerror_set_ignore(FALSE);
542     }
543
544     /* user input (action-bound) events */
545     if (e->type == ButtonPress || e->type == ButtonRelease ||
546         e->type == MotionNotify || e->type == KeyPress ||
547         e->type == KeyRelease)
548     {
549         if (menu_frame_visible)
550             event_handle_menu(e);
551         else {
552             if (!keyboard_process_interactive_grab(e, &client)) {
553                 if (moveresize_in_progress) {
554                     moveresize_event(e);
555
556                     /* make further actions work on the client being
557                        moved/resized */
558                     client = moveresize_client;
559                 }
560
561                 menu_can_hide = FALSE;
562                 ob_main_loop_timeout_add(ob_main_loop,
563                                          config_menu_hide_delay * 1000,
564                                          menu_hide_delay_func,
565                                          NULL, g_direct_equal, NULL);
566
567                 if (e->type == ButtonPress || e->type == ButtonRelease ||
568                     e->type == MotionNotify) {
569                     mouse_event(client, e);
570                 } else if (e->type == KeyPress) {
571                     keyboard_event((focus_cycle_target ? focus_cycle_target :
572                                     client), e);
573                 }
574             }
575         }
576     }
577     /* if something happens and it's not from an XEvent, then we don't know
578        the time */
579     event_curtime = CurrentTime;
580 }
581
582 static void event_handle_root(XEvent *e)
583 {
584     Atom msgtype;
585      
586     switch(e->type) {
587     case SelectionClear:
588         ob_debug("Another WM has requested to replace us. Exiting.\n");
589         ob_exit_replace();
590         break;
591
592     case ClientMessage:
593         if (e->xclient.format != 32) break;
594
595         msgtype = e->xclient.message_type;
596         if (msgtype == prop_atoms.net_current_desktop) {
597             guint d = e->xclient.data.l[0];
598             if (d < screen_num_desktops) {
599                 event_curtime = e->xclient.data.l[1];
600                 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime);
601                 screen_set_desktop(d);
602             }
603         } else if (msgtype == prop_atoms.net_number_of_desktops) {
604             guint d = e->xclient.data.l[0];
605             if (d > 0)
606                 screen_set_num_desktops(d);
607         } else if (msgtype == prop_atoms.net_showing_desktop) {
608             screen_show_desktop(e->xclient.data.l[0] != 0);
609         } else if (msgtype == prop_atoms.ob_control) {
610             if (e->xclient.data.l[0] == 1)
611                 ob_reconfigure();
612             else if (e->xclient.data.l[0] == 2)
613                 ob_restart();
614         }
615         break;
616     case PropertyNotify:
617         if (e->xproperty.atom == prop_atoms.net_desktop_names)
618             screen_update_desktop_names();
619         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
620             screen_update_layout();
621         break;
622     case ConfigureNotify:
623 #ifdef XRANDR
624         XRRUpdateConfiguration(e);
625 #endif
626         screen_resize();
627         break;
628     default:
629         ;
630     }
631 }
632
633 static void event_handle_group(ObGroup *group, XEvent *e)
634 {
635     GSList *it;
636
637     g_assert(e->type == PropertyNotify);
638
639     for (it = group->members; it; it = g_slist_next(it))
640         event_handle_client(it->data, e);
641 }
642
643 void event_enter_client(ObClient *client)
644 {
645     g_assert(config_focus_follow);
646
647     if (client_normal(client) && client_can_focus(client)) {
648         if (config_focus_delay) {
649             ObFocusDelayData *data;
650
651             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
652
653             data = g_new(ObFocusDelayData, 1);
654             data->client = client;
655             data->time = event_curtime;
656
657             ob_main_loop_timeout_add(ob_main_loop,
658                                      config_focus_delay,
659                                      focus_delay_func,
660                                      data, focus_delay_cmp, focus_delay_dest);
661         } else {
662             ObFocusDelayData data;
663             data.client = client;
664             data.time = event_curtime;
665             focus_delay_func(&data);
666         }
667     }
668 }
669
670 static void event_handle_client(ObClient *client, XEvent *e)
671 {
672     XEvent ce;
673     Atom msgtype;
674     gint i=0;
675     ObFrameContext con;
676      
677     switch (e->type) {
678     case VisibilityNotify:
679         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
680         break;
681     case ButtonPress:
682     case ButtonRelease:
683         /* Wheel buttons don't draw because they are an instant click, so it
684            is a waste of resources to go drawing it. */
685         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
686             con = frame_context(client, e->xbutton.window);
687             con = mouse_button_frame_context(con, e->xbutton.button);
688             switch (con) {
689             case OB_FRAME_CONTEXT_MAXIMIZE:
690                 client->frame->max_press = (e->type == ButtonPress);
691                 framerender_frame(client->frame);
692                 break;
693             case OB_FRAME_CONTEXT_CLOSE:
694                 client->frame->close_press = (e->type == ButtonPress);
695                 framerender_frame(client->frame);
696                 break;
697             case OB_FRAME_CONTEXT_ICONIFY:
698                 client->frame->iconify_press = (e->type == ButtonPress);
699                 framerender_frame(client->frame);
700                 break;
701             case OB_FRAME_CONTEXT_ALLDESKTOPS:
702                 client->frame->desk_press = (e->type == ButtonPress);
703                 framerender_frame(client->frame);
704                 break; 
705             case OB_FRAME_CONTEXT_SHADE:
706                 client->frame->shade_press = (e->type == ButtonPress);
707                 framerender_frame(client->frame);
708                 break;
709             default:
710                 /* nothing changes with clicks for any other contexts */
711                 break;
712             }
713         }
714         break;
715     case LeaveNotify:
716         con = frame_context(client, e->xcrossing.window);
717         switch (con) {
718         case OB_FRAME_CONTEXT_MAXIMIZE:
719             client->frame->max_hover = FALSE;
720             frame_adjust_state(client->frame);
721             break;
722         case OB_FRAME_CONTEXT_ALLDESKTOPS:
723             client->frame->desk_hover = FALSE;
724             frame_adjust_state(client->frame);
725             break;
726         case OB_FRAME_CONTEXT_SHADE:
727             client->frame->shade_hover = FALSE;
728             frame_adjust_state(client->frame);
729             break;
730         case OB_FRAME_CONTEXT_ICONIFY:
731             client->frame->iconify_hover = FALSE;
732             frame_adjust_state(client->frame);
733             break;
734         case OB_FRAME_CONTEXT_CLOSE:
735             client->frame->close_hover = FALSE;
736             frame_adjust_state(client->frame);
737             break;
738         case OB_FRAME_CONTEXT_FRAME:
739             if (keyboard_interactively_grabbed())
740                 break;
741             if (config_focus_follow && config_focus_delay)
742                 ob_main_loop_timeout_remove_data(ob_main_loop,
743                                                  focus_delay_func,
744                                                  client, FALSE);
745             break;
746         default:
747             break;
748         }
749         break;
750     case EnterNotify:
751     {
752         gboolean nofocus = FALSE;
753
754         if (ignore_enter_focus) {
755             ignore_enter_focus--;
756             nofocus = TRUE;
757         }
758
759         con = frame_context(client, e->xcrossing.window);
760         switch (con) {
761         case OB_FRAME_CONTEXT_MAXIMIZE:
762             client->frame->max_hover = TRUE;
763             frame_adjust_state(client->frame);
764             break;
765         case OB_FRAME_CONTEXT_ALLDESKTOPS:
766             client->frame->desk_hover = TRUE;
767             frame_adjust_state(client->frame);
768             break;
769         case OB_FRAME_CONTEXT_SHADE:
770             client->frame->shade_hover = TRUE;
771             frame_adjust_state(client->frame);
772             break;
773         case OB_FRAME_CONTEXT_ICONIFY:
774             client->frame->iconify_hover = TRUE;
775             frame_adjust_state(client->frame);
776             break;
777         case OB_FRAME_CONTEXT_CLOSE:
778             client->frame->close_hover = TRUE;
779             frame_adjust_state(client->frame);
780             break;
781         case OB_FRAME_CONTEXT_FRAME:
782             if (keyboard_interactively_grabbed())
783                 break;
784             if (e->xcrossing.mode == NotifyGrab ||
785                 e->xcrossing.mode == NotifyUngrab)
786             {
787                 ob_debug_type(OB_DEBUG_FOCUS,
788                               "%sNotify mode %d detail %d on %lx IGNORED\n",
789                               (e->type == EnterNotify ? "Enter" : "Leave"),
790                               e->xcrossing.mode,
791                               e->xcrossing.detail, client?client->window:0);
792             } else {
793                 ob_debug_type(OB_DEBUG_FOCUS,
794                               "%sNotify mode %d detail %d on %lx, "
795                               "focusing window: %d\n",
796                               (e->type == EnterNotify ? "Enter" : "Leave"),
797                               e->xcrossing.mode,
798                               e->xcrossing.detail, (client?client->window:0),
799                               !nofocus);
800                 if (!nofocus && config_focus_follow)
801                     event_enter_client(client);
802             }
803             break;
804         default:
805             break;
806         }
807         break;
808     }
809     case ConfigureRequest:
810         /* compress these */
811         while (XCheckTypedWindowEvent(ob_display, client->window,
812                                       ConfigureRequest, &ce)) {
813             ++i;
814             /* XXX if this causes bad things.. we can compress config req's
815                with the same mask. */
816             e->xconfigurerequest.value_mask |=
817                 ce.xconfigurerequest.value_mask;
818             if (ce.xconfigurerequest.value_mask & CWX)
819                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
820             if (ce.xconfigurerequest.value_mask & CWY)
821                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
822             if (ce.xconfigurerequest.value_mask & CWWidth)
823                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
824             if (ce.xconfigurerequest.value_mask & CWHeight)
825                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
826             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
827                 e->xconfigurerequest.border_width =
828                     ce.xconfigurerequest.border_width;
829             if (ce.xconfigurerequest.value_mask & CWStackMode)
830                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
831         }
832
833         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
834         if (client->iconic || client->shaded) return;
835
836         /* resize, then move, as specified in the EWMH section 7.7 */
837         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
838                                                CWX | CWY |
839                                                CWBorderWidth)) {
840             gint x, y, w, h;
841             ObCorner corner;
842
843             if (e->xconfigurerequest.value_mask & CWBorderWidth)
844                 client->border_width = e->xconfigurerequest.border_width;
845
846             x = (e->xconfigurerequest.value_mask & CWX) ?
847                 e->xconfigurerequest.x : client->area.x;
848             y = (e->xconfigurerequest.value_mask & CWY) ?
849                 e->xconfigurerequest.y : client->area.y;
850             w = (e->xconfigurerequest.value_mask & CWWidth) ?
851                 e->xconfigurerequest.width : client->area.width;
852             h = (e->xconfigurerequest.value_mask & CWHeight) ?
853                 e->xconfigurerequest.height : client->area.height;
854
855             {
856                 gint newx = x;
857                 gint newy = y;
858                 gint fw = w +
859                      client->frame->size.left + client->frame->size.right;
860                 gint fh = h +
861                      client->frame->size.top + client->frame->size.bottom;
862                 /* make this rude for size-only changes but not for position
863                    changes.. */
864                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
865                                    (e->xconfigurerequest.value_mask & CWY));
866
867                 client_find_onscreen(client, &newx, &newy, fw, fh,
868                                      !moving);
869                 if (e->xconfigurerequest.value_mask & CWX)
870                     x = newx;
871                 if (e->xconfigurerequest.value_mask & CWY)
872                     y = newy;
873             }
874
875             switch (client->gravity) {
876             case NorthEastGravity:
877             case EastGravity:
878                 corner = OB_CORNER_TOPRIGHT;
879                 break;
880             case SouthWestGravity:
881             case SouthGravity:
882                 corner = OB_CORNER_BOTTOMLEFT;
883                 break;
884             case SouthEastGravity:
885                 corner = OB_CORNER_BOTTOMRIGHT;
886                 break;
887             default:     /* NorthWest, Static, etc */
888                 corner = OB_CORNER_TOPLEFT;
889             }
890
891             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
892                                   TRUE);
893         }
894
895         if (e->xconfigurerequest.value_mask & CWStackMode) {
896             switch (e->xconfigurerequest.detail) {
897             case Below:
898             case BottomIf:
899                 /* Apps are so rude. And this is totally disconnected from
900                    activation/focus. Bleh. */
901                 /*client_lower(client);*/
902                 break;
903
904             case Above:
905             case TopIf:
906             default:
907                 /* Apps are so rude. And this is totally disconnected from
908                    activation/focus. Bleh. */
909                 /*client_raise(client);*/
910                 break;
911             }
912         }
913         break;
914     case UnmapNotify:
915         if (client->ignore_unmaps) {
916             client->ignore_unmaps--;
917             break;
918         }
919         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
920                  "ignores left %d\n",
921                  client->window, e->xunmap.event, e->xunmap.from_configure,
922                  client->ignore_unmaps);
923         client_unmanage(client);
924         break;
925     case DestroyNotify:
926         ob_debug("DestroyNotify for window 0x%x\n", client->window);
927         client_unmanage(client);
928         break;
929     case ReparentNotify:
930         /* this is when the client is first taken captive in the frame */
931         if (e->xreparent.parent == client->frame->plate) break;
932
933         /*
934           This event is quite rare and is usually handled in unmapHandler.
935           However, if the window is unmapped when the reparent event occurs,
936           the window manager never sees it because an unmap event is not sent
937           to an already unmapped window.
938         */
939
940         /* we don't want the reparent event, put it back on the stack for the
941            X server to deal with after we unmanage the window */
942         XPutBackEvent(ob_display, e);
943      
944         ob_debug("ReparentNotify for window 0x%x\n", client->window);
945         client_unmanage(client);
946         break;
947     case MapRequest:
948         ob_debug("MapRequest for 0x%lx\n", client->window);
949         if (!client->iconic) break; /* this normally doesn't happen, but if it
950                                        does, we don't want it!
951                                        it can happen now when the window is on
952                                        another desktop, but we still don't
953                                        want it! */
954         client_activate(client, FALSE, TRUE);
955         break;
956     case ClientMessage:
957         /* validate cuz we query stuff off the client here */
958         if (!client_validate(client)) break;
959
960         if (e->xclient.format != 32) return;
961
962         msgtype = e->xclient.message_type;
963         if (msgtype == prop_atoms.wm_change_state) {
964             /* compress changes into a single change */
965             while (XCheckTypedWindowEvent(ob_display, client->window,
966                                           e->type, &ce)) {
967                 /* XXX: it would be nice to compress ALL messages of a
968                    type, not just messages in a row without other
969                    message types between. */
970                 if (ce.xclient.message_type != msgtype) {
971                     XPutBackEvent(ob_display, &ce);
972                     break;
973                 }
974                 e->xclient = ce.xclient;
975             }
976             client_set_wm_state(client, e->xclient.data.l[0]);
977         } else if (msgtype == prop_atoms.net_wm_desktop) {
978             /* compress changes into a single change */
979             while (XCheckTypedWindowEvent(ob_display, client->window,
980                                           e->type, &ce)) {
981                 /* XXX: it would be nice to compress ALL messages of a
982                    type, not just messages in a row without other
983                    message types between. */
984                 if (ce.xclient.message_type != msgtype) {
985                     XPutBackEvent(ob_display, &ce);
986                     break;
987                 }
988                 e->xclient = ce.xclient;
989             }
990             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
991                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
992                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
993                                    FALSE);
994         } else if (msgtype == prop_atoms.net_wm_state) {
995             /* can't compress these */
996             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
997                      (e->xclient.data.l[0] == 0 ? "Remove" :
998                       e->xclient.data.l[0] == 1 ? "Add" :
999                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1000                      e->xclient.data.l[1], e->xclient.data.l[2],
1001                      client->window);
1002             client_set_state(client, e->xclient.data.l[0],
1003                              e->xclient.data.l[1], e->xclient.data.l[2]);
1004         } else if (msgtype == prop_atoms.net_close_window) {
1005             ob_debug("net_close_window for 0x%lx\n", client->window);
1006             client_close(client);
1007         } else if (msgtype == prop_atoms.net_active_window) {
1008             ob_debug("net_active_window for 0x%lx source=%s\n",
1009                      client->window,
1010                      (e->xclient.data.l[0] == 0 ? "unknown" :
1011                       (e->xclient.data.l[0] == 1 ? "application" :
1012                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1013             /* XXX make use of data.l[2] ! */
1014             event_curtime = e->xclient.data.l[1];
1015             client_activate(client, FALSE,
1016                             (e->xclient.data.l[0] == 0 ||
1017                              e->xclient.data.l[0] == 2));
1018         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1019             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1020                      client->window, e->xclient.data.l[2]);
1021             if ((Atom)e->xclient.data.l[2] ==
1022                 prop_atoms.net_wm_moveresize_size_topleft ||
1023                 (Atom)e->xclient.data.l[2] ==
1024                 prop_atoms.net_wm_moveresize_size_top ||
1025                 (Atom)e->xclient.data.l[2] ==
1026                 prop_atoms.net_wm_moveresize_size_topright ||
1027                 (Atom)e->xclient.data.l[2] ==
1028                 prop_atoms.net_wm_moveresize_size_right ||
1029                 (Atom)e->xclient.data.l[2] ==
1030                 prop_atoms.net_wm_moveresize_size_right ||
1031                 (Atom)e->xclient.data.l[2] ==
1032                 prop_atoms.net_wm_moveresize_size_bottomright ||
1033                 (Atom)e->xclient.data.l[2] ==
1034                 prop_atoms.net_wm_moveresize_size_bottom ||
1035                 (Atom)e->xclient.data.l[2] ==
1036                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1037                 (Atom)e->xclient.data.l[2] ==
1038                 prop_atoms.net_wm_moveresize_size_left ||
1039                 (Atom)e->xclient.data.l[2] ==
1040                 prop_atoms.net_wm_moveresize_move ||
1041                 (Atom)e->xclient.data.l[2] ==
1042                 prop_atoms.net_wm_moveresize_size_keyboard ||
1043                 (Atom)e->xclient.data.l[2] ==
1044                 prop_atoms.net_wm_moveresize_move_keyboard) {
1045
1046                 moveresize_start(client, e->xclient.data.l[0],
1047                                  e->xclient.data.l[1], e->xclient.data.l[3],
1048                                  e->xclient.data.l[2]);
1049             }
1050             else if ((Atom)e->xclient.data.l[2] ==
1051                      prop_atoms.net_wm_moveresize_cancel)
1052                 moveresize_end(TRUE);
1053         } else if (msgtype == prop_atoms.net_moveresize_window) {
1054             gint oldg = client->gravity;
1055             gint tmpg, x, y, w, h;
1056
1057             if (e->xclient.data.l[0] & 0xff)
1058                 tmpg = e->xclient.data.l[0] & 0xff;
1059             else
1060                 tmpg = oldg;
1061
1062             if (e->xclient.data.l[0] & 1 << 8)
1063                 x = e->xclient.data.l[1];
1064             else
1065                 x = client->area.x;
1066             if (e->xclient.data.l[0] & 1 << 9)
1067                 y = e->xclient.data.l[2];
1068             else
1069                 y = client->area.y;
1070             if (e->xclient.data.l[0] & 1 << 10)
1071                 w = e->xclient.data.l[3];
1072             else
1073                 w = client->area.width;
1074             if (e->xclient.data.l[0] & 1 << 11)
1075                 h = e->xclient.data.l[4];
1076             else
1077                 h = client->area.height;
1078             client->gravity = tmpg;
1079
1080             {
1081                 gint newx = x;
1082                 gint newy = y;
1083                 gint fw = w +
1084                      client->frame->size.left + client->frame->size.right;
1085                 gint fh = h +
1086                      client->frame->size.top + client->frame->size.bottom;
1087                 client_find_onscreen(client, &newx, &newy, fw, fh,
1088                                      client_normal(client));
1089                 if (e->xclient.data.l[0] & 1 << 8)
1090                     x = newx;
1091                 if (e->xclient.data.l[0] & 1 << 9)
1092                     y = newy;
1093             }
1094
1095             client_configure(client, OB_CORNER_TOPLEFT,
1096                              x, y, w, h, FALSE, TRUE);
1097
1098             client->gravity = oldg;
1099         }
1100         break;
1101     case PropertyNotify:
1102         /* validate cuz we query stuff off the client here */
1103         if (!client_validate(client)) break;
1104   
1105         /* compress changes to a single property into a single change */
1106         while (XCheckTypedWindowEvent(ob_display, client->window,
1107                                       e->type, &ce)) {
1108             Atom a, b;
1109
1110             /* XXX: it would be nice to compress ALL changes to a property,
1111                not just changes in a row without other props between. */
1112
1113             a = ce.xproperty.atom;
1114             b = e->xproperty.atom;
1115
1116             if (a == b)
1117                 continue;
1118             if ((a == prop_atoms.net_wm_name ||
1119                  a == prop_atoms.wm_name ||
1120                  a == prop_atoms.net_wm_icon_name ||
1121                  a == prop_atoms.wm_icon_name)
1122                 &&
1123                 (b == prop_atoms.net_wm_name ||
1124                  b == prop_atoms.wm_name ||
1125                  b == prop_atoms.net_wm_icon_name ||
1126                  b == prop_atoms.wm_icon_name)) {
1127                 continue;
1128             }
1129             if (a == prop_atoms.net_wm_icon &&
1130                 b == prop_atoms.net_wm_icon)
1131                 continue;
1132
1133             XPutBackEvent(ob_display, &ce);
1134             break;
1135         }
1136
1137         msgtype = e->xproperty.atom;
1138         if (msgtype == XA_WM_NORMAL_HINTS) {
1139             client_update_normal_hints(client);
1140             /* normal hints can make a window non-resizable */
1141             client_setup_decor_and_functions(client);
1142         } else if (msgtype == XA_WM_HINTS) {
1143             client_update_wmhints(client);
1144         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1145             client_update_transient_for(client);
1146             client_get_type(client);
1147             /* type may have changed, so update the layer */
1148             client_calc_layer(client);
1149             client_setup_decor_and_functions(client);
1150         } else if (msgtype == prop_atoms.net_wm_name ||
1151                    msgtype == prop_atoms.wm_name ||
1152                    msgtype == prop_atoms.net_wm_icon_name ||
1153                    msgtype == prop_atoms.wm_icon_name) {
1154             client_update_title(client);
1155         } else if (msgtype == prop_atoms.wm_class) {
1156             client_update_class(client);
1157         } else if (msgtype == prop_atoms.wm_protocols) {
1158             client_update_protocols(client);
1159             client_setup_decor_and_functions(client);
1160         }
1161         else if (msgtype == prop_atoms.net_wm_strut) {
1162             client_update_strut(client);
1163         }
1164         else if (msgtype == prop_atoms.net_wm_icon) {
1165             client_update_icons(client);
1166         }
1167         else if (msgtype == prop_atoms.net_wm_user_time) {
1168             client_update_user_time(client);
1169         }
1170         else if (msgtype == prop_atoms.sm_client_id) {
1171             client_update_sm_client_id(client);
1172         }
1173     default:
1174         ;
1175 #ifdef SHAPE
1176         if (extensions_shape && e->type == extensions_shape_event_basep) {
1177             client->shaped = ((XShapeEvent*)e)->shaped;
1178             frame_adjust_shape(client->frame);
1179         }
1180 #endif
1181     }
1182 }
1183
1184 static void event_handle_dock(ObDock *s, XEvent *e)
1185 {
1186     switch (e->type) {
1187     case ButtonPress:
1188         if (e->xbutton.button == 1)
1189             stacking_raise(DOCK_AS_WINDOW(s));
1190         else if (e->xbutton.button == 2)
1191             stacking_lower(DOCK_AS_WINDOW(s));
1192         break;
1193     case EnterNotify:
1194         dock_hide(FALSE);
1195         break;
1196     case LeaveNotify:
1197         dock_hide(TRUE);
1198         break;
1199     }
1200 }
1201
1202 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1203 {
1204     switch (e->type) {
1205     case MotionNotify:
1206         dock_app_drag(app, &e->xmotion);
1207         break;
1208     case UnmapNotify:
1209         if (app->ignore_unmaps) {
1210             app->ignore_unmaps--;
1211             break;
1212         }
1213         dock_remove(app, TRUE);
1214         break;
1215     case DestroyNotify:
1216         dock_remove(app, FALSE);
1217         break;
1218     case ReparentNotify:
1219         dock_remove(app, FALSE);
1220         break;
1221     case ConfigureNotify:
1222         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1223         break;
1224     }
1225 }
1226
1227 ObMenuFrame* find_active_menu()
1228 {
1229     GList *it;
1230     ObMenuFrame *ret = NULL;
1231
1232     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1233         ret = it->data;
1234         if (ret->selected)
1235             break;
1236         ret = NULL;
1237     }
1238     return ret;
1239 }
1240
1241 ObMenuFrame* find_active_or_last_menu()
1242 {
1243     ObMenuFrame *ret = NULL;
1244
1245     ret = find_active_menu();
1246     if (!ret && menu_frame_visible)
1247         ret = menu_frame_visible->data;
1248     return ret;
1249 }
1250
1251 static void event_handle_menu(XEvent *ev)
1252 {
1253     ObMenuFrame *f;
1254     ObMenuEntryFrame *e;
1255
1256     switch (ev->type) {
1257     case ButtonRelease:
1258         if (menu_can_hide) {
1259             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1260                                             ev->xbutton.y_root)))
1261                 menu_entry_frame_execute(e, ev->xbutton.state,
1262                                          ev->xbutton.time);
1263             else
1264                 menu_frame_hide_all();
1265         }
1266         break;
1267     case EnterNotify:
1268         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1269             if (e->ignore_enters)
1270                 --e->ignore_enters;
1271             else
1272                 menu_frame_select(e->frame, e);
1273         }
1274         break;
1275     case LeaveNotify:
1276         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1277             (f = find_active_menu()) && f->selected == e &&
1278             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1279         {
1280             menu_frame_select(e->frame, NULL);
1281         }
1282     case MotionNotify:   
1283         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1284                                         ev->xmotion.y_root)))
1285             menu_frame_select(e->frame, e);   
1286         break;
1287     case KeyPress:
1288         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1289             menu_frame_hide_all();
1290         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1291             ObMenuFrame *f;
1292             if ((f = find_active_menu()))
1293                 menu_entry_frame_execute(f->selected, ev->xkey.state,
1294                                          ev->xkey.time);
1295         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1296             ObMenuFrame *f;
1297             if ((f = find_active_or_last_menu()) && f->parent)
1298                 menu_frame_select(f, NULL);
1299         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1300             ObMenuFrame *f;
1301             if ((f = find_active_or_last_menu()) && f->child)
1302                 menu_frame_select_next(f->child);
1303         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1304             ObMenuFrame *f;
1305             if ((f = find_active_or_last_menu()))
1306                 menu_frame_select_previous(f);
1307         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1308             ObMenuFrame *f;
1309             if ((f = find_active_or_last_menu()))
1310                 menu_frame_select_next(f);
1311         }
1312         break;
1313     }
1314 }
1315
1316 static gboolean menu_hide_delay_func(gpointer data)
1317 {
1318     menu_can_hide = TRUE;
1319     return FALSE; /* no repeat */
1320 }
1321
1322 static void focus_delay_dest(gpointer data)
1323 {
1324     g_free(data);
1325 }
1326
1327 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1328 {
1329     const ObFocusDelayData *f1 = d1;
1330     return f1->client == d2;
1331 }
1332
1333 static gboolean focus_delay_func(gpointer data)
1334 {
1335     ObFocusDelayData *d = data;
1336     Time old = event_curtime;
1337
1338     event_curtime = d->time;
1339     if (focus_client != d->client) {
1340         if (client_focus(d->client) && config_focus_raise)
1341             client_raise(d->client);
1342     }
1343     event_curtime = old;
1344     return FALSE; /* no repeat */
1345 }
1346
1347 static void focus_delay_client_dest(ObClient *client, gpointer data)
1348 {
1349     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1350                                      client, FALSE);
1351 }
1352
1353 void event_halt_focus_delay()
1354 {
1355     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1356 }
1357
1358 void event_ignore_queued_enters()
1359 {
1360     GSList *saved = NULL, *it;
1361     XEvent *e;
1362                 
1363     XSync(ob_display, FALSE);
1364
1365     /* count the events */
1366     while (TRUE) {
1367         e = g_new(XEvent, 1);
1368         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1369             ObWindow *win;
1370             
1371             win = g_hash_table_lookup(window_map, &e->xany.window);
1372             if (win && WINDOW_IS_CLIENT(win))
1373                 ++ignore_enter_focus;
1374             
1375             saved = g_slist_append(saved, e);
1376         } else {
1377             g_free(e);
1378             break;
1379         }
1380     }
1381     /* put the events back */
1382     for (it = saved; it; it = g_slist_next(it)) {
1383         XPutBackEvent(ob_display, it->data);
1384         g_free(it->data);
1385     }
1386     g_slist_free(saved);
1387 }
1388
1389 gboolean event_time_after(Time t1, Time t2)
1390 {
1391     g_assert(t1 != CurrentTime);
1392     g_assert(t2 != CurrentTime);
1393
1394     /*
1395       Timestamp values wrap around (after about 49.7 days). The server, given
1396       its current time is represented by timestamp T, always interprets
1397       timestamps from clients by treating half of the timestamp space as being
1398       later in time than T.
1399       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1400     */
1401
1402     /* TIME_HALF is half of the number space of a Time type variable */
1403 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1404
1405     if (t2 >= TIME_HALF)
1406         /* t2 is in the second half so t1 might wrap around and be smaller than
1407            t2 */
1408         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1409     else
1410         /* t2 is in the first half so t1 has to come after it */
1411         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1412 }