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