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