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