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