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