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