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