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