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