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