make keeping windows on screen much more clever
[dana/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 "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "mouse.h"
35 #include "mainloop.h"
36 #include "framerender.h"
37 #include "focus.h"
38 #include "moveresize.h"
39 #include "group.h"
40 #include "stacking.h"
41 #include "extensions.h"
42 #include "translate.h"
43
44 #include <X11/Xlib.h>
45 #include <X11/keysym.h>
46 #include <X11/Xatom.h>
47 #include <glib.h>
48
49 #ifdef HAVE_SYS_SELECT_H
50 #  include <sys/select.h>
51 #endif
52 #ifdef HAVE_SIGNAL_H
53 #  include <signal.h>
54 #endif
55 #ifdef HAVE_UNISTD_H
56 #  include <unistd.h> /* for usleep() */
57 #endif
58 #ifdef XKB
59 #  include <X11/XKBlib.h>
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 } ObFocusDelayData;
76
77 static void event_process(const XEvent *e, gpointer data);
78 static void event_handle_root(XEvent *e);
79 static void event_handle_menu_shortcut(XEvent *e);
80 static void event_handle_menu(XEvent *e);
81 static void event_handle_dock(ObDock *s, XEvent *e);
82 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
83 static void event_handle_client(ObClient *c, XEvent *e);
84 static void event_handle_group(ObGroup *g, XEvent *e);
85
86 static void focus_delay_dest(gpointer data);
87 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
88 static gboolean focus_delay_func(gpointer data);
89 static void focus_delay_client_dest(ObClient *client, gpointer data);
90
91 static gboolean menu_hide_delay_func(gpointer data);
92
93 /* The time for the current event being processed */
94 Time event_curtime = CurrentTime;
95
96 /*! The value of the mask for the NumLock modifier */
97 guint NumLockMask;
98 /*! The value of the mask for the ScrollLock modifier */
99 guint ScrollLockMask;
100 /*! The key codes for the modifier keys */
101 static XModifierKeymap *modmap;
102 /*! Table of the constant modifier masks */
103 static const gint mask_table[] = {
104     ShiftMask, LockMask, ControlMask, Mod1Mask,
105     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
106 };
107 static gint mask_table_size;
108
109 static guint ignore_enter_focus = 0;
110
111 static gboolean menu_can_hide;
112
113 #ifdef USE_SM
114 static void ice_handler(gint fd, gpointer conn)
115 {
116     Bool b;
117     IceProcessMessages(conn, NULL, &b);
118 }
119
120 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
121                       IcePointer *watch_data)
122 {
123     static gint fd = -1;
124
125     if (opening) {
126         fd = IceConnectionNumber(conn);
127         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
128     } else {
129         ob_main_loop_fd_remove(ob_main_loop, fd);
130         fd = -1;
131     }
132 }
133 #endif
134
135 void event_startup(gboolean reconfig)
136 {
137     if (reconfig) return;
138
139     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
140      
141     /* get lock masks that are defined by the display (not constant) */
142     modmap = XGetModifierMapping(ob_display);
143     g_assert(modmap);
144     if (modmap && modmap->max_keypermod > 0) {
145         size_t cnt;
146         const size_t size = mask_table_size * modmap->max_keypermod;
147         /* get the values of the keyboard lock modifiers
148            Note: Caps lock is not retrieved the same way as Scroll and Num
149            lock since it doesn't need to be. */
150         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
151         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
152                                                      XK_Scroll_Lock);
153
154         for (cnt = 0; cnt < size; ++cnt) {
155             if (! modmap->modifiermap[cnt]) continue;
156
157             if (num_lock == modmap->modifiermap[cnt])
158                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
159             if (scroll_lock == modmap->modifiermap[cnt])
160                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
161         }
162     }
163
164     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
165
166 #ifdef USE_SM
167     IceAddConnectionWatch(ice_watch, NULL);
168 #endif
169
170     client_add_destructor(focus_delay_client_dest, NULL);
171 }
172
173 void event_shutdown(gboolean reconfig)
174 {
175     if (reconfig) return;
176
177 #ifdef USE_SM
178     IceRemoveConnectionWatch(ice_watch, NULL);
179 #endif
180
181     client_remove_destructor(focus_delay_client_dest);
182     XFreeModifiermap(modmap);
183 }
184
185 static Window event_get_window(XEvent *e)
186 {
187     Window window;
188
189     /* pick a window */
190     switch (e->type) {
191     case SelectionClear:
192         window = RootWindow(ob_display, ob_screen);
193         break;
194     case MapRequest:
195         window = e->xmap.window;
196         break;
197     case UnmapNotify:
198         window = e->xunmap.window;
199         break;
200     case DestroyNotify:
201         window = e->xdestroywindow.window;
202         break;
203     case ConfigureRequest:
204         window = e->xconfigurerequest.window;
205         break;
206     case ConfigureNotify:
207         window = e->xconfigure.window;
208         break;
209     default:
210 #ifdef XKB
211         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
212             switch (((XkbAnyEvent*)e)->xkb_type) {
213             case XkbBellNotify:
214                 window = ((XkbBellNotifyEvent*)e)->window;
215             default:
216                 window = None;
217             }
218         } else
219 #endif
220 #ifdef SYNC
221         if (extensions_sync &&
222             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
223         {
224             window = None;
225         } else
226 #endif
227             window = e->xany.window;
228     }
229     return window;
230 }
231
232 static void event_set_curtime(XEvent *e)
233 {
234     Time t = CurrentTime;
235
236     /* grab the lasttime and hack up the state */
237     switch (e->type) {
238     case ButtonPress:
239     case ButtonRelease:
240         t = e->xbutton.time;
241         break;
242     case KeyPress:
243         t = e->xkey.time;
244         break;
245     case KeyRelease:
246         t = e->xkey.time;
247         break;
248     case MotionNotify:
249         t = e->xmotion.time;
250         break;
251     case PropertyNotify:
252         t = e->xproperty.time;
253         break;
254     case EnterNotify:
255     case LeaveNotify:
256         t = e->xcrossing.time;
257         break;
258     default:
259 #ifdef SYNC
260         if (extensions_sync &&
261             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
262         {
263             t = ((XSyncAlarmNotifyEvent*)e)->time;
264         }
265 #endif
266         /* if more event types are anticipated, get their timestamp
267            explicitly */
268         break;
269     }
270
271     event_curtime = t;
272 }
273
274 #define STRIP_MODS(s) \
275         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
276         /* kill off the Button1Mask etc, only want the modifiers */ \
277         s &= (ControlMask | ShiftMask | Mod1Mask | \
278               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
279
280 static void event_hack_mods(XEvent *e)
281 {
282 #ifdef XKB
283     XkbStateRec xkb_state;
284 #endif
285     KeyCode *kp;
286     gint i, k;
287
288     switch (e->type) {
289     case ButtonPress:
290     case ButtonRelease:
291         STRIP_MODS(e->xbutton.state);
292         break;
293     case KeyPress:
294         STRIP_MODS(e->xkey.state);
295         break;
296     case KeyRelease:
297         STRIP_MODS(e->xkey.state);
298         /* remove from the state the mask of the modifier being released, if
299            it is a modifier key being released (this is a little ugly..) */
300 #ifdef XKB
301         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
302             e->xkey.state = xkb_state.compat_state;
303             break;
304         }
305 #endif
306         kp = modmap->modifiermap;
307         for (i = 0; i < mask_table_size; ++i) {
308             for (k = 0; k < modmap->max_keypermod; ++k) {
309                 if (*kp == e->xkey.keycode) { /* found the keycode */
310                     /* remove the mask for it */
311                     e->xkey.state &= ~mask_table[i];
312                     /* cause the first loop to break; */
313                     i = mask_table_size;
314                     break; /* get outta here! */
315                 }
316                 ++kp;
317             }
318         }
319         break;
320     case MotionNotify:
321         STRIP_MODS(e->xmotion.state);
322         /* compress events */
323         {
324             XEvent ce;
325             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
326                                           e->type, &ce)) {
327                 e->xmotion.x_root = ce.xmotion.x_root;
328                 e->xmotion.y_root = ce.xmotion.y_root;
329             }
330         }
331         break;
332     }
333 }
334
335 static gboolean wanted_focusevent(XEvent *e)
336 {
337     gint mode = e->xfocus.mode;
338     gint detail = e->xfocus.detail;
339     Window win = e->xany.window;
340
341     if (e->type == FocusIn) {
342
343         /* These are ones we never want.. */
344
345         /* This means focus was given by a keyboard/mouse grab. */
346         if (mode == NotifyGrab)
347             return FALSE;
348         /* This means focus was given back from a keyboard/mouse grab. */
349         if (mode == NotifyUngrab)
350             return FALSE;
351
352         /* These are the ones we want.. */
353
354         if (win == RootWindow(ob_display, ob_screen)) {
355             /* This means focus reverted off of a client */
356             if (detail == NotifyPointerRoot || detail == NotifyDetailNone ||
357                 detail == NotifyInferior)
358                 return TRUE;
359             else
360                 return FALSE;
361         }
362
363         /* This means focus moved from the root window to a client */
364         if (detail == NotifyVirtual)
365             return TRUE;
366         /* This means focus moved from one client to another */
367         if (detail == NotifyNonlinearVirtual)
368             return TRUE;
369
370         /* Otherwise.. */
371         return FALSE;
372     } else {
373         g_assert(e->type == FocusOut);
374
375
376         /* These are ones we never want.. */
377
378         /* This means focus was taken by a keyboard/mouse grab. */
379         if (mode == NotifyGrab)
380             return FALSE;
381
382         /* Focus left the root window revertedto state */
383         if (win == RootWindow(ob_display, ob_screen))
384             return FALSE;
385
386         /* These are the ones we want.. */
387
388         /* This means focus moved from a client to the root window */
389         if (detail == NotifyVirtual)
390             return TRUE;
391         /* This means focus moved from one client to another */
392         if (detail == NotifyNonlinearVirtual)
393             return TRUE;
394         /* This means focus had moved to our frame window and now moved off */
395         if (detail == NotifyNonlinear)
396             return TRUE;
397
398         /* Otherwise.. */
399         return FALSE;
400     }
401 }
402
403 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
404 {
405     return e->type == FocusIn && wanted_focusevent(e);
406 }
407
408 static gboolean event_ignore(XEvent *e, ObClient *client)
409 {
410     switch(e->type) {
411     case FocusIn:
412         if (!wanted_focusevent(e))
413             return TRUE;
414         break;
415     case FocusOut:
416         if (!wanted_focusevent(e))
417             return TRUE;
418         break;
419     }
420     return FALSE;
421 }
422
423 static void event_process(const XEvent *ec, gpointer data)
424 {
425     Window window;
426     ObGroup *group = NULL;
427     ObClient *client = NULL;
428     ObDock *dock = NULL;
429     ObDockApp *dockapp = NULL;
430     ObWindow *obwin = NULL;
431     XEvent ee, *e;
432     ObEventData *ed = data;
433
434     /* make a copy we can mangle */
435     ee = *ec;
436     e = &ee;
437
438     window = event_get_window(e);
439     if (!(e->type == PropertyNotify &&
440           (group = g_hash_table_lookup(group_map, &window))))
441         if ((obwin = g_hash_table_lookup(window_map, &window))) {
442             switch (obwin->type) {
443             case Window_Dock:
444                 dock = WINDOW_AS_DOCK(obwin);
445                 break;
446             case Window_DockApp:
447                 dockapp = WINDOW_AS_DOCKAPP(obwin);
448                 break;
449             case Window_Client:
450                 client = WINDOW_AS_CLIENT(obwin);
451                 break;
452             case Window_Menu:
453             case Window_Internal:
454                 /* not to be used for events */
455                 g_assert_not_reached();
456                 break;
457             }
458         }
459
460     event_set_curtime(e);
461     event_hack_mods(e);
462     if (event_ignore(e, client)) {
463         if (ed)
464             ed->ignored = TRUE;
465         return;
466     } else if (ed)
467             ed->ignored = FALSE;
468
469     /* deal with it in the kernel */
470
471     if (menu_frame_visible &&
472         (e->type == EnterNotify || e->type == LeaveNotify))
473     {
474         /* crossing events for menu */
475         event_handle_menu(e);
476     } else if (e->type == FocusIn) {
477         if (client && client != focus_client) {
478             frame_adjust_focus(client->frame, TRUE);
479             focus_set_client(client);
480             client_calc_layer(client);
481         }
482     } else if (e->type == FocusOut) {
483         gboolean nomove = FALSE;
484         XEvent ce;
485
486         ob_debug_type(OB_DEBUG_FOCUS, "FocusOut Event\n");
487
488         /* Look for the followup FocusIn */
489         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
490             /* There is no FocusIn, this means focus went to a window that
491                is not being managed, or a window on another screen. */
492             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a black hole !\n");
493             /* nothing is focused */
494             focus_set_client(NULL);
495         } else if (ce.xany.window == e->xany.window) {
496             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
497             /* If focus didn't actually move anywhere, there is nothing to do*/
498             nomove = TRUE;
499         } else if (ce.xfocus.detail == NotifyPointerRoot ||
500                    ce.xfocus.detail == NotifyDetailNone ||
501                    ce.xfocus.detail == NotifyInferior) {
502             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to root\n");
503             /* Focus has been reverted to the root window or nothing
504                FocusOut events come after UnmapNotify, so we don't need to
505                worry about focusing an invalid window
506              */
507             focus_fallback(TRUE);
508         } else {
509             /* Focus did move, so process the FocusIn event */
510             ObEventData ed = { .ignored = FALSE };
511             event_process(&ce, &ed);
512             if (ed.ignored) {
513                 /* The FocusIn was ignored, this means it was on a window
514                    that isn't a client. */
515                 ob_debug_type(OB_DEBUG_FOCUS,
516                               "Focus went to an unmanaged window 0x%x !\n",
517                               ce.xfocus.window);
518                 focus_fallback(TRUE);
519             }
520         }
521
522         if (client && !nomove) {
523             frame_adjust_focus(client->frame, FALSE);
524             /* focus_set_client has already been called for sure */
525             client_calc_layer(client);
526         }
527     } else if (group)
528         event_handle_group(group, e);
529     else if (client)
530         event_handle_client(client, e);
531     else if (dockapp)
532         event_handle_dockapp(dockapp, e);
533     else if (dock)
534         event_handle_dock(dock, e);
535     else if (window == RootWindow(ob_display, ob_screen))
536         event_handle_root(e);
537     else if (e->type == MapRequest)
538         client_manage(window);
539     else if (e->type == ConfigureRequest) {
540         /* unhandled configure requests must be used to configure the
541            window directly */
542         XWindowChanges xwc;
543
544         xwc.x = e->xconfigurerequest.x;
545         xwc.y = e->xconfigurerequest.y;
546         xwc.width = e->xconfigurerequest.width;
547         xwc.height = e->xconfigurerequest.height;
548         xwc.border_width = e->xconfigurerequest.border_width;
549         xwc.sibling = e->xconfigurerequest.above;
550         xwc.stack_mode = e->xconfigurerequest.detail;
551        
552         /* we are not to be held responsible if someone sends us an
553            invalid request! */
554         xerror_set_ignore(TRUE);
555         XConfigureWindow(ob_display, window,
556                          e->xconfigurerequest.value_mask, &xwc);
557         xerror_set_ignore(FALSE);
558     }
559 #ifdef SYNC
560     else if (extensions_sync &&
561         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
562     {
563         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
564         if (se->alarm == moveresize_alarm && moveresize_in_progress)
565             moveresize_event(e);
566     }
567 #endif
568
569     /* user input (action-bound) events */
570     if (e->type == ButtonPress || e->type == ButtonRelease ||
571         e->type == MotionNotify || e->type == KeyPress ||
572         e->type == KeyRelease)
573     {
574         if (menu_frame_visible)
575             event_handle_menu(e);
576         else {
577             if (!keyboard_process_interactive_grab(e, &client)) {
578                 if (moveresize_in_progress) {
579                     moveresize_event(e);
580
581                     /* make further actions work on the client being
582                        moved/resized */
583                     client = moveresize_client;
584                 }
585
586                 menu_can_hide = FALSE;
587                 ob_main_loop_timeout_add(ob_main_loop,
588                                          config_menu_hide_delay * 1000,
589                                          menu_hide_delay_func,
590                                          NULL, g_direct_equal, NULL);
591
592                 if (e->type == ButtonPress || e->type == ButtonRelease ||
593                     e->type == MotionNotify) {
594                     mouse_event(client, e);
595                 } else if (e->type == KeyPress) {
596                     keyboard_event((focus_cycle_target ? focus_cycle_target :
597                                     (client ? client : focus_client)), e);
598                 }
599             }
600         }
601     }
602     /* if something happens and it's not from an XEvent, then we don't know
603        the time */
604     event_curtime = CurrentTime;
605 }
606
607 static void event_handle_root(XEvent *e)
608 {
609     Atom msgtype;
610      
611     switch(e->type) {
612     case SelectionClear:
613         ob_debug("Another WM has requested to replace us. Exiting.\n");
614         ob_exit_replace();
615         break;
616
617     case ClientMessage:
618         if (e->xclient.format != 32) break;
619
620         msgtype = e->xclient.message_type;
621         if (msgtype == prop_atoms.net_current_desktop) {
622             guint d = e->xclient.data.l[0];
623             if (d < screen_num_desktops) {
624                 event_curtime = e->xclient.data.l[1];
625                 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime);
626                 screen_set_desktop(d);
627             }
628         } else if (msgtype == prop_atoms.net_number_of_desktops) {
629             guint d = e->xclient.data.l[0];
630             if (d > 0)
631                 screen_set_num_desktops(d);
632         } else if (msgtype == prop_atoms.net_showing_desktop) {
633             screen_show_desktop(e->xclient.data.l[0] != 0);
634         } else if (msgtype == prop_atoms.ob_control) {
635             if (e->xclient.data.l[0] == 1)
636                 ob_reconfigure();
637             else if (e->xclient.data.l[0] == 2)
638                 ob_restart();
639         }
640         break;
641     case PropertyNotify:
642         if (e->xproperty.atom == prop_atoms.net_desktop_names)
643             screen_update_desktop_names();
644         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
645             screen_update_layout();
646         break;
647     case ConfigureNotify:
648 #ifdef XRANDR
649         XRRUpdateConfiguration(e);
650 #endif
651         screen_resize();
652         break;
653     default:
654         ;
655     }
656 }
657
658 static void event_handle_group(ObGroup *group, XEvent *e)
659 {
660     GSList *it;
661
662     g_assert(e->type == PropertyNotify);
663
664     for (it = group->members; it; it = g_slist_next(it))
665         event_handle_client(it->data, e);
666 }
667
668 void event_enter_client(ObClient *client)
669 {
670     g_assert(config_focus_follow);
671
672     if (client_normal(client) && client_can_focus(client)) {
673         if (config_focus_delay) {
674             ObFocusDelayData *data;
675
676             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
677
678             data = g_new(ObFocusDelayData, 1);
679             data->client = client;
680             data->time = event_curtime;
681
682             ob_main_loop_timeout_add(ob_main_loop,
683                                      config_focus_delay,
684                                      focus_delay_func,
685                                      data, focus_delay_cmp, focus_delay_dest);
686         } else {
687             ObFocusDelayData data;
688             data.client = client;
689             data.time = event_curtime;
690             focus_delay_func(&data);
691         }
692     }
693 }
694
695 static void event_handle_client(ObClient *client, XEvent *e)
696 {
697     XEvent ce;
698     Atom msgtype;
699     gint i=0;
700     ObFrameContext con;
701      
702     switch (e->type) {
703     case ButtonPress:
704     case ButtonRelease:
705         /* Wheel buttons don't draw because they are an instant click, so it
706            is a waste of resources to go drawing it. */
707         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
708             con = frame_context(client, e->xbutton.window);
709             con = mouse_button_frame_context(con, e->xbutton.button);
710             switch (con) {
711             case OB_FRAME_CONTEXT_MAXIMIZE:
712                 client->frame->max_press = (e->type == ButtonPress);
713                 framerender_frame(client->frame);
714                 break;
715             case OB_FRAME_CONTEXT_CLOSE:
716                 client->frame->close_press = (e->type == ButtonPress);
717                 framerender_frame(client->frame);
718                 break;
719             case OB_FRAME_CONTEXT_ICONIFY:
720                 client->frame->iconify_press = (e->type == ButtonPress);
721                 framerender_frame(client->frame);
722                 break;
723             case OB_FRAME_CONTEXT_ALLDESKTOPS:
724                 client->frame->desk_press = (e->type == ButtonPress);
725                 framerender_frame(client->frame);
726                 break; 
727             case OB_FRAME_CONTEXT_SHADE:
728                 client->frame->shade_press = (e->type == ButtonPress);
729                 framerender_frame(client->frame);
730                 break;
731             default:
732                 /* nothing changes with clicks for any other contexts */
733                 break;
734             }
735         }
736         break;
737     case LeaveNotify:
738         con = frame_context(client, e->xcrossing.window);
739         switch (con) {
740         case OB_FRAME_CONTEXT_MAXIMIZE:
741             client->frame->max_hover = FALSE;
742             frame_adjust_state(client->frame);
743             break;
744         case OB_FRAME_CONTEXT_ALLDESKTOPS:
745             client->frame->desk_hover = FALSE;
746             frame_adjust_state(client->frame);
747             break;
748         case OB_FRAME_CONTEXT_SHADE:
749             client->frame->shade_hover = FALSE;
750             frame_adjust_state(client->frame);
751             break;
752         case OB_FRAME_CONTEXT_ICONIFY:
753             client->frame->iconify_hover = FALSE;
754             frame_adjust_state(client->frame);
755             break;
756         case OB_FRAME_CONTEXT_CLOSE:
757             client->frame->close_hover = FALSE;
758             frame_adjust_state(client->frame);
759             break;
760         case OB_FRAME_CONTEXT_FRAME:
761             ob_debug_type(OB_DEBUG_FOCUS,
762                           "%sNotify mode %d detail %d on %lx\n",
763                           (e->type == EnterNotify ? "Enter" : "Leave"),
764                           e->xcrossing.mode,
765                           e->xcrossing.detail, (client?client->window:0));
766             if (keyboard_interactively_grabbed())
767                 break;
768             if (config_focus_follow && config_focus_delay &&
769                 /* leave inferior events can happen when the mouse goes onto
770                    the window's border and then into the window before the
771                    delay is up */
772                 e->xcrossing.detail != NotifyInferior)
773             {
774                 ob_main_loop_timeout_remove_data(ob_main_loop,
775                                                  focus_delay_func,
776                                                  client, FALSE);
777             }
778             break;
779         default:
780             break;
781         }
782         break;
783     case EnterNotify:
784     {
785         gboolean nofocus = FALSE;
786
787         if (ignore_enter_focus) {
788             ignore_enter_focus--;
789             nofocus = TRUE;
790         }
791
792         con = frame_context(client, e->xcrossing.window);
793         switch (con) {
794         case OB_FRAME_CONTEXT_MAXIMIZE:
795             client->frame->max_hover = TRUE;
796             frame_adjust_state(client->frame);
797             break;
798         case OB_FRAME_CONTEXT_ALLDESKTOPS:
799             client->frame->desk_hover = TRUE;
800             frame_adjust_state(client->frame);
801             break;
802         case OB_FRAME_CONTEXT_SHADE:
803             client->frame->shade_hover = TRUE;
804             frame_adjust_state(client->frame);
805             break;
806         case OB_FRAME_CONTEXT_ICONIFY:
807             client->frame->iconify_hover = TRUE;
808             frame_adjust_state(client->frame);
809             break;
810         case OB_FRAME_CONTEXT_CLOSE:
811             client->frame->close_hover = TRUE;
812             frame_adjust_state(client->frame);
813             break;
814         case OB_FRAME_CONTEXT_FRAME:
815             if (keyboard_interactively_grabbed())
816                 break;
817             if (e->xcrossing.mode == NotifyGrab ||
818                 e->xcrossing.mode == NotifyUngrab ||
819                 /*ignore enters when we're already in the window */
820                 e->xcrossing.detail == NotifyInferior)
821             {
822                 ob_debug_type(OB_DEBUG_FOCUS,
823                               "%sNotify mode %d detail %d on %lx IGNORED\n",
824                               (e->type == EnterNotify ? "Enter" : "Leave"),
825                               e->xcrossing.mode,
826                               e->xcrossing.detail, client?client->window:0);
827             } else {
828                 ob_debug_type(OB_DEBUG_FOCUS,
829                               "%sNotify mode %d detail %d on %lx, "
830                               "focusing window: %d\n",
831                               (e->type == EnterNotify ? "Enter" : "Leave"),
832                               e->xcrossing.mode,
833                               e->xcrossing.detail, (client?client->window:0),
834                               !nofocus);
835                 if (!nofocus && config_focus_follow)
836                     event_enter_client(client);
837             }
838             break;
839         default:
840             break;
841         }
842         break;
843     }
844     case ConfigureRequest:
845         /* compress these */
846         while (XCheckTypedWindowEvent(ob_display, client->window,
847                                       ConfigureRequest, &ce)) {
848             ++i;
849             /* XXX if this causes bad things.. we can compress config req's
850                with the same mask. */
851             e->xconfigurerequest.value_mask |=
852                 ce.xconfigurerequest.value_mask;
853             if (ce.xconfigurerequest.value_mask & CWX)
854                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
855             if (ce.xconfigurerequest.value_mask & CWY)
856                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
857             if (ce.xconfigurerequest.value_mask & CWWidth)
858                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
859             if (ce.xconfigurerequest.value_mask & CWHeight)
860                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
861             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
862                 e->xconfigurerequest.border_width =
863                     ce.xconfigurerequest.border_width;
864             if (ce.xconfigurerequest.value_mask & CWStackMode)
865                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
866         }
867
868         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
869         if (client->iconic || client->shaded) return;
870
871         /* resize, then move, as specified in the EWMH section 7.7 */
872         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
873                                                CWX | CWY |
874                                                CWBorderWidth)) {
875             gint x, y, w, h;
876
877             if (e->xconfigurerequest.value_mask & CWBorderWidth)
878                 client->border_width = e->xconfigurerequest.border_width;
879
880             x = (e->xconfigurerequest.value_mask & CWX) ?
881                 e->xconfigurerequest.x : client->area.x;
882             y = (e->xconfigurerequest.value_mask & CWY) ?
883                 e->xconfigurerequest.y : client->area.y;
884             w = (e->xconfigurerequest.value_mask & CWWidth) ?
885                 e->xconfigurerequest.width : client->area.width;
886             h = (e->xconfigurerequest.value_mask & CWHeight) ?
887                 e->xconfigurerequest.height : client->area.height;
888
889             client_find_onscreen(client, &x, &y, w, h, FALSE);
890             client_configure_full(client, x, y, w, h, FALSE, TRUE, TRUE);
891         }
892
893         if (e->xconfigurerequest.value_mask & CWStackMode) {
894             switch (e->xconfigurerequest.detail) {
895             case Below:
896             case BottomIf:
897                 /* Apps are so rude. And this is totally disconnected from
898                    activation/focus. Bleh. */
899                 /*client_lower(client);*/
900                 break;
901
902             case Above:
903             case TopIf:
904             default:
905                 /* Apps are so rude. And this is totally disconnected from
906                    activation/focus. Bleh. */
907                 /*client_raise(client);*/
908                 break;
909             }
910         }
911         break;
912     case UnmapNotify:
913         if (client->ignore_unmaps) {
914             client->ignore_unmaps--;
915             break;
916         }
917         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
918                  "ignores left %d\n",
919                  client->window, e->xunmap.event, e->xunmap.from_configure,
920                  client->ignore_unmaps);
921         client_unmanage(client);
922         break;
923     case DestroyNotify:
924         ob_debug("DestroyNotify for window 0x%x\n", client->window);
925         client_unmanage(client);
926         break;
927     case ReparentNotify:
928         /* this is when the client is first taken captive in the frame */
929         if (e->xreparent.parent == client->frame->plate) break;
930
931         /*
932           This event is quite rare and is usually handled in unmapHandler.
933           However, if the window is unmapped when the reparent event occurs,
934           the window manager never sees it because an unmap event is not sent
935           to an already unmapped window.
936         */
937
938         /* we don't want the reparent event, put it back on the stack for the
939            X server to deal with after we unmanage the window */
940         XPutBackEvent(ob_display, e);
941      
942         ob_debug("ReparentNotify for window 0x%x\n", client->window);
943         client_unmanage(client);
944         break;
945     case MapRequest:
946         ob_debug("MapRequest for 0x%lx\n", client->window);
947         if (!client->iconic) break; /* this normally doesn't happen, but if it
948                                        does, we don't want it!
949                                        it can happen now when the window is on
950                                        another desktop, but we still don't
951                                        want it! */
952         client_activate(client, FALSE, TRUE);
953         break;
954     case ClientMessage:
955         /* validate cuz we query stuff off the client here */
956         if (!client_validate(client)) break;
957
958         if (e->xclient.format != 32) return;
959
960         msgtype = e->xclient.message_type;
961         if (msgtype == prop_atoms.wm_change_state) {
962             /* compress changes into a single change */
963             while (XCheckTypedWindowEvent(ob_display, client->window,
964                                           e->type, &ce)) {
965                 /* XXX: it would be nice to compress ALL messages of a
966                    type, not just messages in a row without other
967                    message types between. */
968                 if (ce.xclient.message_type != msgtype) {
969                     XPutBackEvent(ob_display, &ce);
970                     break;
971                 }
972                 e->xclient = ce.xclient;
973             }
974             client_set_wm_state(client, e->xclient.data.l[0]);
975         } else if (msgtype == prop_atoms.net_wm_desktop) {
976             /* compress changes into a single change */
977             while (XCheckTypedWindowEvent(ob_display, client->window,
978                                           e->type, &ce)) {
979                 /* XXX: it would be nice to compress ALL messages of a
980                    type, not just messages in a row without other
981                    message types between. */
982                 if (ce.xclient.message_type != msgtype) {
983                     XPutBackEvent(ob_display, &ce);
984                     break;
985                 }
986                 e->xclient = ce.xclient;
987             }
988             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
989                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
990                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
991                                    FALSE);
992         } else if (msgtype == prop_atoms.net_wm_state) {
993             /* can't compress these */
994             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
995                      (e->xclient.data.l[0] == 0 ? "Remove" :
996                       e->xclient.data.l[0] == 1 ? "Add" :
997                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
998                      e->xclient.data.l[1], e->xclient.data.l[2],
999                      client->window);
1000             client_set_state(client, e->xclient.data.l[0],
1001                              e->xclient.data.l[1], e->xclient.data.l[2]);
1002         } else if (msgtype == prop_atoms.net_close_window) {
1003             ob_debug("net_close_window for 0x%lx\n", client->window);
1004             client_close(client);
1005         } else if (msgtype == prop_atoms.net_active_window) {
1006             ob_debug("net_active_window for 0x%lx source=%s\n",
1007                      client->window,
1008                      (e->xclient.data.l[0] == 0 ? "unknown" :
1009                       (e->xclient.data.l[0] == 1 ? "application" :
1010                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1011             /* XXX make use of data.l[2] ! */
1012             event_curtime = e->xclient.data.l[1];
1013             client_activate(client, FALSE,
1014                             (e->xclient.data.l[0] == 0 ||
1015                              e->xclient.data.l[0] == 2));
1016         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1017             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1018                      client->window, e->xclient.data.l[2]);
1019             if ((Atom)e->xclient.data.l[2] ==
1020                 prop_atoms.net_wm_moveresize_size_topleft ||
1021                 (Atom)e->xclient.data.l[2] ==
1022                 prop_atoms.net_wm_moveresize_size_top ||
1023                 (Atom)e->xclient.data.l[2] ==
1024                 prop_atoms.net_wm_moveresize_size_topright ||
1025                 (Atom)e->xclient.data.l[2] ==
1026                 prop_atoms.net_wm_moveresize_size_right ||
1027                 (Atom)e->xclient.data.l[2] ==
1028                 prop_atoms.net_wm_moveresize_size_right ||
1029                 (Atom)e->xclient.data.l[2] ==
1030                 prop_atoms.net_wm_moveresize_size_bottomright ||
1031                 (Atom)e->xclient.data.l[2] ==
1032                 prop_atoms.net_wm_moveresize_size_bottom ||
1033                 (Atom)e->xclient.data.l[2] ==
1034                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1035                 (Atom)e->xclient.data.l[2] ==
1036                 prop_atoms.net_wm_moveresize_size_left ||
1037                 (Atom)e->xclient.data.l[2] ==
1038                 prop_atoms.net_wm_moveresize_move ||
1039                 (Atom)e->xclient.data.l[2] ==
1040                 prop_atoms.net_wm_moveresize_size_keyboard ||
1041                 (Atom)e->xclient.data.l[2] ==
1042                 prop_atoms.net_wm_moveresize_move_keyboard) {
1043
1044                 moveresize_start(client, e->xclient.data.l[0],
1045                                  e->xclient.data.l[1], e->xclient.data.l[3],
1046                                  e->xclient.data.l[2]);
1047             }
1048             else if ((Atom)e->xclient.data.l[2] ==
1049                      prop_atoms.net_wm_moveresize_cancel)
1050                 moveresize_end(TRUE);
1051         } else if (msgtype == prop_atoms.net_moveresize_window) {
1052             gint grav, x, y, w, h;
1053
1054             if (e->xclient.data.l[0] & 0xff)
1055                 grav = e->xclient.data.l[0] & 0xff;
1056             else 
1057                 grav = client->gravity;
1058
1059             if (e->xclient.data.l[0] & 1 << 8)
1060                 x = e->xclient.data.l[1];
1061             else
1062                 x = client->area.x;
1063             if (e->xclient.data.l[0] & 1 << 9)
1064                 y = e->xclient.data.l[2];
1065             else
1066                 y = client->area.y;
1067             if (e->xclient.data.l[0] & 1 << 10)
1068                 w = e->xclient.data.l[3];
1069             else
1070                 w = client->area.width;
1071             if (e->xclient.data.l[0] & 1 << 11)
1072                 h = e->xclient.data.l[4];
1073             else
1074                 h = client->area.height;
1075
1076             client_convert_gravity(client, grav, &x, &y, w, h);
1077             client_find_onscreen(client, &x, &y, w, h, FALSE);
1078             client_configure(client, x, y, w, h, FALSE, TRUE);
1079         }
1080         break;
1081     case PropertyNotify:
1082         /* validate cuz we query stuff off the client here */
1083         if (!client_validate(client)) break;
1084   
1085         /* compress changes to a single property into a single change */
1086         while (XCheckTypedWindowEvent(ob_display, client->window,
1087                                       e->type, &ce)) {
1088             Atom a, b;
1089
1090             /* XXX: it would be nice to compress ALL changes to a property,
1091                not just changes in a row without other props between. */
1092
1093             a = ce.xproperty.atom;
1094             b = e->xproperty.atom;
1095
1096             if (a == b)
1097                 continue;
1098             if ((a == prop_atoms.net_wm_name ||
1099                  a == prop_atoms.wm_name ||
1100                  a == prop_atoms.net_wm_icon_name ||
1101                  a == prop_atoms.wm_icon_name)
1102                 &&
1103                 (b == prop_atoms.net_wm_name ||
1104                  b == prop_atoms.wm_name ||
1105                  b == prop_atoms.net_wm_icon_name ||
1106                  b == prop_atoms.wm_icon_name)) {
1107                 continue;
1108             }
1109             if (a == prop_atoms.net_wm_icon &&
1110                 b == prop_atoms.net_wm_icon)
1111                 continue;
1112
1113             XPutBackEvent(ob_display, &ce);
1114             break;
1115         }
1116
1117         msgtype = e->xproperty.atom;
1118         if (msgtype == XA_WM_NORMAL_HINTS) {
1119             client_update_normal_hints(client);
1120             /* normal hints can make a window non-resizable */
1121             client_setup_decor_and_functions(client);
1122         } else if (msgtype == XA_WM_HINTS) {
1123             client_update_wmhints(client);
1124         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1125             client_update_transient_for(client);
1126             client_get_type(client);
1127             /* type may have changed, so update the layer */
1128             client_calc_layer(client);
1129             client_setup_decor_and_functions(client);
1130         } else if (msgtype == prop_atoms.net_wm_name ||
1131                    msgtype == prop_atoms.wm_name ||
1132                    msgtype == prop_atoms.net_wm_icon_name ||
1133                    msgtype == prop_atoms.wm_icon_name) {
1134             client_update_title(client);
1135         } else if (msgtype == prop_atoms.wm_class) {
1136             client_update_class(client);
1137         } else if (msgtype == prop_atoms.wm_protocols) {
1138             client_update_protocols(client);
1139             client_setup_decor_and_functions(client);
1140         }
1141         else if (msgtype == prop_atoms.net_wm_strut) {
1142             client_update_strut(client);
1143         }
1144         else if (msgtype == prop_atoms.net_wm_icon) {
1145             client_update_icons(client);
1146         }
1147         else if (msgtype == prop_atoms.net_wm_user_time) {
1148             client_update_user_time(client);
1149         }
1150 #ifdef SYNC
1151         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1152             client_update_sync_request_counter(client);
1153         }
1154 #endif
1155         else if (msgtype == prop_atoms.sm_client_id) {
1156             client_update_sm_client_id(client);
1157         }
1158     case ColormapNotify:
1159         client_update_colormap(client, e->xcolormap.colormap);
1160         break;
1161     default:
1162         ;
1163 #ifdef SHAPE
1164         if (extensions_shape && e->type == extensions_shape_event_basep) {
1165             client->shaped = ((XShapeEvent*)e)->shaped;
1166             frame_adjust_shape(client->frame);
1167         }
1168 #endif
1169     }
1170 }
1171
1172 static void event_handle_dock(ObDock *s, XEvent *e)
1173 {
1174     switch (e->type) {
1175     case ButtonPress:
1176         if (e->xbutton.button == 1)
1177             stacking_raise(DOCK_AS_WINDOW(s));
1178         else if (e->xbutton.button == 2)
1179             stacking_lower(DOCK_AS_WINDOW(s));
1180         break;
1181     case EnterNotify:
1182         dock_hide(FALSE);
1183         break;
1184     case LeaveNotify:
1185         dock_hide(TRUE);
1186         break;
1187     }
1188 }
1189
1190 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1191 {
1192     switch (e->type) {
1193     case MotionNotify:
1194         dock_app_drag(app, &e->xmotion);
1195         break;
1196     case UnmapNotify:
1197         if (app->ignore_unmaps) {
1198             app->ignore_unmaps--;
1199             break;
1200         }
1201         dock_remove(app, TRUE);
1202         break;
1203     case DestroyNotify:
1204         dock_remove(app, FALSE);
1205         break;
1206     case ReparentNotify:
1207         dock_remove(app, FALSE);
1208         break;
1209     case ConfigureNotify:
1210         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1211         break;
1212     }
1213 }
1214
1215 static ObMenuFrame* find_active_menu()
1216 {
1217     GList *it;
1218     ObMenuFrame *ret = NULL;
1219
1220     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1221         ret = it->data;
1222         if (ret->selected)
1223             break;
1224         ret = NULL;
1225     }
1226     return ret;
1227 }
1228
1229 static ObMenuFrame* find_active_or_last_menu()
1230 {
1231     ObMenuFrame *ret = NULL;
1232
1233     ret = find_active_menu();
1234     if (!ret && menu_frame_visible)
1235         ret = menu_frame_visible->data;
1236     return ret;
1237 }
1238
1239 static void event_handle_menu_shortcut(XEvent *ev)
1240 {
1241     gunichar unikey = 0;
1242     ObMenuFrame *frame;
1243     GList *start;
1244     GList *it;
1245     ObMenuEntryFrame *found = NULL;
1246     guint num_found = 0;
1247
1248     {
1249         const char *key;
1250         if ((key = translate_keycode(ev->xkey.keycode)) == NULL)
1251             return;
1252         /* don't accept keys that aren't a single letter, like "space" */
1253         if (key[1] != '\0')
1254             return;
1255         unikey = g_utf8_get_char_validated(key, -1);
1256         if (unikey == (gunichar)-1 || unikey == (gunichar)-2 || unikey == 0)
1257             return;
1258     }
1259
1260     if ((frame = find_active_or_last_menu()) == NULL)
1261         return;
1262
1263
1264     if (!frame->entries)
1265         return; /* nothing in the menu anyways */
1266
1267     /* start after the selected one */
1268     start = frame->entries;
1269     if (frame->selected) {
1270         for (it = start; frame->selected != it->data; it = g_list_next(it))
1271             g_assert(it != NULL); /* nothing was selected? */
1272         /* next with wraparound */
1273         start = g_list_next(it);
1274         if (start == NULL) start = frame->entries;
1275     }
1276
1277     it = start;
1278     do {
1279         ObMenuEntryFrame *e = it->data;
1280         gunichar entrykey = 0;
1281
1282         if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1283             e->entry->data.normal.enabled)
1284             entrykey = e->entry->data.normal.shortcut;
1285         else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1286             entrykey = e->entry->data.submenu.submenu->shortcut;
1287
1288         if (unikey == entrykey) {
1289             if (found == NULL) found = e;
1290             ++num_found;
1291         }
1292
1293         /* next with wraparound */
1294         it = g_list_next(it);
1295         if (it == NULL) it = frame->entries;
1296     } while (it != start);
1297
1298     if (found) {
1299         if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1300             num_found == 1)
1301         {
1302             menu_frame_select(frame, found, TRUE);
1303             usleep(50000);
1304             menu_entry_frame_execute(found, ev->xkey.state,
1305                                      ev->xkey.time);
1306         } else {
1307             menu_frame_select(frame, found, TRUE);
1308             if (num_found == 1)
1309                 menu_frame_select_next(frame->child);
1310         }
1311     }
1312 }
1313
1314 static void event_handle_menu(XEvent *ev)
1315 {
1316     ObMenuFrame *f;
1317     ObMenuEntryFrame *e;
1318
1319     switch (ev->type) {
1320     case ButtonRelease:
1321         if (menu_can_hide) {
1322             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1323                                             ev->xbutton.y_root)))
1324                 menu_entry_frame_execute(e, ev->xbutton.state,
1325                                          ev->xbutton.time);
1326             else
1327                 menu_frame_hide_all();
1328         }
1329         break;
1330     case EnterNotify:
1331         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1332             if (e->ignore_enters)
1333                 --e->ignore_enters;
1334             else
1335                 menu_frame_select(e->frame, e, FALSE);
1336         }
1337         break;
1338     case LeaveNotify:
1339         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1340             (f = find_active_menu()) && f->selected == e &&
1341             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1342         {
1343             menu_frame_select(e->frame, NULL, FALSE);
1344         }
1345     case MotionNotify:   
1346         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1347                                         ev->xmotion.y_root)))
1348             menu_frame_select(e->frame, e, FALSE);
1349         break;
1350     case KeyPress:
1351         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1352             if ((f = find_active_or_last_menu()) && f->parent)
1353                 menu_frame_select(f, NULL, TRUE);
1354             else
1355                 menu_frame_hide_all();
1356         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1357             ObMenuFrame *f;
1358             if ((f = find_active_menu())) {
1359                 if (f->child)
1360                     menu_frame_select_next(f->child);
1361                 else
1362                     menu_entry_frame_execute(f->selected, ev->xkey.state,
1363                                              ev->xkey.time);
1364             }
1365         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1366             ObMenuFrame *f;
1367             if ((f = find_active_or_last_menu()))
1368                 menu_frame_select(f, NULL, TRUE);
1369         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1370             ObMenuFrame *f;
1371             if ((f = find_active_menu()) && f->child)
1372                 menu_frame_select_next(f->child);
1373         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1374             ObMenuFrame *f;
1375             if ((f = find_active_or_last_menu()))
1376                 menu_frame_select_previous(f);
1377         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1378             ObMenuFrame *f;
1379             if ((f = find_active_or_last_menu()))
1380                 menu_frame_select_next(f);
1381         } else
1382             event_handle_menu_shortcut(ev);
1383         break;
1384     }
1385 }
1386
1387 static gboolean menu_hide_delay_func(gpointer data)
1388 {
1389     menu_can_hide = TRUE;
1390     return FALSE; /* no repeat */
1391 }
1392
1393 static void focus_delay_dest(gpointer data)
1394 {
1395     g_free(data);
1396 }
1397
1398 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1399 {
1400     const ObFocusDelayData *f1 = d1;
1401     return f1->client == d2;
1402 }
1403
1404 static gboolean focus_delay_func(gpointer data)
1405 {
1406     ObFocusDelayData *d = data;
1407     Time old = event_curtime;
1408
1409     event_curtime = d->time;
1410     if (focus_client != d->client) {
1411         if (client_focus(d->client) && config_focus_raise)
1412             client_raise(d->client);
1413     }
1414     event_curtime = old;
1415     return FALSE; /* no repeat */
1416 }
1417
1418 static void focus_delay_client_dest(ObClient *client, gpointer data)
1419 {
1420     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1421                                      client, FALSE);
1422 }
1423
1424 void event_halt_focus_delay()
1425 {
1426     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1427 }
1428
1429 void event_ignore_queued_enters()
1430 {
1431     GSList *saved = NULL, *it;
1432     XEvent *e;
1433                 
1434     XSync(ob_display, FALSE);
1435
1436     /* count the events */
1437     while (TRUE) {
1438         e = g_new(XEvent, 1);
1439         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1440             ObWindow *win;
1441             
1442             win = g_hash_table_lookup(window_map, &e->xany.window);
1443             if (win && WINDOW_IS_CLIENT(win))
1444                 ++ignore_enter_focus;
1445             
1446             saved = g_slist_append(saved, e);
1447         } else {
1448             g_free(e);
1449             break;
1450         }
1451     }
1452     /* put the events back */
1453     for (it = saved; it; it = g_slist_next(it)) {
1454         XPutBackEvent(ob_display, it->data);
1455         g_free(it->data);
1456     }
1457     g_slist_free(saved);
1458 }
1459
1460 gboolean event_time_after(Time t1, Time t2)
1461 {
1462     g_assert(t1 != CurrentTime);
1463     g_assert(t2 != CurrentTime);
1464
1465     /*
1466       Timestamp values wrap around (after about 49.7 days). The server, given
1467       its current time is represented by timestamp T, always interprets
1468       timestamps from clients by treating half of the timestamp space as being
1469       later in time than T.
1470       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1471     */
1472
1473     /* TIME_HALF is half of the number space of a Time type variable */
1474 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1475
1476     if (t2 >= TIME_HALF)
1477         /* t2 is in the second half so t1 might wrap around and be smaller than
1478            t2 */
1479         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1480     else
1481         /* t2 is in the first half so t1 has to come after it */
1482         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1483 }