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