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