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