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