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