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