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