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