only ignore vertical scrolls
[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 gboolean event_handle_menu_keyboard(XEvent *e);
80 static gboolean 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         gboolean useevent = TRUE;
575
576         if (menu_frame_visible) {
577             if (event_handle_menu(e))
578                 /* don't use the event if the menu used it, but if the menu
579                    didn't use it and it's a keypress that is bound, it will
580                    close the menu and be used */
581                 useevent = FALSE;
582         }
583
584         if (useevent) {
585             if (!keyboard_process_interactive_grab(e, &client)) {
586                 if (moveresize_in_progress) {
587                     moveresize_event(e);
588
589                     /* make further actions work on the client being
590                        moved/resized */
591                     client = moveresize_client;
592                 }
593
594                 menu_can_hide = FALSE;
595                 ob_main_loop_timeout_add(ob_main_loop,
596                                          config_menu_hide_delay * 1000,
597                                          menu_hide_delay_func,
598                                          NULL, g_direct_equal, NULL);
599
600                 if (e->type == ButtonPress || e->type == ButtonRelease ||
601                     e->type == MotionNotify) {
602                     mouse_event(client, e);
603                 } else if (e->type == KeyPress) {
604                     keyboard_event((focus_cycle_target ? focus_cycle_target :
605                                     (client ? client : focus_client)), e);
606                 }
607             }
608         }
609     }
610     /* if something happens and it's not from an XEvent, then we don't know
611        the time */
612     event_curtime = CurrentTime;
613 }
614
615 static void event_handle_root(XEvent *e)
616 {
617     Atom msgtype;
618      
619     switch(e->type) {
620     case SelectionClear:
621         ob_debug("Another WM has requested to replace us. Exiting.\n");
622         ob_exit_replace();
623         break;
624
625     case ClientMessage:
626         if (e->xclient.format != 32) break;
627
628         msgtype = e->xclient.message_type;
629         if (msgtype == prop_atoms.net_current_desktop) {
630             guint d = e->xclient.data.l[0];
631             if (d < screen_num_desktops) {
632                 event_curtime = e->xclient.data.l[1];
633                 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime);
634                 screen_set_desktop(d);
635             }
636         } else if (msgtype == prop_atoms.net_number_of_desktops) {
637             guint d = e->xclient.data.l[0];
638             if (d > 0)
639                 screen_set_num_desktops(d);
640         } else if (msgtype == prop_atoms.net_showing_desktop) {
641             screen_show_desktop(e->xclient.data.l[0] != 0);
642         } else if (msgtype == prop_atoms.ob_control) {
643             if (e->xclient.data.l[0] == 1)
644                 ob_reconfigure();
645             else if (e->xclient.data.l[0] == 2)
646                 ob_restart();
647         }
648         break;
649     case PropertyNotify:
650         if (e->xproperty.atom == prop_atoms.net_desktop_names)
651             screen_update_desktop_names();
652         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
653             screen_update_layout();
654         break;
655     case ConfigureNotify:
656 #ifdef XRANDR
657         XRRUpdateConfiguration(e);
658 #endif
659         screen_resize();
660         break;
661     default:
662         ;
663     }
664 }
665
666 static void event_handle_group(ObGroup *group, XEvent *e)
667 {
668     GSList *it;
669
670     g_assert(e->type == PropertyNotify);
671
672     for (it = group->members; it; it = g_slist_next(it))
673         event_handle_client(it->data, e);
674 }
675
676 void event_enter_client(ObClient *client)
677 {
678     g_assert(config_focus_follow);
679
680     if (client_normal(client) && client_can_focus(client)) {
681         if (config_focus_delay) {
682             ObFocusDelayData *data;
683
684             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
685
686             data = g_new(ObFocusDelayData, 1);
687             data->client = client;
688             data->time = event_curtime;
689
690             ob_main_loop_timeout_add(ob_main_loop,
691                                      config_focus_delay,
692                                      focus_delay_func,
693                                      data, focus_delay_cmp, focus_delay_dest);
694         } else {
695             ObFocusDelayData data;
696             data.client = client;
697             data.time = event_curtime;
698             focus_delay_func(&data);
699         }
700     }
701 }
702
703 static void event_handle_client(ObClient *client, XEvent *e)
704 {
705     XEvent ce;
706     Atom msgtype;
707     gint i=0;
708     ObFrameContext con;
709      
710     switch (e->type) {
711     case ButtonPress:
712     case ButtonRelease:
713         /* Wheel buttons don't draw because they are an instant click, so it
714            is a waste of resources to go drawing it. */
715         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
716             con = frame_context(client, e->xbutton.window);
717             con = mouse_button_frame_context(con, e->xbutton.button);
718             switch (con) {
719             case OB_FRAME_CONTEXT_MAXIMIZE:
720                 client->frame->max_press = (e->type == ButtonPress);
721                 framerender_frame(client->frame);
722                 break;
723             case OB_FRAME_CONTEXT_CLOSE:
724                 client->frame->close_press = (e->type == ButtonPress);
725                 framerender_frame(client->frame);
726                 break;
727             case OB_FRAME_CONTEXT_ICONIFY:
728                 client->frame->iconify_press = (e->type == ButtonPress);
729                 framerender_frame(client->frame);
730                 break;
731             case OB_FRAME_CONTEXT_ALLDESKTOPS:
732                 client->frame->desk_press = (e->type == ButtonPress);
733                 framerender_frame(client->frame);
734                 break; 
735             case OB_FRAME_CONTEXT_SHADE:
736                 client->frame->shade_press = (e->type == ButtonPress);
737                 framerender_frame(client->frame);
738                 break;
739             default:
740                 /* nothing changes with clicks for any other contexts */
741                 break;
742             }
743         }
744         break;
745     case LeaveNotify:
746         con = frame_context(client, e->xcrossing.window);
747         switch (con) {
748         case OB_FRAME_CONTEXT_MAXIMIZE:
749             client->frame->max_hover = FALSE;
750             frame_adjust_state(client->frame);
751             break;
752         case OB_FRAME_CONTEXT_ALLDESKTOPS:
753             client->frame->desk_hover = FALSE;
754             frame_adjust_state(client->frame);
755             break;
756         case OB_FRAME_CONTEXT_SHADE:
757             client->frame->shade_hover = FALSE;
758             frame_adjust_state(client->frame);
759             break;
760         case OB_FRAME_CONTEXT_ICONIFY:
761             client->frame->iconify_hover = FALSE;
762             frame_adjust_state(client->frame);
763             break;
764         case OB_FRAME_CONTEXT_CLOSE:
765             client->frame->close_hover = FALSE;
766             frame_adjust_state(client->frame);
767             break;
768         case OB_FRAME_CONTEXT_FRAME:
769             ob_debug_type(OB_DEBUG_FOCUS,
770                           "%sNotify mode %d detail %d on %lx\n",
771                           (e->type == EnterNotify ? "Enter" : "Leave"),
772                           e->xcrossing.mode,
773                           e->xcrossing.detail, (client?client->window:0));
774             if (keyboard_interactively_grabbed())
775                 break;
776             if (config_focus_follow && config_focus_delay &&
777                 /* leave inferior events can happen when the mouse goes onto
778                    the window's border and then into the window before the
779                    delay is up */
780                 e->xcrossing.detail != NotifyInferior)
781             {
782                 ob_main_loop_timeout_remove_data(ob_main_loop,
783                                                  focus_delay_func,
784                                                  client, FALSE);
785             }
786             break;
787         default:
788             break;
789         }
790         break;
791     case EnterNotify:
792     {
793         gboolean nofocus = FALSE;
794
795         if (ignore_enter_focus) {
796             ignore_enter_focus--;
797             nofocus = TRUE;
798         }
799
800         con = frame_context(client, e->xcrossing.window);
801         switch (con) {
802         case OB_FRAME_CONTEXT_MAXIMIZE:
803             client->frame->max_hover = TRUE;
804             frame_adjust_state(client->frame);
805             break;
806         case OB_FRAME_CONTEXT_ALLDESKTOPS:
807             client->frame->desk_hover = TRUE;
808             frame_adjust_state(client->frame);
809             break;
810         case OB_FRAME_CONTEXT_SHADE:
811             client->frame->shade_hover = TRUE;
812             frame_adjust_state(client->frame);
813             break;
814         case OB_FRAME_CONTEXT_ICONIFY:
815             client->frame->iconify_hover = TRUE;
816             frame_adjust_state(client->frame);
817             break;
818         case OB_FRAME_CONTEXT_CLOSE:
819             client->frame->close_hover = TRUE;
820             frame_adjust_state(client->frame);
821             break;
822         case OB_FRAME_CONTEXT_FRAME:
823             if (keyboard_interactively_grabbed())
824                 break;
825             if (e->xcrossing.mode == NotifyGrab ||
826                 e->xcrossing.mode == NotifyUngrab ||
827                 /*ignore enters when we're already in the window */
828                 e->xcrossing.detail == NotifyInferior)
829             {
830                 ob_debug_type(OB_DEBUG_FOCUS,
831                               "%sNotify mode %d detail %d on %lx IGNORED\n",
832                               (e->type == EnterNotify ? "Enter" : "Leave"),
833                               e->xcrossing.mode,
834                               e->xcrossing.detail, client?client->window:0);
835             } else {
836                 ob_debug_type(OB_DEBUG_FOCUS,
837                               "%sNotify mode %d detail %d on %lx, "
838                               "focusing window: %d\n",
839                               (e->type == EnterNotify ? "Enter" : "Leave"),
840                               e->xcrossing.mode,
841                               e->xcrossing.detail, (client?client->window:0),
842                               !nofocus);
843                 if (!nofocus && config_focus_follow)
844                     event_enter_client(client);
845             }
846             break;
847         default:
848             break;
849         }
850         break;
851     }
852     case ConfigureRequest:
853         /* compress these */
854         while (XCheckTypedWindowEvent(ob_display, client->window,
855                                       ConfigureRequest, &ce)) {
856             ++i;
857             /* XXX if this causes bad things.. we can compress config req's
858                with the same mask. */
859             e->xconfigurerequest.value_mask |=
860                 ce.xconfigurerequest.value_mask;
861             if (ce.xconfigurerequest.value_mask & CWX)
862                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
863             if (ce.xconfigurerequest.value_mask & CWY)
864                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
865             if (ce.xconfigurerequest.value_mask & CWWidth)
866                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
867             if (ce.xconfigurerequest.value_mask & CWHeight)
868                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
869             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
870                 e->xconfigurerequest.border_width =
871                     ce.xconfigurerequest.border_width;
872             if (ce.xconfigurerequest.value_mask & CWStackMode)
873                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
874         }
875
876         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
877         if (client->iconic || client->shaded) return;
878
879         /* resize, then move, as specified in the EWMH section 7.7 */
880         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
881                                                CWX | CWY |
882                                                CWBorderWidth)) {
883             gint x, y, w, h;
884
885             if (e->xconfigurerequest.value_mask & CWBorderWidth)
886                 client->border_width = e->xconfigurerequest.border_width;
887
888             x = (e->xconfigurerequest.value_mask & CWX) ?
889                 e->xconfigurerequest.x : client->area.x;
890             y = (e->xconfigurerequest.value_mask & CWY) ?
891                 e->xconfigurerequest.y : client->area.y;
892             w = (e->xconfigurerequest.value_mask & CWWidth) ?
893                 e->xconfigurerequest.width : client->area.width;
894             h = (e->xconfigurerequest.value_mask & CWHeight) ?
895                 e->xconfigurerequest.height : client->area.height;
896
897             client_find_onscreen(client, &x, &y, w, h, FALSE);
898             client_configure_full(client, x, y, w, h, FALSE, TRUE, TRUE);
899         }
900
901         if (e->xconfigurerequest.value_mask & CWStackMode) {
902             switch (e->xconfigurerequest.detail) {
903             case Below:
904             case BottomIf:
905                 /* Apps are so rude. And this is totally disconnected from
906                    activation/focus. Bleh. */
907                 /*client_lower(client);*/
908                 break;
909
910             case Above:
911             case TopIf:
912             default:
913                 /* Apps are so rude. And this is totally disconnected from
914                    activation/focus. Bleh. */
915                 /*client_raise(client);*/
916                 break;
917             }
918         }
919         break;
920     case UnmapNotify:
921         if (client->ignore_unmaps) {
922             client->ignore_unmaps--;
923             break;
924         }
925         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
926                  "ignores left %d\n",
927                  client->window, e->xunmap.event, e->xunmap.from_configure,
928                  client->ignore_unmaps);
929         client_unmanage(client);
930         break;
931     case DestroyNotify:
932         ob_debug("DestroyNotify for window 0x%x\n", client->window);
933         client_unmanage(client);
934         break;
935     case ReparentNotify:
936         /* this is when the client is first taken captive in the frame */
937         if (e->xreparent.parent == client->frame->plate) break;
938
939         /*
940           This event is quite rare and is usually handled in unmapHandler.
941           However, if the window is unmapped when the reparent event occurs,
942           the window manager never sees it because an unmap event is not sent
943           to an already unmapped window.
944         */
945
946         /* we don't want the reparent event, put it back on the stack for the
947            X server to deal with after we unmanage the window */
948         XPutBackEvent(ob_display, e);
949      
950         ob_debug("ReparentNotify for window 0x%x\n", client->window);
951         client_unmanage(client);
952         break;
953     case MapRequest:
954         ob_debug("MapRequest for 0x%lx\n", client->window);
955         if (!client->iconic) break; /* this normally doesn't happen, but if it
956                                        does, we don't want it!
957                                        it can happen now when the window is on
958                                        another desktop, but we still don't
959                                        want it! */
960         client_activate(client, FALSE, TRUE);
961         break;
962     case ClientMessage:
963         /* validate cuz we query stuff off the client here */
964         if (!client_validate(client)) break;
965
966         if (e->xclient.format != 32) return;
967
968         msgtype = e->xclient.message_type;
969         if (msgtype == prop_atoms.wm_change_state) {
970             /* compress changes into a single change */
971             while (XCheckTypedWindowEvent(ob_display, client->window,
972                                           e->type, &ce)) {
973                 /* XXX: it would be nice to compress ALL messages of a
974                    type, not just messages in a row without other
975                    message types between. */
976                 if (ce.xclient.message_type != msgtype) {
977                     XPutBackEvent(ob_display, &ce);
978                     break;
979                 }
980                 e->xclient = ce.xclient;
981             }
982             client_set_wm_state(client, e->xclient.data.l[0]);
983         } else if (msgtype == prop_atoms.net_wm_desktop) {
984             /* compress changes into a single change */
985             while (XCheckTypedWindowEvent(ob_display, client->window,
986                                           e->type, &ce)) {
987                 /* XXX: it would be nice to compress ALL messages of a
988                    type, not just messages in a row without other
989                    message types between. */
990                 if (ce.xclient.message_type != msgtype) {
991                     XPutBackEvent(ob_display, &ce);
992                     break;
993                 }
994                 e->xclient = ce.xclient;
995             }
996             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
997                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
998                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
999                                    FALSE);
1000         } else if (msgtype == prop_atoms.net_wm_state) {
1001             /* can't compress these */
1002             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1003                      (e->xclient.data.l[0] == 0 ? "Remove" :
1004                       e->xclient.data.l[0] == 1 ? "Add" :
1005                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1006                      e->xclient.data.l[1], e->xclient.data.l[2],
1007                      client->window);
1008             client_set_state(client, e->xclient.data.l[0],
1009                              e->xclient.data.l[1], e->xclient.data.l[2]);
1010         } else if (msgtype == prop_atoms.net_close_window) {
1011             ob_debug("net_close_window for 0x%lx\n", client->window);
1012             client_close(client);
1013         } else if (msgtype == prop_atoms.net_active_window) {
1014             ob_debug("net_active_window for 0x%lx source=%s\n",
1015                      client->window,
1016                      (e->xclient.data.l[0] == 0 ? "unknown" :
1017                       (e->xclient.data.l[0] == 1 ? "application" :
1018                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1019             /* XXX make use of data.l[2] ! */
1020             event_curtime = e->xclient.data.l[1];
1021             client_activate(client, FALSE,
1022                             (e->xclient.data.l[0] == 0 ||
1023                              e->xclient.data.l[0] == 2));
1024         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1025             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1026                      client->window, e->xclient.data.l[2]);
1027             if ((Atom)e->xclient.data.l[2] ==
1028                 prop_atoms.net_wm_moveresize_size_topleft ||
1029                 (Atom)e->xclient.data.l[2] ==
1030                 prop_atoms.net_wm_moveresize_size_top ||
1031                 (Atom)e->xclient.data.l[2] ==
1032                 prop_atoms.net_wm_moveresize_size_topright ||
1033                 (Atom)e->xclient.data.l[2] ==
1034                 prop_atoms.net_wm_moveresize_size_right ||
1035                 (Atom)e->xclient.data.l[2] ==
1036                 prop_atoms.net_wm_moveresize_size_right ||
1037                 (Atom)e->xclient.data.l[2] ==
1038                 prop_atoms.net_wm_moveresize_size_bottomright ||
1039                 (Atom)e->xclient.data.l[2] ==
1040                 prop_atoms.net_wm_moveresize_size_bottom ||
1041                 (Atom)e->xclient.data.l[2] ==
1042                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1043                 (Atom)e->xclient.data.l[2] ==
1044                 prop_atoms.net_wm_moveresize_size_left ||
1045                 (Atom)e->xclient.data.l[2] ==
1046                 prop_atoms.net_wm_moveresize_move ||
1047                 (Atom)e->xclient.data.l[2] ==
1048                 prop_atoms.net_wm_moveresize_size_keyboard ||
1049                 (Atom)e->xclient.data.l[2] ==
1050                 prop_atoms.net_wm_moveresize_move_keyboard) {
1051
1052                 moveresize_start(client, e->xclient.data.l[0],
1053                                  e->xclient.data.l[1], e->xclient.data.l[3],
1054                                  e->xclient.data.l[2]);
1055             }
1056             else if ((Atom)e->xclient.data.l[2] ==
1057                      prop_atoms.net_wm_moveresize_cancel)
1058                 moveresize_end(TRUE);
1059         } else if (msgtype == prop_atoms.net_moveresize_window) {
1060             gint grav, x, y, w, h;
1061
1062             if (e->xclient.data.l[0] & 0xff)
1063                 grav = e->xclient.data.l[0] & 0xff;
1064             else 
1065                 grav = client->gravity;
1066
1067             if (e->xclient.data.l[0] & 1 << 8)
1068                 x = e->xclient.data.l[1];
1069             else
1070                 x = client->area.x;
1071             if (e->xclient.data.l[0] & 1 << 9)
1072                 y = e->xclient.data.l[2];
1073             else
1074                 y = client->area.y;
1075             if (e->xclient.data.l[0] & 1 << 10)
1076                 w = e->xclient.data.l[3];
1077             else
1078                 w = client->area.width;
1079             if (e->xclient.data.l[0] & 1 << 11)
1080                 h = e->xclient.data.l[4];
1081             else
1082                 h = client->area.height;
1083
1084             client_convert_gravity(client, grav, &x, &y, w, h);
1085             client_find_onscreen(client, &x, &y, w, h, FALSE);
1086             client_configure(client, x, y, w, h, FALSE, TRUE);
1087         }
1088         break;
1089     case PropertyNotify:
1090         /* validate cuz we query stuff off the client here */
1091         if (!client_validate(client)) break;
1092   
1093         /* compress changes to a single property into a single change */
1094         while (XCheckTypedWindowEvent(ob_display, client->window,
1095                                       e->type, &ce)) {
1096             Atom a, b;
1097
1098             /* XXX: it would be nice to compress ALL changes to a property,
1099                not just changes in a row without other props between. */
1100
1101             a = ce.xproperty.atom;
1102             b = e->xproperty.atom;
1103
1104             if (a == b)
1105                 continue;
1106             if ((a == prop_atoms.net_wm_name ||
1107                  a == prop_atoms.wm_name ||
1108                  a == prop_atoms.net_wm_icon_name ||
1109                  a == prop_atoms.wm_icon_name)
1110                 &&
1111                 (b == prop_atoms.net_wm_name ||
1112                  b == prop_atoms.wm_name ||
1113                  b == prop_atoms.net_wm_icon_name ||
1114                  b == prop_atoms.wm_icon_name)) {
1115                 continue;
1116             }
1117             if (a == prop_atoms.net_wm_icon &&
1118                 b == prop_atoms.net_wm_icon)
1119                 continue;
1120
1121             XPutBackEvent(ob_display, &ce);
1122             break;
1123         }
1124
1125         msgtype = e->xproperty.atom;
1126         if (msgtype == XA_WM_NORMAL_HINTS) {
1127             client_update_normal_hints(client);
1128             /* normal hints can make a window non-resizable */
1129             client_setup_decor_and_functions(client);
1130         } else if (msgtype == XA_WM_HINTS) {
1131             client_update_wmhints(client);
1132         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1133             client_update_transient_for(client);
1134             client_get_type(client);
1135             /* type may have changed, so update the layer */
1136             client_calc_layer(client);
1137             client_setup_decor_and_functions(client);
1138         } else if (msgtype == prop_atoms.net_wm_name ||
1139                    msgtype == prop_atoms.wm_name ||
1140                    msgtype == prop_atoms.net_wm_icon_name ||
1141                    msgtype == prop_atoms.wm_icon_name) {
1142             client_update_title(client);
1143         } else if (msgtype == prop_atoms.wm_class) {
1144             client_update_class(client);
1145         } else if (msgtype == prop_atoms.wm_protocols) {
1146             client_update_protocols(client);
1147             client_setup_decor_and_functions(client);
1148         }
1149         else if (msgtype == prop_atoms.net_wm_strut) {
1150             client_update_strut(client);
1151         }
1152         else if (msgtype == prop_atoms.net_wm_icon) {
1153             client_update_icons(client);
1154         }
1155         else if (msgtype == prop_atoms.net_wm_user_time) {
1156             client_update_user_time(client);
1157         }
1158 #ifdef SYNC
1159         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1160             client_update_sync_request_counter(client);
1161         }
1162 #endif
1163         else if (msgtype == prop_atoms.sm_client_id) {
1164             client_update_sm_client_id(client);
1165         }
1166     case ColormapNotify:
1167         client_update_colormap(client, e->xcolormap.colormap);
1168         break;
1169     default:
1170         ;
1171 #ifdef SHAPE
1172         if (extensions_shape && e->type == extensions_shape_event_basep) {
1173             client->shaped = ((XShapeEvent*)e)->shaped;
1174             frame_adjust_shape(client->frame);
1175         }
1176 #endif
1177     }
1178 }
1179
1180 static void event_handle_dock(ObDock *s, XEvent *e)
1181 {
1182     switch (e->type) {
1183     case ButtonPress:
1184         if (e->xbutton.button == 1)
1185             stacking_raise(DOCK_AS_WINDOW(s));
1186         else if (e->xbutton.button == 2)
1187             stacking_lower(DOCK_AS_WINDOW(s));
1188         break;
1189     case EnterNotify:
1190         dock_hide(FALSE);
1191         break;
1192     case LeaveNotify:
1193         dock_hide(TRUE);
1194         break;
1195     }
1196 }
1197
1198 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1199 {
1200     switch (e->type) {
1201     case MotionNotify:
1202         dock_app_drag(app, &e->xmotion);
1203         break;
1204     case UnmapNotify:
1205         if (app->ignore_unmaps) {
1206             app->ignore_unmaps--;
1207             break;
1208         }
1209         dock_remove(app, TRUE);
1210         break;
1211     case DestroyNotify:
1212         dock_remove(app, FALSE);
1213         break;
1214     case ReparentNotify:
1215         dock_remove(app, FALSE);
1216         break;
1217     case ConfigureNotify:
1218         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1219         break;
1220     }
1221 }
1222
1223 static ObMenuFrame* find_active_menu()
1224 {
1225     GList *it;
1226     ObMenuFrame *ret = NULL;
1227
1228     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1229         ret = it->data;
1230         if (ret->selected)
1231             break;
1232         ret = NULL;
1233     }
1234     return ret;
1235 }
1236
1237 static ObMenuFrame* find_active_or_last_menu()
1238 {
1239     ObMenuFrame *ret = NULL;
1240
1241     ret = find_active_menu();
1242     if (!ret && menu_frame_visible)
1243         ret = menu_frame_visible->data;
1244     return ret;
1245 }
1246
1247 static gboolean event_handle_menu_keyboard(XEvent *ev)
1248 {
1249     guint keycode, state;
1250     gunichar unikey;
1251     ObMenuFrame *frame;
1252     gboolean ret = TRUE;
1253
1254     keycode = ev->xkey.keycode;
1255     state = ev->xkey.state;
1256     unikey = translate_unichar(keycode);
1257
1258     frame = find_active_or_last_menu();
1259     if (frame == NULL)
1260         ret = FALSE;
1261
1262     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1263         /* Escape closes the active menu */
1264         menu_frame_hide(frame);
1265     }
1266
1267     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1268                                                       state == ControlMask))
1269     {
1270         /* Enter runs the active item or goes into the submenu.
1271            Control-Enter runs it without closing the menu. */
1272         if (frame->child)
1273             menu_frame_select_next(frame->child);
1274         else
1275             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1276     }
1277
1278     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1279         /* Left goes to the parent menu */
1280         menu_frame_select(frame, NULL, TRUE);
1281     }
1282
1283     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1284         /* Right goes to the selected submenu */
1285         if (frame->child) menu_frame_select_next(frame->child);
1286     }
1287
1288     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1289         menu_frame_select_previous(frame);
1290     }
1291
1292     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1293         menu_frame_select_next(frame);
1294     }
1295
1296     /* keyboard accelerator shortcuts. */
1297     else if (ev->xkey.state == 0 &&
1298              /* was it a valid key? */
1299              unikey != 0 &&
1300              /* don't bother if the menu is empty. */
1301              frame->entries)
1302     {
1303         GList *start;
1304         GList *it;
1305         ObMenuEntryFrame *found = NULL;
1306         guint num_found = 0;
1307
1308         /* start after the selected one */
1309         start = frame->entries;
1310         if (frame->selected) {
1311             for (it = start; frame->selected != it->data; it = g_list_next(it))
1312                 g_assert(it != NULL); /* nothing was selected? */
1313             /* next with wraparound */
1314             start = g_list_next(it);
1315             if (start == NULL) start = frame->entries;
1316         }
1317
1318         it = start;
1319         do {
1320             ObMenuEntryFrame *e = it->data;
1321             gunichar entrykey = 0;
1322
1323             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1324                 e->entry->data.normal.enabled)
1325                 entrykey = e->entry->data.normal.shortcut;
1326             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1327                 entrykey = e->entry->data.submenu.submenu->shortcut;
1328
1329             if (unikey == entrykey) {
1330                 if (found == NULL) found = e;
1331                 ++num_found;
1332             }
1333
1334             /* next with wraparound */
1335             it = g_list_next(it);
1336             if (it == NULL) it = frame->entries;
1337         } while (it != start);
1338
1339         if (found) {
1340             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1341                 num_found == 1)
1342             {
1343                 menu_frame_select(frame, found, TRUE);
1344                 usleep(50000);
1345                 menu_entry_frame_execute(found, state, ev->xkey.time);
1346             } else {
1347                 menu_frame_select(frame, found, TRUE);
1348                 if (num_found == 1)
1349                     menu_frame_select_next(frame->child);
1350             }
1351         } else
1352             ret = FALSE;
1353     }
1354     else
1355         ret = FALSE;
1356
1357     return ret;
1358 }
1359
1360 static gboolean event_handle_menu(XEvent *ev)
1361 {
1362     ObMenuFrame *f;
1363     ObMenuEntryFrame *e;
1364     gboolean ret = TRUE;
1365
1366     switch (ev->type) {
1367     case ButtonRelease:
1368         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1369             && menu_can_hide)
1370         {
1371             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1372                                             ev->xbutton.y_root)))
1373                 menu_entry_frame_execute(e, ev->xbutton.state,
1374                                          ev->xbutton.time);
1375             else
1376                 menu_frame_hide_all();
1377         }
1378         break;
1379     case EnterNotify:
1380         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1381             if (e->ignore_enters)
1382                 --e->ignore_enters;
1383             else
1384                 menu_frame_select(e->frame, e, FALSE);
1385         }
1386         break;
1387     case LeaveNotify:
1388         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1389             (f = find_active_menu()) && f->selected == e &&
1390             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1391         {
1392             menu_frame_select(e->frame, NULL, FALSE);
1393         }
1394     case MotionNotify:   
1395         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1396                                         ev->xmotion.y_root)))
1397             menu_frame_select(e->frame, e, FALSE);
1398         break;
1399     case KeyPress:
1400         ret = event_handle_menu_keyboard(ev);
1401         break;
1402     }
1403     return ret;
1404 }
1405
1406 static gboolean menu_hide_delay_func(gpointer data)
1407 {
1408     menu_can_hide = TRUE;
1409     return FALSE; /* no repeat */
1410 }
1411
1412 static void focus_delay_dest(gpointer data)
1413 {
1414     g_free(data);
1415 }
1416
1417 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1418 {
1419     const ObFocusDelayData *f1 = d1;
1420     return f1->client == d2;
1421 }
1422
1423 static gboolean focus_delay_func(gpointer data)
1424 {
1425     ObFocusDelayData *d = data;
1426     Time old = event_curtime;
1427
1428     event_curtime = d->time;
1429     if (focus_client != d->client) {
1430         if (client_focus(d->client) && config_focus_raise)
1431             client_raise(d->client);
1432     }
1433     event_curtime = old;
1434     return FALSE; /* no repeat */
1435 }
1436
1437 static void focus_delay_client_dest(ObClient *client, gpointer data)
1438 {
1439     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1440                                      client, FALSE);
1441 }
1442
1443 void event_halt_focus_delay()
1444 {
1445     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1446 }
1447
1448 void event_ignore_queued_enters()
1449 {
1450     GSList *saved = NULL, *it;
1451     XEvent *e;
1452                 
1453     XSync(ob_display, FALSE);
1454
1455     /* count the events */
1456     while (TRUE) {
1457         e = g_new(XEvent, 1);
1458         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1459             ObWindow *win;
1460             
1461             win = g_hash_table_lookup(window_map, &e->xany.window);
1462             if (win && WINDOW_IS_CLIENT(win))
1463                 ++ignore_enter_focus;
1464             
1465             saved = g_slist_append(saved, e);
1466         } else {
1467             g_free(e);
1468             break;
1469         }
1470     }
1471     /* put the events back */
1472     for (it = saved; it; it = g_slist_next(it)) {
1473         XPutBackEvent(ob_display, it->data);
1474         g_free(it->data);
1475     }
1476     g_slist_free(saved);
1477 }
1478
1479 gboolean event_time_after(Time t1, Time t2)
1480 {
1481     g_assert(t1 != CurrentTime);
1482     g_assert(t2 != CurrentTime);
1483
1484     /*
1485       Timestamp values wrap around (after about 49.7 days). The server, given
1486       its current time is represented by timestamp T, always interprets
1487       timestamps from clients by treating half of the timestamp space as being
1488       later in time than T.
1489       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1490     */
1491
1492     /* TIME_HALF is half of the number space of a Time type variable */
1493 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1494
1495     if (t2 >= TIME_HALF)
1496         /* t2 is in the second half so t1 might wrap around and be smaller than
1497            t2 */
1498         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1499     else
1500         /* t2 is in the first half so t1 has to come after it */
1501         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1502 }