dont go to disabled menu entries with keyboard shortcuts
[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             ObCorner corner;
877
878             if (e->xconfigurerequest.value_mask & CWBorderWidth)
879                 client->border_width = e->xconfigurerequest.border_width;
880
881             x = (e->xconfigurerequest.value_mask & CWX) ?
882                 e->xconfigurerequest.x : client->area.x;
883             y = (e->xconfigurerequest.value_mask & CWY) ?
884                 e->xconfigurerequest.y : client->area.y;
885             w = (e->xconfigurerequest.value_mask & CWWidth) ?
886                 e->xconfigurerequest.width : client->area.width;
887             h = (e->xconfigurerequest.value_mask & CWHeight) ?
888                 e->xconfigurerequest.height : client->area.height;
889
890             {
891                 gint newx = x;
892                 gint newy = y;
893                 gint fw = w +
894                      client->frame->size.left + client->frame->size.right;
895                 gint fh = h +
896                      client->frame->size.top + client->frame->size.bottom;
897                 /* make this rude for size-only changes but not for position
898                    changes.. */
899                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
900                                    (e->xconfigurerequest.value_mask & CWY));
901
902                 client_find_onscreen(client, &newx, &newy, fw, fh,
903                                      !moving);
904                 if (e->xconfigurerequest.value_mask & CWX)
905                     x = newx;
906                 if (e->xconfigurerequest.value_mask & CWY)
907                     y = newy;
908             }
909
910             switch (client->gravity) {
911             case NorthEastGravity:
912             case EastGravity:
913                 corner = OB_CORNER_TOPRIGHT;
914                 break;
915             case SouthWestGravity:
916             case SouthGravity:
917                 corner = OB_CORNER_BOTTOMLEFT;
918                 break;
919             case SouthEastGravity:
920                 corner = OB_CORNER_BOTTOMRIGHT;
921                 break;
922             default:     /* NorthWest, Static, etc */
923                 corner = OB_CORNER_TOPLEFT;
924             }
925
926             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
927                                   TRUE);
928         }
929
930         if (e->xconfigurerequest.value_mask & CWStackMode) {
931             switch (e->xconfigurerequest.detail) {
932             case Below:
933             case BottomIf:
934                 /* Apps are so rude. And this is totally disconnected from
935                    activation/focus. Bleh. */
936                 /*client_lower(client);*/
937                 break;
938
939             case Above:
940             case TopIf:
941             default:
942                 /* Apps are so rude. And this is totally disconnected from
943                    activation/focus. Bleh. */
944                 /*client_raise(client);*/
945                 break;
946             }
947         }
948         break;
949     case UnmapNotify:
950         if (client->ignore_unmaps) {
951             client->ignore_unmaps--;
952             break;
953         }
954         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
955                  "ignores left %d\n",
956                  client->window, e->xunmap.event, e->xunmap.from_configure,
957                  client->ignore_unmaps);
958         client_unmanage(client);
959         break;
960     case DestroyNotify:
961         ob_debug("DestroyNotify for window 0x%x\n", client->window);
962         client_unmanage(client);
963         break;
964     case ReparentNotify:
965         /* this is when the client is first taken captive in the frame */
966         if (e->xreparent.parent == client->frame->plate) break;
967
968         /*
969           This event is quite rare and is usually handled in unmapHandler.
970           However, if the window is unmapped when the reparent event occurs,
971           the window manager never sees it because an unmap event is not sent
972           to an already unmapped window.
973         */
974
975         /* we don't want the reparent event, put it back on the stack for the
976            X server to deal with after we unmanage the window */
977         XPutBackEvent(ob_display, e);
978      
979         ob_debug("ReparentNotify for window 0x%x\n", client->window);
980         client_unmanage(client);
981         break;
982     case MapRequest:
983         ob_debug("MapRequest for 0x%lx\n", client->window);
984         if (!client->iconic) break; /* this normally doesn't happen, but if it
985                                        does, we don't want it!
986                                        it can happen now when the window is on
987                                        another desktop, but we still don't
988                                        want it! */
989         client_activate(client, FALSE, TRUE);
990         break;
991     case ClientMessage:
992         /* validate cuz we query stuff off the client here */
993         if (!client_validate(client)) break;
994
995         if (e->xclient.format != 32) return;
996
997         msgtype = e->xclient.message_type;
998         if (msgtype == prop_atoms.wm_change_state) {
999             /* compress changes into a single change */
1000             while (XCheckTypedWindowEvent(ob_display, client->window,
1001                                           e->type, &ce)) {
1002                 /* XXX: it would be nice to compress ALL messages of a
1003                    type, not just messages in a row without other
1004                    message types between. */
1005                 if (ce.xclient.message_type != msgtype) {
1006                     XPutBackEvent(ob_display, &ce);
1007                     break;
1008                 }
1009                 e->xclient = ce.xclient;
1010             }
1011             client_set_wm_state(client, e->xclient.data.l[0]);
1012         } else if (msgtype == prop_atoms.net_wm_desktop) {
1013             /* compress changes into a single change */
1014             while (XCheckTypedWindowEvent(ob_display, client->window,
1015                                           e->type, &ce)) {
1016                 /* XXX: it would be nice to compress ALL messages of a
1017                    type, not just messages in a row without other
1018                    message types between. */
1019                 if (ce.xclient.message_type != msgtype) {
1020                     XPutBackEvent(ob_display, &ce);
1021                     break;
1022                 }
1023                 e->xclient = ce.xclient;
1024             }
1025             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1026                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1027                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1028                                    FALSE);
1029         } else if (msgtype == prop_atoms.net_wm_state) {
1030             /* can't compress these */
1031             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1032                      (e->xclient.data.l[0] == 0 ? "Remove" :
1033                       e->xclient.data.l[0] == 1 ? "Add" :
1034                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1035                      e->xclient.data.l[1], e->xclient.data.l[2],
1036                      client->window);
1037             client_set_state(client, e->xclient.data.l[0],
1038                              e->xclient.data.l[1], e->xclient.data.l[2]);
1039         } else if (msgtype == prop_atoms.net_close_window) {
1040             ob_debug("net_close_window for 0x%lx\n", client->window);
1041             client_close(client);
1042         } else if (msgtype == prop_atoms.net_active_window) {
1043             ob_debug("net_active_window for 0x%lx source=%s\n",
1044                      client->window,
1045                      (e->xclient.data.l[0] == 0 ? "unknown" :
1046                       (e->xclient.data.l[0] == 1 ? "application" :
1047                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1048             /* XXX make use of data.l[2] ! */
1049             event_curtime = e->xclient.data.l[1];
1050             client_activate(client, FALSE,
1051                             (e->xclient.data.l[0] == 0 ||
1052                              e->xclient.data.l[0] == 2));
1053         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1054             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1055                      client->window, e->xclient.data.l[2]);
1056             if ((Atom)e->xclient.data.l[2] ==
1057                 prop_atoms.net_wm_moveresize_size_topleft ||
1058                 (Atom)e->xclient.data.l[2] ==
1059                 prop_atoms.net_wm_moveresize_size_top ||
1060                 (Atom)e->xclient.data.l[2] ==
1061                 prop_atoms.net_wm_moveresize_size_topright ||
1062                 (Atom)e->xclient.data.l[2] ==
1063                 prop_atoms.net_wm_moveresize_size_right ||
1064                 (Atom)e->xclient.data.l[2] ==
1065                 prop_atoms.net_wm_moveresize_size_right ||
1066                 (Atom)e->xclient.data.l[2] ==
1067                 prop_atoms.net_wm_moveresize_size_bottomright ||
1068                 (Atom)e->xclient.data.l[2] ==
1069                 prop_atoms.net_wm_moveresize_size_bottom ||
1070                 (Atom)e->xclient.data.l[2] ==
1071                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1072                 (Atom)e->xclient.data.l[2] ==
1073                 prop_atoms.net_wm_moveresize_size_left ||
1074                 (Atom)e->xclient.data.l[2] ==
1075                 prop_atoms.net_wm_moveresize_move ||
1076                 (Atom)e->xclient.data.l[2] ==
1077                 prop_atoms.net_wm_moveresize_size_keyboard ||
1078                 (Atom)e->xclient.data.l[2] ==
1079                 prop_atoms.net_wm_moveresize_move_keyboard) {
1080
1081                 moveresize_start(client, e->xclient.data.l[0],
1082                                  e->xclient.data.l[1], e->xclient.data.l[3],
1083                                  e->xclient.data.l[2]);
1084             }
1085             else if ((Atom)e->xclient.data.l[2] ==
1086                      prop_atoms.net_wm_moveresize_cancel)
1087                 moveresize_end(TRUE);
1088         } else if (msgtype == prop_atoms.net_moveresize_window) {
1089             gint oldg = client->gravity;
1090             gint tmpg, x, y, w, h;
1091
1092             if (e->xclient.data.l[0] & 0xff)
1093                 tmpg = e->xclient.data.l[0] & 0xff;
1094             else
1095                 tmpg = oldg;
1096
1097             if (e->xclient.data.l[0] & 1 << 8)
1098                 x = e->xclient.data.l[1];
1099             else
1100                 x = client->area.x;
1101             if (e->xclient.data.l[0] & 1 << 9)
1102                 y = e->xclient.data.l[2];
1103             else
1104                 y = client->area.y;
1105             if (e->xclient.data.l[0] & 1 << 10)
1106                 w = e->xclient.data.l[3];
1107             else
1108                 w = client->area.width;
1109             if (e->xclient.data.l[0] & 1 << 11)
1110                 h = e->xclient.data.l[4];
1111             else
1112                 h = client->area.height;
1113             client->gravity = tmpg;
1114
1115             {
1116                 gint newx = x;
1117                 gint newy = y;
1118                 gint fw = w +
1119                      client->frame->size.left + client->frame->size.right;
1120                 gint fh = h +
1121                      client->frame->size.top + client->frame->size.bottom;
1122                 client_find_onscreen(client, &newx, &newy, fw, fh,
1123                                      client_normal(client));
1124                 if (e->xclient.data.l[0] & 1 << 8)
1125                     x = newx;
1126                 if (e->xclient.data.l[0] & 1 << 9)
1127                     y = newy;
1128             }
1129
1130             client_configure(client, OB_CORNER_TOPLEFT,
1131                              x, y, w, h, FALSE, TRUE);
1132
1133             client->gravity = oldg;
1134         }
1135         break;
1136     case PropertyNotify:
1137         /* validate cuz we query stuff off the client here */
1138         if (!client_validate(client)) break;
1139   
1140         /* compress changes to a single property into a single change */
1141         while (XCheckTypedWindowEvent(ob_display, client->window,
1142                                       e->type, &ce)) {
1143             Atom a, b;
1144
1145             /* XXX: it would be nice to compress ALL changes to a property,
1146                not just changes in a row without other props between. */
1147
1148             a = ce.xproperty.atom;
1149             b = e->xproperty.atom;
1150
1151             if (a == b)
1152                 continue;
1153             if ((a == prop_atoms.net_wm_name ||
1154                  a == prop_atoms.wm_name ||
1155                  a == prop_atoms.net_wm_icon_name ||
1156                  a == prop_atoms.wm_icon_name)
1157                 &&
1158                 (b == prop_atoms.net_wm_name ||
1159                  b == prop_atoms.wm_name ||
1160                  b == prop_atoms.net_wm_icon_name ||
1161                  b == prop_atoms.wm_icon_name)) {
1162                 continue;
1163             }
1164             if (a == prop_atoms.net_wm_icon &&
1165                 b == prop_atoms.net_wm_icon)
1166                 continue;
1167
1168             XPutBackEvent(ob_display, &ce);
1169             break;
1170         }
1171
1172         msgtype = e->xproperty.atom;
1173         if (msgtype == XA_WM_NORMAL_HINTS) {
1174             client_update_normal_hints(client);
1175             /* normal hints can make a window non-resizable */
1176             client_setup_decor_and_functions(client);
1177         } else if (msgtype == XA_WM_HINTS) {
1178             client_update_wmhints(client);
1179         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1180             client_update_transient_for(client);
1181             client_get_type(client);
1182             /* type may have changed, so update the layer */
1183             client_calc_layer(client);
1184             client_setup_decor_and_functions(client);
1185         } else if (msgtype == prop_atoms.net_wm_name ||
1186                    msgtype == prop_atoms.wm_name ||
1187                    msgtype == prop_atoms.net_wm_icon_name ||
1188                    msgtype == prop_atoms.wm_icon_name) {
1189             client_update_title(client);
1190         } else if (msgtype == prop_atoms.wm_class) {
1191             client_update_class(client);
1192         } else if (msgtype == prop_atoms.wm_protocols) {
1193             client_update_protocols(client);
1194             client_setup_decor_and_functions(client);
1195         }
1196         else if (msgtype == prop_atoms.net_wm_strut) {
1197             client_update_strut(client);
1198         }
1199         else if (msgtype == prop_atoms.net_wm_icon) {
1200             client_update_icons(client);
1201         }
1202         else if (msgtype == prop_atoms.net_wm_user_time) {
1203             client_update_user_time(client);
1204         }
1205 #ifdef SYNC
1206         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1207             client_update_sync_request_counter(client);
1208         }
1209 #endif
1210         else if (msgtype == prop_atoms.sm_client_id) {
1211             client_update_sm_client_id(client);
1212         }
1213     case ColormapNotify:
1214         client_update_colormap(client, e->xcolormap.colormap);
1215         break;
1216     default:
1217         ;
1218 #ifdef SHAPE
1219         if (extensions_shape && e->type == extensions_shape_event_basep) {
1220             client->shaped = ((XShapeEvent*)e)->shaped;
1221             frame_adjust_shape(client->frame);
1222         }
1223 #endif
1224     }
1225 }
1226
1227 static void event_handle_dock(ObDock *s, XEvent *e)
1228 {
1229     switch (e->type) {
1230     case ButtonPress:
1231         if (e->xbutton.button == 1)
1232             stacking_raise(DOCK_AS_WINDOW(s));
1233         else if (e->xbutton.button == 2)
1234             stacking_lower(DOCK_AS_WINDOW(s));
1235         break;
1236     case EnterNotify:
1237         dock_hide(FALSE);
1238         break;
1239     case LeaveNotify:
1240         dock_hide(TRUE);
1241         break;
1242     }
1243 }
1244
1245 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1246 {
1247     switch (e->type) {
1248     case MotionNotify:
1249         dock_app_drag(app, &e->xmotion);
1250         break;
1251     case UnmapNotify:
1252         if (app->ignore_unmaps) {
1253             app->ignore_unmaps--;
1254             break;
1255         }
1256         dock_remove(app, TRUE);
1257         break;
1258     case DestroyNotify:
1259         dock_remove(app, FALSE);
1260         break;
1261     case ReparentNotify:
1262         dock_remove(app, FALSE);
1263         break;
1264     case ConfigureNotify:
1265         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1266         break;
1267     }
1268 }
1269
1270 static ObMenuFrame* find_active_menu()
1271 {
1272     GList *it;
1273     ObMenuFrame *ret = NULL;
1274
1275     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1276         ret = it->data;
1277         if (ret->selected)
1278             break;
1279         ret = NULL;
1280     }
1281     return ret;
1282 }
1283
1284 static ObMenuFrame* find_active_or_last_menu()
1285 {
1286     ObMenuFrame *ret = NULL;
1287
1288     ret = find_active_menu();
1289     if (!ret && menu_frame_visible)
1290         ret = menu_frame_visible->data;
1291     return ret;
1292 }
1293
1294 static void event_handle_menu_shortcut(XEvent *ev)
1295 {
1296     gunichar unikey = 0;
1297     ObMenuFrame *frame;
1298     GList *start;
1299     GList *it;
1300     ObMenuEntryFrame *found = NULL;
1301     guint num_found = 0;
1302
1303     {
1304         const char *key;
1305         if ((key = translate_keycode(ev->xkey.keycode)) == NULL)
1306             return;
1307         unikey = g_utf8_get_char_validated(key, -1);
1308         if (unikey == (gunichar)-1 || unikey == (gunichar)-2 || unikey == 0)
1309             return;
1310     }
1311
1312     if ((frame = find_active_or_last_menu()) == NULL)
1313         return;
1314
1315
1316     if (!frame->entries)
1317         return; /* nothing in the menu anyways */
1318
1319     /* start after the selected one */
1320     start = frame->entries;
1321     if (frame->selected) {
1322         for (it = start; frame->selected != it->data; it = g_list_next(it))
1323             g_assert(it != NULL); /* nothing was selected? */
1324         /* next with wraparound */
1325         start = g_list_next(it);
1326         if (start == NULL) start = frame->entries;
1327     }
1328
1329     it = start;
1330     do {
1331         ObMenuEntryFrame *e = it->data;
1332         gunichar entrykey = 0;
1333
1334         if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1335             e->entry->data.normal.enabled)
1336             entrykey = e->entry->data.normal.shortcut;
1337         else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1338             entrykey = e->entry->data.submenu.submenu->shortcut;
1339
1340         if (unikey == entrykey) {
1341             if (found == NULL) found = e;
1342             ++num_found;
1343         }
1344
1345         /* next with wraparound */
1346         it = g_list_next(it);
1347         if (it == NULL) it = frame->entries;
1348     } while (it != start);
1349
1350     if (found) {
1351         if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1352             num_found == 1)
1353         {
1354             menu_frame_select(frame, found, TRUE);
1355             usleep(50000);
1356             menu_entry_frame_execute(found, ev->xkey.state,
1357                                      ev->xkey.time);
1358         } else {
1359             menu_frame_select(frame, found, TRUE);
1360             if (num_found == 1)
1361                 menu_frame_select_next(frame->child);
1362         }
1363     }
1364 }
1365
1366 static void event_handle_menu(XEvent *ev)
1367 {
1368     ObMenuFrame *f;
1369     ObMenuEntryFrame *e;
1370
1371     switch (ev->type) {
1372     case ButtonRelease:
1373         if (menu_can_hide) {
1374             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1375                                             ev->xbutton.y_root)))
1376                 menu_entry_frame_execute(e, ev->xbutton.state,
1377                                          ev->xbutton.time);
1378             else
1379                 menu_frame_hide_all();
1380         }
1381         break;
1382     case EnterNotify:
1383         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1384             if (e->ignore_enters)
1385                 --e->ignore_enters;
1386             else
1387                 menu_frame_select(e->frame, e, FALSE);
1388         }
1389         break;
1390     case LeaveNotify:
1391         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1392             (f = find_active_menu()) && f->selected == e &&
1393             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1394         {
1395             menu_frame_select(e->frame, NULL, FALSE);
1396         }
1397     case MotionNotify:   
1398         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1399                                         ev->xmotion.y_root)))
1400             menu_frame_select(e->frame, e, FALSE);
1401         break;
1402     case KeyPress:
1403         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1404             if ((f = find_active_or_last_menu()) && f->parent)
1405                 menu_frame_select(f, NULL, TRUE);
1406             else
1407                 menu_frame_hide_all();
1408         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1409             ObMenuFrame *f;
1410             if ((f = find_active_menu())) {
1411                 if (f->child)
1412                     menu_frame_select_next(f->child);
1413                 else
1414                     menu_entry_frame_execute(f->selected, ev->xkey.state,
1415                                              ev->xkey.time);
1416             }
1417         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1418             ObMenuFrame *f;
1419             if ((f = find_active_or_last_menu()))
1420                 menu_frame_select(f, NULL, TRUE);
1421         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1422             ObMenuFrame *f;
1423             if ((f = find_active_menu()) && f->child)
1424                 menu_frame_select_next(f->child);
1425         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1426             ObMenuFrame *f;
1427             if ((f = find_active_or_last_menu()))
1428                 menu_frame_select_previous(f);
1429         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1430             ObMenuFrame *f;
1431             if ((f = find_active_or_last_menu()))
1432                 menu_frame_select_next(f);
1433         } else
1434             event_handle_menu_shortcut(ev);
1435         break;
1436     }
1437 }
1438
1439 static gboolean menu_hide_delay_func(gpointer data)
1440 {
1441     menu_can_hide = TRUE;
1442     return FALSE; /* no repeat */
1443 }
1444
1445 static void focus_delay_dest(gpointer data)
1446 {
1447     g_free(data);
1448 }
1449
1450 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1451 {
1452     const ObFocusDelayData *f1 = d1;
1453     return f1->client == d2;
1454 }
1455
1456 static gboolean focus_delay_func(gpointer data)
1457 {
1458     ObFocusDelayData *d = data;
1459     Time old = event_curtime;
1460
1461     event_curtime = d->time;
1462     if (focus_client != d->client) {
1463         if (client_focus(d->client) && config_focus_raise)
1464             client_raise(d->client);
1465     }
1466     event_curtime = old;
1467     return FALSE; /* no repeat */
1468 }
1469
1470 static void focus_delay_client_dest(ObClient *client, gpointer data)
1471 {
1472     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1473                                      client, FALSE);
1474 }
1475
1476 void event_halt_focus_delay()
1477 {
1478     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1479 }
1480
1481 void event_ignore_queued_enters()
1482 {
1483     GSList *saved = NULL, *it;
1484     XEvent *e;
1485                 
1486     XSync(ob_display, FALSE);
1487
1488     /* count the events */
1489     while (TRUE) {
1490         e = g_new(XEvent, 1);
1491         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1492             ObWindow *win;
1493             
1494             win = g_hash_table_lookup(window_map, &e->xany.window);
1495             if (win && WINDOW_IS_CLIENT(win))
1496                 ++ignore_enter_focus;
1497             
1498             saved = g_slist_append(saved, e);
1499         } else {
1500             g_free(e);
1501             break;
1502         }
1503     }
1504     /* put the events back */
1505     for (it = saved; it; it = g_slist_next(it)) {
1506         XPutBackEvent(ob_display, it->data);
1507         g_free(it->data);
1508     }
1509     g_slist_free(saved);
1510 }
1511
1512 gboolean event_time_after(Time t1, Time t2)
1513 {
1514     g_assert(t1 != CurrentTime);
1515     g_assert(t2 != CurrentTime);
1516
1517     /*
1518       Timestamp values wrap around (after about 49.7 days). The server, given
1519       its current time is represented by timestamp T, always interprets
1520       timestamps from clients by treating half of the timestamp space as being
1521       later in time than T.
1522       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1523     */
1524
1525     /* TIME_HALF is half of the number space of a Time type variable */
1526 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1527
1528     if (t2 >= TIME_HALF)
1529         /* t2 is in the second half so t1 might wrap around and be smaller than
1530            t2 */
1531         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1532     else
1533         /* t2 is in the first half so t1 has to come after it */
1534         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1535 }