]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
Merge branch 'review' into wip/mikabox
[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 int 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
729             /* We ignored the release event so make sure we don't think
730                the button is still pressed */
731             else if (e->type == ButtonRelease)
732                 button = 0;
733         }
734     }
735     else if (e->type == KeyPress || e->type == KeyRelease ||
736              e->type == MotionNotify)
737     {
738         event_sourcetime = event_curtime;
739
740         used = event_handle_user_input(client, e);
741
742         if (prompt && !used)
743             used = event_handle_prompt(prompt, e);
744     }
745
746     /* show any debug prompts that are queued */
747     ob_debug_show_prompts();
748
749     /* if something happens and it's not from an XEvent, then we don't know
750        the time, so clear it here until the next event is handled */
751     event_curtime = event_sourcetime = CurrentTime;
752     event_curserial = 0;
753 }
754
755 static void event_handle_root(XEvent *e)
756 {
757     Atom msgtype;
758
759     switch(e->type) {
760     case SelectionClear:
761         ob_debug("Another WM has requested to replace us. Exiting.");
762         ob_exit_replace();
763         break;
764
765     case ClientMessage:
766         if (e->xclient.format != 32) break;
767
768         msgtype = e->xclient.message_type;
769         if (msgtype == OBT_PROP_ATOM(NET_CURRENT_DESKTOP)) {
770             guint d = e->xclient.data.l[0];
771             if (d < screen_num_desktops) {
772                 if (e->xclient.data.l[1] == 0)
773                     ob_debug_type(OB_DEBUG_APP_BUGS,
774                                   "_NET_CURRENT_DESKTOP message is missing "
775                                   "a timestamp");
776                 else
777                     event_sourcetime = e->xclient.data.l[1];
778                 screen_set_desktop(d, TRUE);
779             }
780         } else if (msgtype == OBT_PROP_ATOM(NET_NUMBER_OF_DESKTOPS)) {
781             guint d = e->xclient.data.l[0];
782             if (d > 0 && d <= 1000)
783                 screen_set_num_desktops(d);
784         } else if (msgtype == OBT_PROP_ATOM(NET_SHOWING_DESKTOP)) {
785             ObScreenShowDestopMode show_mode;
786             if (e->xclient.data.l[0] != 0)
787                 show_mode = SCREEN_SHOW_DESKTOP_UNTIL_WINDOW;
788             else
789                 show_mode = SCREEN_SHOW_DESKTOP_NO;
790             screen_show_desktop(show_mode, NULL);
791         } else if (msgtype == OBT_PROP_ATOM(OB_CONTROL)) {
792             ob_debug("OB_CONTROL: %d", e->xclient.data.l[0]);
793             if (e->xclient.data.l[0] == 1)
794                 ob_reconfigure();
795             else if (e->xclient.data.l[0] == 2)
796                 ob_restart();
797             else if (e->xclient.data.l[0] == 3)
798                 ob_exit(0);
799         } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
800             if ((Atom)e->xclient.data.l[0] == OBT_PROP_ATOM(NET_WM_PING))
801                 ping_got_pong(e->xclient.data.l[1]);
802         }
803         break;
804     case PropertyNotify:
805         if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_NAMES)) {
806             ob_debug("UPDATE DESKTOP NAMES");
807             screen_update_desktop_names();
808         }
809         else if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_LAYOUT))
810             screen_update_layout();
811         break;
812     case ConfigureNotify:
813 #ifdef XRANDR
814         XRRUpdateConfiguration(e);
815 #endif
816         screen_resize();
817         break;
818     default:
819         ;
820     }
821 }
822
823 void event_enter_client(ObClient *client)
824 {
825     g_assert(config_focus_follow);
826
827     if (is_enter_focus_event_ignored(event_curserial)) {
828         ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu "
829                       "on client 0x%x", event_curserial, client->window);
830         return;
831     }
832
833     ob_debug_type(OB_DEBUG_FOCUS, "using enter event with serial %lu "
834                   "on client 0x%x", event_curserial, client->window);
835
836     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 */)))) {
837         if (config_focus_delay) {
838             ObFocusDelayData *data;
839
840             if (focus_delay_timeout_id)
841                 g_source_remove(focus_delay_timeout_id);
842
843             data = g_slice_new(ObFocusDelayData);
844             data->client = client;
845             data->time = event_time();
846             data->serial = event_curserial;
847
848             focus_delay_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
849                                                         config_focus_delay,
850                                                         focus_delay_func,
851                                                         data,
852                                                         focus_delay_dest);
853             focus_delay_timeout_client = client;
854         } else {
855             ObFocusDelayData data;
856             data.client = client;
857             data.time = event_time();
858             data.serial = event_curserial;
859             focus_delay_func(&data);
860         }
861     }
862 }
863
864 void event_leave_client(ObClient *client)
865 {
866     g_assert(config_focus_follow);
867
868     if (is_enter_focus_event_ignored(event_curserial)) {
869         ob_debug_type(OB_DEBUG_FOCUS, "Ignoring leave event with serial %lu\n"
870                       "on client 0x%x", event_curserial, client->window);
871         return;
872     }
873
874     if (client == focus_client) {
875         if (config_focus_delay) {
876             ObFocusDelayData *data;
877
878             if (unfocus_delay_timeout_id)
879                 g_source_remove(unfocus_delay_timeout_id);
880
881             data = g_slice_new(ObFocusDelayData);
882             data->client = client;
883             data->time = event_time();
884             data->serial = event_curserial;
885
886             unfocus_delay_timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
887                                                           config_focus_delay,
888                                                           unfocus_delay_func,
889                                                           data,
890                                                           unfocus_delay_dest);
891             unfocus_delay_timeout_client = client;
892         } else {
893             ObFocusDelayData data;
894             data.client = client;
895             data.time = event_time();
896             data.serial = event_curserial;
897             unfocus_delay_func(&data);
898         }
899     }
900 }
901
902 static gboolean *context_to_button(ObFrame *f, ObFrameContext con, gboolean press)
903 {
904     if (press) {
905         switch (con) {
906         case OB_FRAME_CONTEXT_MAXIMIZE:
907             return &f->max_press;
908         case OB_FRAME_CONTEXT_CLOSE:
909             return &f->close_press;
910         case OB_FRAME_CONTEXT_ICONIFY:
911             return &f->iconify_press;
912         case OB_FRAME_CONTEXT_ALLDESKTOPS:
913             return &f->desk_press;
914         case OB_FRAME_CONTEXT_SHADE:
915             return &f->shade_press;
916         default:
917             return NULL;
918         }
919     } else {
920         switch (con) {
921         case OB_FRAME_CONTEXT_MAXIMIZE:
922             return &f->max_hover;
923         case OB_FRAME_CONTEXT_CLOSE:
924             return &f->close_hover;
925         case OB_FRAME_CONTEXT_ICONIFY:
926             return &f->iconify_hover;
927         case OB_FRAME_CONTEXT_ALLDESKTOPS:
928             return &f->desk_hover;
929         case OB_FRAME_CONTEXT_SHADE:
930             return &f->shade_hover;
931         default:
932             return NULL;
933         }
934     }
935 }
936
937 static gboolean more_client_message_event(Window window, Atom msgtype)
938 {
939     ObtXQueueWindowMessage wm;
940     wm.window = window;
941     wm.message = msgtype;
942     return xqueue_exists_local(xqueue_match_window_message, &wm);
943 }
944
945 struct ObSkipPropertyChange {
946     Window window;
947     Atom prop;
948 };
949
950 static gboolean skip_property_change(XEvent *e, gpointer data)
951 {
952     const struct ObSkipPropertyChange s = *(struct ObSkipPropertyChange*)data;
953
954     if (e->type == PropertyNotify && e->xproperty.window == s.window) {
955         const Atom a = e->xproperty.atom;
956         const Atom b = s.prop;
957
958         /* these are all updated together */
959         if ((a == OBT_PROP_ATOM(NET_WM_NAME) ||
960              a == OBT_PROP_ATOM(WM_NAME) ||
961              a == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
962              a == OBT_PROP_ATOM(WM_ICON_NAME))
963             &&
964             (b == OBT_PROP_ATOM(NET_WM_NAME) ||
965              b == OBT_PROP_ATOM(WM_NAME) ||
966              b == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
967              b == OBT_PROP_ATOM(WM_ICON_NAME)))
968         {
969             return TRUE;
970         }
971         else if (a == b && a == OBT_PROP_ATOM(NET_WM_ICON))
972             return TRUE;
973     }
974     return FALSE;
975 }
976
977 static void event_handle_client(ObClient *client, XEvent *e)
978 {
979     Atom msgtype;
980     ObFrameContext con;
981     gboolean *but;
982     static gint px = -1, py = -1;
983     static guint pb = 0;
984     static ObFrameContext pcon = OB_FRAME_CONTEXT_NONE;
985
986     switch (e->type) {
987     case ButtonPress:
988         /* save where the press occured for the first button pressed */
989         if (!pb) {
990             pb = e->xbutton.button;
991             px = e->xbutton.x;
992             py = e->xbutton.y;
993
994             pcon = frame_context(client, e->xbutton.window, px, py);
995             pcon = mouse_button_frame_context(pcon, e->xbutton.button,
996                                               e->xbutton.state);
997         }
998     case ButtonRelease:
999         /* Wheel buttons don't draw because they are an instant click, so it
1000            is a waste of resources to go drawing it.
1001            if the user is doing an interactive thing, or has a menu open then
1002            the mouse is grabbed (possibly) and if we get these events we don't
1003            want to deal with them
1004         */
1005         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
1006             !grab_on_keyboard())
1007         {
1008             /* use where the press occured */
1009             con = frame_context(client, e->xbutton.window, px, py);
1010             con = mouse_button_frame_context(con, e->xbutton.button,
1011                                              e->xbutton.state);
1012
1013             /* button presses on CLIENT_CONTEXTs are not accompanied by a
1014                release because they are Replayed to the client */
1015             if ((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) &&
1016                 e->xbutton.button == pb)
1017                 pb = 0, px = py = -1, pcon = OB_FRAME_CONTEXT_NONE;
1018
1019             but = context_to_button(client->frame, con, TRUE);
1020             if (but) {
1021                 *but = (e->type == ButtonPress);
1022                 frame_adjust_state(client->frame);
1023             }
1024         }
1025         break;
1026     case MotionNotify:
1027         /* when there is a grab on the pointer, we won't get enter/leave
1028            notifies, but we still get motion events */
1029         if (grab_on_pointer()) break;
1030
1031         con = frame_context(client, e->xmotion.window,
1032                             e->xmotion.x, e->xmotion.y);
1033         switch (con) {
1034         case OB_FRAME_CONTEXT_TITLEBAR:
1035         case OB_FRAME_CONTEXT_TLCORNER:
1036         case OB_FRAME_CONTEXT_TRCORNER:
1037             /* we've left the button area inside the titlebar */
1038             if (client->frame->max_hover || client->frame->desk_hover ||
1039                 client->frame->shade_hover || client->frame->iconify_hover ||
1040                 client->frame->close_hover)
1041             {
1042                 client->frame->max_hover =
1043                     client->frame->desk_hover =
1044                     client->frame->shade_hover =
1045                     client->frame->iconify_hover =
1046                     client->frame->close_hover = FALSE;
1047                 frame_adjust_state(client->frame);
1048             }
1049             break;
1050         default:
1051             but = context_to_button(client->frame, con, FALSE);
1052             if (but && !*but && !pb) {
1053                 *but = TRUE;
1054                 frame_adjust_state(client->frame);
1055             }
1056             break;
1057         }
1058         break;
1059     case LeaveNotify:
1060         con = frame_context(client, e->xcrossing.window,
1061                             e->xcrossing.x, e->xcrossing.y);
1062         switch (con) {
1063         case OB_FRAME_CONTEXT_TITLEBAR:
1064         case OB_FRAME_CONTEXT_TLCORNER:
1065         case OB_FRAME_CONTEXT_TRCORNER:
1066             /* we've left the button area inside the titlebar */
1067             client->frame->max_hover =
1068                 client->frame->desk_hover =
1069                 client->frame->shade_hover =
1070                 client->frame->iconify_hover =
1071                 client->frame->close_hover = FALSE;
1072             if (e->xcrossing.mode == NotifyGrab) {
1073                 client->frame->max_press =
1074                     client->frame->desk_press =
1075                     client->frame->shade_press =
1076                     client->frame->iconify_press =
1077                     client->frame->close_press = FALSE;
1078             }
1079             break;
1080         case OB_FRAME_CONTEXT_FRAME:
1081             /* When the mouse leaves an animating window, don't use the
1082                corresponding enter events. Pretend like the animating window
1083                doesn't even exist..! */
1084             if (frame_iconify_animating(client->frame))
1085                 event_end_ignore_all_enters(event_start_ignore_all_enters());
1086
1087             ob_debug_type(OB_DEBUG_FOCUS,
1088                           "%sNotify mode %d detail %d on %lx",
1089                           (e->type == EnterNotify ? "Enter" : "Leave"),
1090                           e->xcrossing.mode,
1091                           e->xcrossing.detail, (client?client->window:0));
1092             if (grab_on_keyboard())
1093                 break;
1094             if (config_focus_follow &&
1095                 /* leave inferior events can happen when the mouse goes onto
1096                    the window's border and then into the window before the
1097                    delay is up */
1098                 e->xcrossing.detail != NotifyInferior)
1099             {
1100                 if (config_focus_delay && focus_delay_timeout_id)
1101                     g_source_remove(focus_delay_timeout_id);
1102                 if (config_unfocus_leave)
1103                     event_leave_client(client);
1104                 else if (client == focus_client)
1105                     client_left_focused = e->xcrossing.time;
1106             }
1107             break;
1108         default:
1109             but = context_to_button(client->frame, con, FALSE);
1110             if (but) {
1111                 *but = FALSE;
1112                 if (e->xcrossing.mode == NotifyGrab) {
1113                     but = context_to_button(client->frame, con, TRUE);
1114                     *but = FALSE;
1115                 }
1116                 frame_adjust_state(client->frame);
1117             }
1118             break;
1119         }
1120         break;
1121     case EnterNotify:
1122     {
1123         con = frame_context(client, e->xcrossing.window,
1124                             e->xcrossing.x, e->xcrossing.y);
1125         switch (con) {
1126         case OB_FRAME_CONTEXT_FRAME:
1127             if (grab_on_keyboard())
1128                 break;
1129             if (e->xcrossing.mode == NotifyGrab ||
1130                 (e->xcrossing.mode == NotifyUngrab &&
1131                  /* ungrab enters are used when _under_ mouse is being used */
1132                  !(config_focus_follow && config_focus_under_mouse)) ||
1133                 /*ignore enters when we're already in the window */
1134                 e->xcrossing.detail == NotifyInferior)
1135             {
1136                 ob_debug_type(OB_DEBUG_FOCUS,
1137                               "%sNotify mode %d detail %d serial %lu on %lx "
1138                               "IGNORED",
1139                               (e->type == EnterNotify ? "Enter" : "Leave"),
1140                               e->xcrossing.mode,
1141                               e->xcrossing.detail,
1142                               e->xcrossing.serial,
1143                               client?client->window:0);
1144             }
1145             else {
1146                 ob_debug_type(OB_DEBUG_FOCUS,
1147                               "%sNotify mode %d detail %d serial %lu on %lx, "
1148                               "focusing window",
1149                               (e->type == EnterNotify ? "Enter" : "Leave"),
1150                               e->xcrossing.mode,
1151                               e->xcrossing.detail,
1152                               e->xcrossing.serial,
1153                               (client?client->window:0));
1154                 if (config_focus_follow) {
1155                     if (config_focus_delay && unfocus_delay_timeout_id)
1156                         g_source_remove(unfocus_delay_timeout_id);
1157                     event_enter_client(client);
1158                 }
1159             }
1160             break;
1161         default:
1162             but = context_to_button(client->frame, con, FALSE);
1163             if (but) {
1164                 *but = TRUE;
1165                 if (e->xcrossing.mode == NotifyUngrab) {
1166                     but = context_to_button(client->frame, con, TRUE);
1167                     *but = (con == pcon);
1168                 }
1169                 frame_adjust_state(client->frame);
1170             }
1171             break;
1172         }
1173         break;
1174     }
1175     case ConfigureRequest:
1176     {
1177         if (client->locked)
1178             break;
1179
1180         /* dont compress these unless you're going to watch for property
1181            notifies in between (these can change what the configure would
1182            do to the window).
1183            also you can't compress stacking events
1184         */
1185
1186         gint x, y, w, h;
1187         gboolean move = FALSE;
1188         gboolean resize = FALSE;
1189
1190         /* get the current area */
1191         RECT_TO_DIMS(client->area, x, y, w, h);
1192
1193         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1194                  "visible %d",
1195                  client->title,
1196                  screen_desktop, client->wmstate, client->frame->visible);
1197         ob_debug("                     x %d y %d w %d h %d b %d",
1198                  x, y, w, h, client->border_width);
1199
1200         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1201             if (client->border_width != e->xconfigurerequest.border_width) {
1202                 client->border_width = e->xconfigurerequest.border_width;
1203
1204                 /* if the border width is changing then that is the same
1205                    as requesting a resize, but we don't actually change
1206                    the client's border, so it will change their root
1207                    coordinates (since they include the border width) and
1208                    we need to a notify then */
1209                 move = TRUE;
1210             }
1211
1212         if (e->xconfigurerequest.value_mask & CWStackMode) {
1213             ObClient *sibling = NULL;
1214             gulong ignore_start;
1215             gboolean ok = TRUE;
1216
1217             /* get the sibling */
1218             if (e->xconfigurerequest.value_mask & CWSibling) {
1219                 ObWindow *win;
1220                 win = window_find(e->xconfigurerequest.above);
1221                 if (win && WINDOW_IS_CLIENT(win) &&
1222                     WINDOW_AS_CLIENT(win) != client)
1223                 {
1224                     sibling = WINDOW_AS_CLIENT(win);
1225                 }
1226                 else
1227                     /* an invalid sibling was specified so don't restack at
1228                        all, it won't make sense no matter what we do */
1229                     ok = FALSE;
1230             }
1231
1232             if (ok) {
1233                 if (!config_focus_under_mouse)
1234                     ignore_start = event_start_ignore_all_enters();
1235                 stacking_restack_request(client, sibling,
1236                                          e->xconfigurerequest.detail);
1237                 if (!config_focus_under_mouse)
1238                     event_end_ignore_all_enters(ignore_start);
1239             }
1240
1241             /* a stacking change moves the window without resizing */
1242             move = TRUE;
1243         }
1244
1245         if ((e->xconfigurerequest.value_mask & CWX) ||
1246             (e->xconfigurerequest.value_mask & CWY) ||
1247             (e->xconfigurerequest.value_mask & CWWidth) ||
1248             (e->xconfigurerequest.value_mask & CWHeight))
1249         {
1250             /* don't allow clients to move shaded windows (fvwm does this)
1251             */
1252             if (e->xconfigurerequest.value_mask & CWX) {
1253                 if (!client->shaded)
1254                     x = e->xconfigurerequest.x;
1255                 move = TRUE;
1256             }
1257             if (e->xconfigurerequest.value_mask & CWY) {
1258                 if (!client->shaded)
1259                     y = e->xconfigurerequest.y;
1260                 move = TRUE;
1261             }
1262
1263             if (e->xconfigurerequest.value_mask & CWWidth) {
1264                 w = e->xconfigurerequest.width;
1265                 resize = TRUE;
1266             }
1267             if (e->xconfigurerequest.value_mask & CWHeight) {
1268                 h = e->xconfigurerequest.height;
1269                 resize = TRUE;
1270             }
1271         }
1272
1273         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1274                  "move %d resize %d",
1275                  e->xconfigurerequest.value_mask & CWX, x,
1276                  e->xconfigurerequest.value_mask & CWY, y,
1277                  e->xconfigurerequest.value_mask & CWWidth, w,
1278                  e->xconfigurerequest.value_mask & CWHeight, h,
1279                  move, resize);
1280
1281         /* check for broken apps moving to their root position
1282
1283            XXX remove this some day...that would be nice. right now all
1284            kde apps do this when they try activate themselves on another
1285            desktop. eg. open amarok window on desktop 1, switch to desktop
1286            2, click amarok tray icon. it will move by its decoration size.
1287         */
1288         if (x != client->area.x &&
1289             x == (client->frame->area.x + client->frame->size.left -
1290                   (gint)client->border_width) &&
1291             y != client->area.y &&
1292             y == (client->frame->area.y + client->frame->size.top -
1293                   (gint)client->border_width) &&
1294             w == client->area.width &&
1295             h == client->area.height)
1296         {
1297             ob_debug_type(OB_DEBUG_APP_BUGS,
1298                           "Application %s is trying to move via "
1299                           "ConfigureRequest to it's root window position "
1300                           "but it is not using StaticGravity",
1301                           client->title);
1302             /* don't move it */
1303             x = client->area.x;
1304             y = client->area.y;
1305
1306             /* they still requested a move, so don't change whether a
1307                notify is sent or not */
1308         }
1309
1310         /* check for broken apps (java swing) moving to 0,0 when there is a
1311            strut there.
1312
1313            XXX remove this some day...that would be nice. but really unexpected
1314            from Sun Microsystems.
1315         */
1316         if (x == 0 && y == 0 && client->gravity == NorthWestGravity &&
1317             client_normal(client))
1318         {
1319             const Rect to = { x, y, w, h };
1320
1321             /* oldschool fullscreen windows are allowed */
1322             if (!client_is_oldfullscreen(client, &to)) {
1323                 Rect *r;
1324
1325                 r = screen_area(client->desktop, SCREEN_AREA_ALL_MONITORS,
1326                                 NULL);
1327                 if (r->x || r->y) {
1328                     /* move the window only to the corner outside struts */
1329                     x = r->x;
1330                     y = r->y;
1331
1332                     ob_debug_type(OB_DEBUG_APP_BUGS,
1333                                   "Application %s is trying to move via "
1334                                   "ConfigureRequest to 0,0 using "
1335                                   "NorthWestGravity, while there is a "
1336                                   "strut there. "
1337                                   "Moving buggy app from (0,0) to (%d,%d)",
1338                                   client->title, r->x, r->y);
1339                 }
1340
1341                 g_slice_free(Rect, r);
1342
1343                 /* they still requested a move, so don't change whether a
1344                    notify is sent or not */
1345             }
1346         }
1347
1348         {
1349             gint lw, lh;
1350
1351             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1352
1353             /* if x was not given, then use gravity to figure out the new
1354                x.  the reference point should not be moved */
1355             if ((e->xconfigurerequest.value_mask & CWWidth &&
1356                  !(e->xconfigurerequest.value_mask & CWX)))
1357                 client_gravity_resize_w(client, &x, client->area.width, w);
1358             /* same for y */
1359             if ((e->xconfigurerequest.value_mask & CWHeight &&
1360                  !(e->xconfigurerequest.value_mask & CWY)))
1361                 client_gravity_resize_h(client, &y, client->area.height,h);
1362
1363             client_find_onscreen(client, &x, &y, w, h, FALSE);
1364
1365             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d",
1366                      x, y, w, h);
1367             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1368         }
1369         break;
1370     }
1371     case UnmapNotify:
1372         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1373                  "ignores left %d",
1374                  client->window, e->xunmap.event, e->xunmap.from_configure,
1375                  client->ignore_unmaps);
1376         if (client->ignore_unmaps) {
1377             client->ignore_unmaps--;
1378             break;
1379         }
1380         client_unmanage(client);
1381         break;
1382     case DestroyNotify:
1383         ob_debug("DestroyNotify for window 0x%x", client->window);
1384         client_unmanage(client);
1385         break;
1386     case ReparentNotify:
1387         /* this is when the client is first taken captive in the frame */
1388         if (e->xreparent.parent == client->frame->window) break;
1389
1390         /*
1391           This event is quite rare and is usually handled in unmapHandler.
1392           However, if the window is unmapped when the reparent event occurs,
1393           the window manager never sees it because an unmap event is not sent
1394           to an already unmapped window.
1395         */
1396
1397         ob_debug("ReparentNotify for window 0x%x", client->window);
1398         client_unmanage(client);
1399         break;
1400     case MapRequest:
1401         ob_debug("MapRequest for 0x%lx", client->window);
1402         if (!client->iconic) break; /* this normally doesn't happen, but if it
1403                                        does, we don't want it!
1404                                        it can happen now when the window is on
1405                                        another desktop, but we still don't
1406                                        want it! */
1407         client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE);
1408         break;
1409     case ClientMessage:
1410         /* validate cuz we query stuff off the client here */
1411         if (!client_validate(client)) break;
1412
1413         if (e->xclient.format != 32) return;
1414
1415         msgtype = e->xclient.message_type;
1416         if (msgtype == OBT_PROP_ATOM(WM_CHANGE_STATE)) {
1417             if (!more_client_message_event(client->window, msgtype))
1418                 if (!client->locked)
1419                     client_set_wm_state(client, e->xclient.data.l[0]);
1420         } else if (msgtype == OBT_PROP_ATOM(NET_WM_DESKTOP)) {
1421             if (!more_client_message_event(client->window, msgtype) &&
1422                 ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1423                  (unsigned)e->xclient.data.l[0] == DESKTOP_ALL))
1424             {
1425                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1426                                    FALSE, FALSE);
1427             }
1428         } else if (msgtype == OBT_PROP_ATOM(NET_WM_STATE)) {
1429             gulong ignore_start;
1430
1431             /* can't compress these */
1432             ob_debug("net_wm_state %s %ld %ld for 0x%lx",
1433                      (e->xclient.data.l[0] == 0 ? "Remove" :
1434                       e->xclient.data.l[0] == 1 ? "Add" :
1435                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1436                      e->xclient.data.l[1], e->xclient.data.l[2],
1437                      client->window);
1438
1439             if (!client->locked) {
1440                 /* ignore enter events caused by these like ob actions do */
1441                 if (!config_focus_under_mouse)
1442                     ignore_start = event_start_ignore_all_enters();
1443                 client_set_state(client, e->xclient.data.l[0],
1444                                  e->xclient.data.l[1], e->xclient.data.l[2]);
1445                 if (!config_focus_under_mouse)
1446                     event_end_ignore_all_enters(ignore_start);
1447             }
1448         } else if (msgtype == OBT_PROP_ATOM(NET_CLOSE_WINDOW)) {
1449             ob_debug("net_close_window for 0x%lx", client->window);
1450             if (!client->locked)
1451                 client_close(client);
1452         } else if (msgtype == OBT_PROP_ATOM(NET_ACTIVE_WINDOW)) {
1453             ob_debug("net_active_window for 0x%lx source=%s",
1454                      client->window,
1455                      (e->xclient.data.l[0] == 0 ? "unknown" :
1456                       (e->xclient.data.l[0] == 1 ? "application" :
1457                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1458             /* XXX make use of data.l[2] !? */
1459             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1460                 /* we can not trust the timestamp from applications.
1461                    e.g. chromium passes a very old timestamp.  openbox thinks
1462                    the window will get focus and calls XSetInputFocus with the
1463                    (old) timestamp, which doesn't end up moving focus at all.
1464                    but the window is raised, not hilited, etc, as if it was
1465                    really going to get focus.
1466
1467                    so do not use this timestamp in event_curtime, as this would
1468                    be used in XSetInputFocus.
1469                 */
1470                 event_sourcetime = e->xclient.data.l[1];
1471                 if (e->xclient.data.l[1] == 0)
1472                     ob_debug_type(OB_DEBUG_APP_BUGS,
1473                                   "_NET_ACTIVE_WINDOW message for window %s is"
1474                                   " missing a timestamp", client->title);
1475             } else
1476                 ob_debug_type(OB_DEBUG_APP_BUGS,
1477                               "_NET_ACTIVE_WINDOW message for window %s is "
1478                               "missing source indication", client->title);
1479             if (!client->locked)
1480                 client_activate(client, FALSE, FALSE, TRUE, TRUE,
1481                                 (e->xclient.data.l[0] == 0 ||
1482                                  e->xclient.data.l[0] == 2));
1483         } else if (msgtype == OBT_PROP_ATOM(OB_FOCUS)) {
1484             client_focus(client);
1485         } else if (msgtype == OBT_PROP_ATOM(NET_WM_MOVERESIZE)) {
1486             ob_debug("net_wm_moveresize for 0x%lx direction %d",
1487                      client->window, e->xclient.data.l[2]);
1488             if ((Atom)e->xclient.data.l[2] ==
1489                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
1490                 (Atom)e->xclient.data.l[2] ==
1491                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) ||
1492                 (Atom)e->xclient.data.l[2] ==
1493                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT) ||
1494                 (Atom)e->xclient.data.l[2] ==
1495                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1496                 (Atom)e->xclient.data.l[2] ==
1497                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1498                 (Atom)e->xclient.data.l[2] ==
1499                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) ||
1500                 (Atom)e->xclient.data.l[2] ==
1501                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM) ||
1502                 (Atom)e->xclient.data.l[2] ==
1503                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) ||
1504                 (Atom)e->xclient.data.l[2] ==
1505                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) ||
1506                 (Atom)e->xclient.data.l[2] ==
1507                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) ||
1508                 (Atom)e->xclient.data.l[2] ==
1509                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD) ||
1510                 (Atom)e->xclient.data.l[2] ==
1511                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
1512             {
1513                 if (!client->locked)
1514                     moveresize_start(client, e->xclient.data.l[0],
1515                                      e->xclient.data.l[1], e->xclient.data.l[3],
1516                                      e->xclient.data.l[2]);
1517             }
1518             else if ((Atom)e->xclient.data.l[2] ==
1519                      OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL))
1520                 if (moveresize_client)
1521                     moveresize_end(TRUE);
1522         } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) {
1523             if (client->locked)
1524                 break;
1525
1526             gint ograv, x, y, w, h;
1527
1528             ograv = client->gravity;
1529
1530             if (e->xclient.data.l[0] & 0xff)
1531                 client->gravity = e->xclient.data.l[0] & 0xff;
1532
1533             if (e->xclient.data.l[0] & 1 << 8)
1534                 x = e->xclient.data.l[1];
1535             else
1536                 x = client->area.x;
1537             if (e->xclient.data.l[0] & 1 << 9)
1538                 y = e->xclient.data.l[2];
1539             else
1540                 y = client->area.y;
1541
1542             if (e->xclient.data.l[0] & 1 << 10) {
1543                 w = e->xclient.data.l[3];
1544
1545                 /* if x was not given, then use gravity to figure out the new
1546                    x.  the reference point should not be moved */
1547                 if (!(e->xclient.data.l[0] & 1 << 8))
1548                     client_gravity_resize_w(client, &x, client->area.width, w);
1549             }
1550             else
1551                 w = client->area.width;
1552
1553             if (e->xclient.data.l[0] & 1 << 11) {
1554                 h = e->xclient.data.l[4];
1555
1556                 /* same for y */
1557                 if (!(e->xclient.data.l[0] & 1 << 9))
1558                     client_gravity_resize_h(client, &y, client->area.height,h);
1559             }
1560             else
1561                 h = client->area.height;
1562
1563             ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)",
1564                      e->xclient.data.l[0] & 1 << 8, x,
1565                      e->xclient.data.l[0] & 1 << 9, y,
1566                      client->gravity);
1567
1568             client_find_onscreen(client, &x, &y, w, h, FALSE);
1569
1570             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1571
1572             client->gravity = ograv;
1573         } else if (msgtype == OBT_PROP_ATOM(NET_RESTACK_WINDOW)) {
1574             if (e->xclient.data.l[0] != 2) {
1575                 ob_debug_type(OB_DEBUG_APP_BUGS,
1576                               "_NET_RESTACK_WINDOW sent for window %s with "
1577                               "invalid source indication %ld",
1578                               client->title, e->xclient.data.l[0]);
1579             } else {
1580                 ObClient *sibling = NULL;
1581                 if (e->xclient.data.l[1]) {
1582                     ObWindow *win = window_find(e->xclient.data.l[1]);
1583                     if (WINDOW_IS_CLIENT(win) &&
1584                         WINDOW_AS_CLIENT(win) != client)
1585                     {
1586                         sibling = WINDOW_AS_CLIENT(win);
1587                     }
1588                     if (sibling == NULL)
1589                         ob_debug_type(OB_DEBUG_APP_BUGS,
1590                                       "_NET_RESTACK_WINDOW sent for window %s "
1591                                       "with invalid sibling 0x%x",
1592                                  client->title, e->xclient.data.l[1]);
1593                 }
1594                 if (e->xclient.data.l[2] == Below ||
1595                     e->xclient.data.l[2] == BottomIf ||
1596                     e->xclient.data.l[2] == Above ||
1597                     e->xclient.data.l[2] == TopIf ||
1598                     e->xclient.data.l[2] == Opposite)
1599                 {
1600                     gulong ignore_start;
1601
1602                     if (!config_focus_under_mouse)
1603                         ignore_start = event_start_ignore_all_enters();
1604                     /* just raise, don't activate */
1605                     stacking_restack_request(client, sibling,
1606                                              e->xclient.data.l[2]);
1607                     if (!config_focus_under_mouse)
1608                         event_end_ignore_all_enters(ignore_start);
1609
1610                     /* send a synthetic ConfigureNotify, cuz this is supposed
1611                        to be like a ConfigureRequest. */
1612                     client_reconfigure(client, TRUE);
1613                 } else
1614                     ob_debug_type(OB_DEBUG_APP_BUGS,
1615                                   "_NET_RESTACK_WINDOW sent for window %s "
1616                                   "with invalid detail %d",
1617                                   client->title, e->xclient.data.l[2]);
1618             }
1619         }
1620         break;
1621     case PropertyNotify:
1622         /* validate cuz we query stuff off the client here */
1623         if (!client_validate(client)) break;
1624
1625         msgtype = e->xproperty.atom;
1626
1627         /* ignore changes to some properties if there is another change
1628            coming in the queue */
1629         {
1630             struct ObSkipPropertyChange s;
1631             s.window = client->window;
1632             s.prop = msgtype;
1633             if (xqueue_exists_local(skip_property_change, &s))
1634                 break;
1635         }
1636
1637         msgtype = e->xproperty.atom;
1638         if (msgtype == XA_WM_NORMAL_HINTS) {
1639             int x, y, w, h, lw, lh;
1640
1641             ob_debug("Update NORMAL hints");
1642             client_update_normal_hints(client);
1643             /* normal hints can make a window non-resizable */
1644             client_setup_decor_and_functions(client, FALSE);
1645
1646             x = client->area.x;
1647             y = client->area.y;
1648             w = client->area.width;
1649             h = client->area.height;
1650
1651             /* apply the new normal hints */
1652             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1653             /* make sure the window is visible, and if the window is resized
1654                off-screen due to the normal hints changing then this will push
1655                it back onto the screen. */
1656             client_find_onscreen(client, &x, &y, w, h, FALSE);
1657
1658             /* make sure the client's sizes are within its bounds, but don't
1659                make it reply with a configurenotify unless something changed.
1660                emacs will update its normal hints every time it receives a
1661                configurenotify */
1662             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1663         } else if (msgtype == OBT_PROP_ATOM(MOTIF_WM_HINTS)) {
1664             client_get_mwm_hints(client);
1665             /* This can override some mwm hints */
1666             client_get_type_and_transientness(client);
1667
1668             /* Apply the changes to the window */
1669             client_setup_decor_and_functions(client, TRUE);
1670         } else if (msgtype == XA_WM_HINTS) {
1671             client_update_wmhints(client);
1672         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1673             /* get the transient-ness first, as this affects if the client
1674                decides to be transient for the group or not in
1675                client_update_transient_for() */
1676             client_get_type_and_transientness(client);
1677             client_update_transient_for(client);
1678             /* type may have changed, so update the layer */
1679             client_calc_layer(client);
1680             client_setup_decor_and_functions(client, TRUE);
1681         } else if (msgtype == OBT_PROP_ATOM(NET_WM_NAME) ||
1682                    msgtype == OBT_PROP_ATOM(WM_NAME) ||
1683                    msgtype == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1684                    msgtype == OBT_PROP_ATOM(WM_ICON_NAME)) {
1685             client_update_title(client);
1686         } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
1687             client_update_protocols(client);
1688         }
1689         else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT) ||
1690                  msgtype == OBT_PROP_ATOM(NET_WM_STRUT_PARTIAL)) {
1691             client_update_strut(client);
1692         }
1693         else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON)) {
1694             client_update_icons(client);
1695         }
1696         else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON_GEOMETRY)) {
1697             client_update_icon_geometry(client);
1698         }
1699         else if (msgtype == OBT_PROP_ATOM(NET_WM_USER_TIME)) {
1700             guint32 t;
1701             if (client == focus_client &&
1702                 OBT_PROP_GET32(client->window, NET_WM_USER_TIME, CARDINAL, &t)
1703                 && t && !event_time_after(t, e->xproperty.time) &&
1704                 (!event_last_user_time ||
1705                  event_time_after(t, event_last_user_time)))
1706             {
1707                 event_last_user_time = t;
1708             }
1709         }
1710         else if (msgtype == OBT_PROP_ATOM(NET_WM_WINDOW_OPACITY)) {
1711             client_update_opacity(client);
1712         }
1713 #ifdef SYNC
1714         else if (msgtype == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) {
1715             /* if they are resizing right now this would cause weird behaviour.
1716                if one day a user reports clients stop resizing, then handle
1717                this better by resetting a new XSync alarm and stuff on the
1718                new counter, but I expect it will never happen */
1719             if (moveresize_client == client)
1720                 moveresize_end(FALSE);
1721             client_update_sync_request_counter(client);
1722         }
1723 #endif
1724         break;
1725     case ColormapNotify:
1726         client_update_colormap(client, e->xcolormap.colormap);
1727         break;
1728     default:
1729         ;
1730 #ifdef SHAPE
1731         {
1732             int kind;
1733             if (obt_display_extension_shape &&
1734                 e->type == obt_display_extension_shape_basep)
1735             {
1736                 switch (((XShapeEvent*)e)->kind) {
1737                     case ShapeBounding:
1738                     case ShapeClip:
1739                         client->shaped = ((XShapeEvent*)e)->shaped;
1740                         kind = ShapeBounding;
1741                         break;
1742 #ifdef ShapeInput
1743                     case ShapeInput:
1744                         client->shaped_input = ((XShapeEvent*)e)->shaped;
1745                         kind = ShapeInput;
1746                         break;
1747 #endif
1748                     default:
1749                         g_assert_not_reached();
1750                 }
1751                 frame_adjust_shape_kind(client->frame, kind);
1752             }
1753         }
1754 #endif
1755     }
1756 }
1757
1758 static void event_handle_dock(ObDock *s, XEvent *e)
1759 {
1760     switch (e->type) {
1761     case EnterNotify:
1762         dock_hide(FALSE);
1763         break;
1764     case LeaveNotify:
1765         /* don't hide when moving into a dock app */
1766         if (e->xcrossing.detail != NotifyInferior)
1767             dock_hide(TRUE);
1768         break;
1769     }
1770 }
1771
1772 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1773 {
1774     switch (e->type) {
1775     case MotionNotify:
1776         dock_app_drag(app, &e->xmotion);
1777         break;
1778     case UnmapNotify:
1779         if (app->ignore_unmaps) {
1780             app->ignore_unmaps--;
1781             break;
1782         }
1783         dock_unmanage(app, TRUE);
1784         break;
1785     case DestroyNotify:
1786     case ReparentNotify:
1787         dock_unmanage(app, FALSE);
1788         break;
1789     case ConfigureNotify:
1790         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1791         break;
1792     }
1793 }
1794
1795 static ObMenuFrame* find_active_menu(void)
1796 {
1797     GList *it;
1798     ObMenuFrame *ret = NULL;
1799
1800     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1801         ret = it->data;
1802         if (ret->selected)
1803             break;
1804         ret = NULL;
1805     }
1806     return ret;
1807 }
1808
1809 static ObMenuFrame* find_active_or_last_menu(void)
1810 {
1811     ObMenuFrame *ret = NULL;
1812
1813     ret = find_active_menu();
1814     if (!ret && menu_frame_visible)
1815         ret = menu_frame_visible->data;
1816     return ret;
1817 }
1818
1819 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
1820 {
1821     switch (e->type) {
1822     case ButtonPress:
1823     case ButtonRelease:
1824     case MotionNotify:
1825         return prompt_mouse_event(p, e);
1826         break;
1827     case KeyPress:
1828         return prompt_key_event(p, e);
1829         break;
1830     }
1831     return FALSE;
1832 }
1833
1834 static gboolean event_handle_menu_input(XEvent *ev)
1835 {
1836     gboolean ret = FALSE;
1837
1838     if (ev->type == ButtonRelease || ev->type == ButtonPress) {
1839         ObMenuEntryFrame *e;
1840
1841         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5) &&
1842             ((ev->type == ButtonRelease && menu_hide_delay_reached()) ||
1843              ev->type == ButtonPress))
1844         {
1845             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1846                                             ev->xbutton.y_root)))
1847             {
1848                 if (ev->type == ButtonPress && e->frame->child)
1849                     menu_frame_select(e->frame->child, NULL, TRUE);
1850                 menu_frame_select(e->frame, e, TRUE);
1851                 if (ev->type == ButtonRelease)
1852                     menu_entry_frame_execute(e, ev->xbutton.state);
1853             }
1854             else
1855                 menu_frame_hide_all();
1856         }
1857         ret = TRUE;
1858     }
1859     else if (ev->type == KeyPress || ev->type == KeyRelease) {
1860         guint mods;
1861         ObMenuFrame *frame;
1862
1863         /* get the modifiers */
1864         mods = obt_keyboard_only_modmasks(ev->xkey.state);
1865
1866         frame = find_active_or_last_menu();
1867         if (frame == NULL)
1868             g_assert_not_reached(); /* there is no active menu */
1869
1870         /* Allow control while going thru the menu */
1871         else if (ev->type == KeyPress && (mods & ~ControlMask) == 0) {
1872             gunichar unikey;
1873             KeySym sym;
1874
1875             frame->got_press = TRUE;
1876             frame->press_keycode = ev->xkey.keycode;
1877             frame->press_doexec = FALSE;
1878
1879             sym = obt_keyboard_keypress_to_keysym(ev);
1880
1881             if (sym == XK_Escape) {
1882                 menu_frame_hide_all();
1883                 ret = TRUE;
1884             }
1885
1886             else if (sym == XK_Left) {
1887                 /* Left goes to the parent menu */
1888                 if (frame->parent) {
1889                     /* remove focus from the child */
1890                     menu_frame_select(frame, NULL, TRUE);
1891                     /* and put it in the parent */
1892                     menu_frame_select(frame->parent, frame->parent->selected,
1893                                       TRUE);
1894                 }
1895                 ret = TRUE;
1896             }
1897
1898             else if (sym == XK_Right || sym == XK_Return || sym == XK_KP_Enter)
1899             {
1900                 /* Right and enter goes to the selected submenu.
1901                    Enter executes instead if it's not on a submenu. */
1902
1903                 if (frame->selected) {
1904                     const ObMenuEntryType t = frame->selected->entry->type;
1905
1906                     if (t == OB_MENU_ENTRY_TYPE_SUBMENU) {
1907                         /* make sure it is visible */
1908                         menu_frame_select(frame, frame->selected, TRUE);
1909                         /* move focus to the child menu */
1910                         menu_frame_select_next(frame->child);
1911                     }
1912                     else if (sym != XK_Right) {
1913                         frame->press_doexec = TRUE;
1914                     }
1915                 }
1916                 ret = TRUE;
1917             }
1918
1919             else if (sym == XK_Up) {
1920                 menu_frame_select_previous(frame);
1921                 ret = TRUE;
1922             }
1923
1924             else if (sym == XK_Down) {
1925                 menu_frame_select_next(frame);
1926                 ret = TRUE;
1927             }
1928
1929             else if (sym == XK_Home) {
1930                 menu_frame_select_first(frame);
1931                 ret = TRUE;
1932             }
1933
1934             else if (sym == XK_End) {
1935                 menu_frame_select_last(frame);
1936                 ret = TRUE;
1937             }
1938
1939             /* keyboard accelerator shortcuts. (if it was a valid key) */
1940             else if (frame->entries &&
1941                      (unikey =
1942                       obt_keyboard_keypress_to_unichar(menu_frame_ic(frame),
1943                                                        ev)))
1944             {
1945                 GList *start;
1946                 GList *it;
1947                 ObMenuEntryFrame *found = NULL;
1948                 guint num_found = 0;
1949
1950                 /* start after the selected one */
1951                 start = frame->entries;
1952                 if (frame->selected) {
1953                     for (it = start; frame->selected != it->data;
1954                          it = g_list_next(it))
1955                         g_assert(it != NULL); /* nothing was selected? */
1956                     /* next with wraparound */
1957                     start = g_list_next(it);
1958                     if (start == NULL) start = frame->entries;
1959                 }
1960
1961                 it = start;
1962                 do {
1963                     ObMenuEntryFrame *e = it->data;
1964                     gunichar entrykey = 0;
1965
1966                     if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1967                         entrykey = e->entry->data.normal.shortcut;
1968                     else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1969                         entrykey = e->entry->data.submenu.submenu->shortcut;
1970
1971                     if (unikey == entrykey) {
1972                         if (found == NULL) found = e;
1973                         ++num_found;
1974                     }
1975
1976                     /* next with wraparound */
1977                     it = g_list_next(it);
1978                     if (it == NULL) it = frame->entries;
1979                 } while (it != start);
1980
1981                 if (found) {
1982                     menu_frame_select(frame, found, TRUE);
1983
1984                     if (num_found == 1) {
1985                         if (found->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
1986                             /* move focus to the child menu */
1987                             menu_frame_select_next(frame->child);
1988                         }
1989                         else {
1990                             frame->press_doexec = TRUE;
1991                         }
1992                     }
1993                     ret = TRUE;
1994                 }
1995             }
1996         }
1997
1998         /* Use KeyRelease events for running things so that the key release
1999            doesn't get sent to the focused application.
2000
2001            Allow ControlMask only, and don't bother if the menu is empty */
2002         else if (ev->type == KeyRelease && (mods & ~ControlMask) == 0) {
2003             if (frame->press_keycode == ev->xkey.keycode &&
2004                 frame->got_press &&
2005                 frame->press_doexec)
2006             {
2007                 if (frame->selected)
2008                     menu_entry_frame_execute(frame->selected, ev->xkey.state);
2009             }
2010         }
2011     }
2012
2013     return ret;
2014 }
2015
2016 static gboolean event_look_for_menu_enter(XEvent *ev, gpointer data)
2017 {
2018     const ObMenuFrame *f = (ObMenuFrame*)data;
2019     ObMenuEntryFrame *e;
2020     return ev->type == EnterNotify &&
2021         (e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
2022         e->frame == f && !e->ignore_enters;
2023 }
2024
2025 static void event_handle_menu(ObMenuFrame *frame, XEvent *ev)
2026 {
2027     ObMenuFrame *f;
2028     ObMenuEntryFrame *e;
2029
2030     switch (ev->type) {
2031     case MotionNotify:
2032         /* We need to catch MotionNotify in addition to EnterNotify because
2033            it is possible for the menu to be opened under the mouse cursor, and
2034            moving the mouse should select the item. */
2035         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xmotion.window))) {
2036             if (e->ignore_enters)
2037                 --e->ignore_enters;
2038             else if (!(f = find_active_menu()) ||
2039                      f == e->frame ||
2040                      f->parent == e->frame ||
2041                      f->child == e->frame)
2042                 menu_frame_select(e->frame, e, FALSE);
2043         }
2044         break;
2045     case EnterNotify:
2046         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
2047             if (e->ignore_enters)
2048                 --e->ignore_enters;
2049             else if (!(f = find_active_menu()) ||
2050                      f == e->frame ||
2051                      f->parent == e->frame ||
2052                      f->child == e->frame)
2053                 menu_frame_select(e->frame, e, FALSE);
2054         }
2055         break;
2056     case LeaveNotify:
2057         /* ignore leaves when we're already in the window */
2058         if (ev->xcrossing.detail == NotifyInferior)
2059             break;
2060
2061         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
2062             /* check if an EnterNotify event is coming, and if not, then select
2063                nothing in the menu */
2064             if (!xqueue_exists_local(event_look_for_menu_enter, e->frame))
2065                 menu_frame_select(e->frame, NULL, FALSE);
2066         }
2067         break;
2068     }
2069 }
2070
2071 static gboolean event_handle_user_input(ObClient *client, XEvent *e)
2072 {
2073     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
2074              e->type == MotionNotify || e->type == KeyPress ||
2075              e->type == KeyRelease);
2076
2077     if (menu_frame_visible) {
2078         if (event_handle_menu_input(e))
2079             /* don't use the event if the menu used it, but if the menu
2080                didn't use it and it's a keypress that is bound, it will
2081                close the menu and be used */
2082             return TRUE;
2083     }
2084
2085     /* if the keyboard interactive action uses the event then dont
2086        use it for bindings. likewise is moveresize uses the event. */
2087     if (actions_interactive_input_event(e) || moveresize_event(e))
2088         return TRUE;
2089
2090     if (moveresize_in_progress)
2091         /* make further actions work on the client being
2092            moved/resized */
2093         client = moveresize_client;
2094
2095     if (e->type == ButtonPress ||
2096         e->type == ButtonRelease ||
2097         e->type == MotionNotify)
2098     {
2099         /* the frame may not be "visible" but they can still click on it
2100            in the case where it is animating before disappearing */
2101         if (!client || !frame_iconify_animating(client->frame))
2102             return mouse_event(client, e);
2103     } else
2104         return keyboard_event((focus_cycle_target ? focus_cycle_target :
2105                                (client ? client : focus_client)), e);
2106
2107     return FALSE;
2108 }
2109
2110 static void focus_delay_dest(gpointer data)
2111 {
2112     g_slice_free(ObFocusDelayData, data);
2113     focus_delay_timeout_id = 0;
2114     focus_delay_timeout_client = NULL;
2115 }
2116
2117 static void unfocus_delay_dest(gpointer data)
2118 {
2119     g_slice_free(ObFocusDelayData, data);
2120     unfocus_delay_timeout_id = 0;
2121     unfocus_delay_timeout_client = NULL;
2122 }
2123
2124 static gboolean focus_delay_func(gpointer data)
2125 {
2126     ObFocusDelayData *d = data;
2127     Time old = event_curtime; /* save the curtime */
2128
2129     event_curtime = d->time;
2130     event_curserial = d->serial;
2131     if (client_focus(d->client) && config_focus_raise)
2132         stacking_raise(CLIENT_AS_WINDOW(d->client));
2133     event_curtime = old;
2134     return FALSE; /* no repeat */
2135 }
2136
2137 static gboolean unfocus_delay_func(gpointer data)
2138 {
2139     ObFocusDelayData *d = data;
2140     Time old = event_curtime; /* save the curtime */
2141
2142     event_curtime = d->time;
2143     event_curserial = d->serial;
2144     focus_nothing();
2145     event_curtime = old;
2146     return FALSE; /* no repeat */
2147 }
2148
2149 static void focus_delay_client_dest(ObClient *client, gpointer data)
2150 {
2151     if (focus_delay_timeout_client == client && focus_delay_timeout_id)
2152         g_source_remove(focus_delay_timeout_id);
2153     if (unfocus_delay_timeout_client == client && unfocus_delay_timeout_id)
2154         g_source_remove(unfocus_delay_timeout_id);
2155 }
2156
2157 void event_halt_focus_delay(void)
2158 {
2159     /* ignore all enter events up till the event which caused this to occur */
2160     if (event_curserial) event_ignore_enter_range(1, event_curserial);
2161     if (focus_delay_timeout_id) g_source_remove(focus_delay_timeout_id);
2162     if (unfocus_delay_timeout_id) g_source_remove(unfocus_delay_timeout_id);
2163 }
2164
2165 gulong event_start_ignore_all_enters(void)
2166 {
2167     return NextRequest(obt_display);
2168 }
2169
2170 static void event_ignore_enter_range(gulong start, gulong end)
2171 {
2172     ObSerialRange *r;
2173
2174     g_assert(start != 0);
2175     g_assert(end != 0);
2176
2177     r = g_slice_new(ObSerialRange);
2178     r->start = start;
2179     r->end = end;
2180     ignore_serials = g_slist_prepend(ignore_serials, r);
2181
2182     ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu",
2183                   r->start, r->end);
2184
2185     /* increment the serial so we don't ignore events we weren't meant to */
2186     OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
2187 }
2188
2189 void event_end_ignore_all_enters(gulong start)
2190 {
2191     /* Use (NextRequest-1) so that we ignore up to the current serial only.
2192        Inside event_ignore_enter_range, we increment the serial by one, but if
2193        we ignore that serial too, then any enter events generated by mouse
2194        movement will be ignored until we create some further network traffic.
2195        Instead ignore up to NextRequest-1, then when we increment the serial,
2196        we will be *past* the range of ignored serials */
2197     event_ignore_enter_range(start, NextRequest(obt_display)-1);
2198 }
2199
2200 static gboolean is_enter_focus_event_ignored(gulong serial)
2201 {
2202     GSList *it, *next;
2203
2204     for (it = ignore_serials; it; it = next) {
2205         ObSerialRange *r = it->data;
2206
2207         next = g_slist_next(it);
2208
2209         if ((glong)(serial - r->end) > 0) {
2210             /* past the end */
2211             ignore_serials = g_slist_delete_link(ignore_serials, it);
2212             g_slice_free(ObSerialRange, r);
2213         }
2214         else if ((glong)(serial - r->start) >= 0)
2215             return TRUE;
2216     }
2217     return FALSE;
2218 }
2219
2220 void event_cancel_all_key_grabs(void)
2221 {
2222     if (actions_interactive_act_running()) {
2223         actions_interactive_cancel_act();
2224         ob_debug("KILLED interactive action");
2225     }
2226     else if (menu_frame_visible) {
2227         menu_frame_hide_all();
2228         ob_debug("KILLED open menus");
2229     }
2230     else if (moveresize_in_progress) {
2231         moveresize_end(TRUE);
2232         ob_debug("KILLED interactive moveresize");
2233     }
2234     else if (grab_on_keyboard()) {
2235         ungrab_keyboard();
2236         ob_debug("KILLED active grab on keyboard");
2237     }
2238     else
2239         ungrab_passive_key();
2240
2241     XSync(obt_display, FALSE);
2242 }
2243
2244 gboolean event_time_after(guint32 t1, guint32 t2)
2245 {
2246     g_assert(t1 != CurrentTime);
2247     g_assert(t2 != CurrentTime);
2248
2249     /*
2250       Timestamp values wrap around (after about 49.7 days). The server, given
2251       its current time is represented by timestamp T, always interprets
2252       timestamps from clients by treating half of the timestamp space as being
2253       later in time than T.
2254       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2255     */
2256
2257     /* TIME_HALF is not half of the number space of a Time type variable.
2258      * Rather, it is half the number space of a timestamp value, which is
2259      * always 32 bits. */
2260 #define TIME_HALF (guint32)(1 << 31)
2261
2262     if (t2 >= TIME_HALF)
2263         /* t2 is in the second half so t1 might wrap around and be smaller than
2264            t2 */
2265         return t1 >= t2 || t1 < (t2 + TIME_HALF);
2266     else
2267         /* t2 is in the first half so t1 has to come after it */
2268         return t1 >= t2 && t1 < (t2 + TIME_HALF);
2269 }
2270
2271 gboolean find_timestamp(XEvent *e, gpointer data)
2272 {
2273     const Time t = event_get_timestamp(e);
2274     if (t && t >= event_curtime) {
2275         event_curtime = t;
2276         return TRUE;
2277     }
2278     else
2279         return FALSE;
2280 }
2281
2282 static Time next_time(void)
2283 {
2284     /* Some events don't come with timestamps :(
2285        ...but we can get one anyways >:) */
2286
2287     /* Generate a timestamp so there is guaranteed at least one in the queue
2288        eventually */
2289     XChangeProperty(obt_display, screen_support_win,
2290                     OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING),
2291                     8, PropModeAppend, NULL, 0);
2292
2293     /* Grab the first timestamp available */
2294     xqueue_exists(find_timestamp, NULL);
2295
2296     /*g_assert(event_curtime != CurrentTime);*/
2297
2298     /* Save the time so we don't have to do this again for this event */
2299     return event_curtime;
2300 }
2301
2302 Time event_time(void)
2303 {
2304     if (event_curtime) return event_curtime;
2305
2306     return next_time();
2307 }
2308
2309 Time event_source_time(void)
2310 {
2311     return event_sourcetime;
2312 }
2313
2314 void event_reset_time(void)
2315 {
2316     next_time();
2317 }
2318
2319 void event_update_user_time(void)
2320 {
2321     event_last_user_time = event_time();
2322 }
2323
2324 void event_reset_user_time(void)
2325 {
2326     event_last_user_time = CurrentTime;
2327 }