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