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