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