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