]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
add an allDesktops option to Next/PreviousWindow to alt-tab across all desktops.
[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_class) {
1132             client_update_class(client);
1133         } else if (msgtype == prop_atoms.wm_protocols) {
1134             client_update_protocols(client);
1135             client_setup_decor_and_functions(client);
1136         }
1137         else if (msgtype == prop_atoms.net_wm_strut) {
1138             client_update_strut(client);
1139         }
1140         else if (msgtype == prop_atoms.net_wm_icon) {
1141             client_update_icons(client);
1142         }
1143         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1144             client_update_icon_geometry(client);
1145         }
1146         else if (msgtype == prop_atoms.net_wm_user_time) {
1147             client_update_user_time(client);
1148         }
1149 #ifdef SYNC
1150         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1151             client_update_sync_request_counter(client);
1152         }
1153 #endif
1154         else if (msgtype == prop_atoms.sm_client_id) {
1155             client_update_sm_client_id(client);
1156         }
1157     case ColormapNotify:
1158         client_update_colormap(client, e->xcolormap.colormap);
1159         break;
1160     default:
1161         ;
1162 #ifdef SHAPE
1163         if (extensions_shape && e->type == extensions_shape_event_basep) {
1164             client->shaped = ((XShapeEvent*)e)->shaped;
1165             frame_adjust_shape(client->frame);
1166         }
1167 #endif
1168     }
1169 }
1170
1171 static void event_handle_dock(ObDock *s, XEvent *e)
1172 {
1173     switch (e->type) {
1174     case ButtonPress:
1175         if (e->xbutton.button == 1)
1176             stacking_raise(DOCK_AS_WINDOW(s));
1177         else if (e->xbutton.button == 2)
1178             stacking_lower(DOCK_AS_WINDOW(s));
1179         break;
1180     case EnterNotify:
1181         dock_hide(FALSE);
1182         break;
1183     case LeaveNotify:
1184         dock_hide(TRUE);
1185         break;
1186     }
1187 }
1188
1189 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1190 {
1191     switch (e->type) {
1192     case MotionNotify:
1193         dock_app_drag(app, &e->xmotion);
1194         break;
1195     case UnmapNotify:
1196         if (app->ignore_unmaps) {
1197             app->ignore_unmaps--;
1198             break;
1199         }
1200         dock_remove(app, TRUE);
1201         break;
1202     case DestroyNotify:
1203         dock_remove(app, FALSE);
1204         break;
1205     case ReparentNotify:
1206         dock_remove(app, FALSE);
1207         break;
1208     case ConfigureNotify:
1209         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1210         break;
1211     }
1212 }
1213
1214 static ObMenuFrame* find_active_menu()
1215 {
1216     GList *it;
1217     ObMenuFrame *ret = NULL;
1218
1219     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1220         ret = it->data;
1221         if (ret->selected)
1222             break;
1223         ret = NULL;
1224     }
1225     return ret;
1226 }
1227
1228 static ObMenuFrame* find_active_or_last_menu()
1229 {
1230     ObMenuFrame *ret = NULL;
1231
1232     ret = find_active_menu();
1233     if (!ret && menu_frame_visible)
1234         ret = menu_frame_visible->data;
1235     return ret;
1236 }
1237
1238 static gboolean event_handle_menu_keyboard(XEvent *ev)
1239 {
1240     guint keycode, state;
1241     gunichar unikey;
1242     ObMenuFrame *frame;
1243     gboolean ret = TRUE;
1244
1245     keycode = ev->xkey.keycode;
1246     state = ev->xkey.state;
1247     unikey = translate_unichar(keycode);
1248
1249     frame = find_active_or_last_menu();
1250     if (frame == NULL)
1251         ret = FALSE;
1252
1253     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1254         /* Escape closes the active menu */
1255         menu_frame_hide(frame);
1256     }
1257
1258     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1259                                                       state == ControlMask))
1260     {
1261         /* Enter runs the active item or goes into the submenu.
1262            Control-Enter runs it without closing the menu. */
1263         if (frame->child)
1264             menu_frame_select_next(frame->child);
1265         else
1266             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1267     }
1268
1269     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1270         /* Left goes to the parent menu */
1271         menu_frame_select(frame, NULL, TRUE);
1272     }
1273
1274     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1275         /* Right goes to the selected submenu */
1276         if (frame->child) menu_frame_select_next(frame->child);
1277     }
1278
1279     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1280         menu_frame_select_previous(frame);
1281     }
1282
1283     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1284         menu_frame_select_next(frame);
1285     }
1286
1287     /* keyboard accelerator shortcuts. */
1288     else if (ev->xkey.state == 0 &&
1289              /* was it a valid key? */
1290              unikey != 0 &&
1291              /* don't bother if the menu is empty. */
1292              frame->entries)
1293     {
1294         GList *start;
1295         GList *it;
1296         ObMenuEntryFrame *found = NULL;
1297         guint num_found = 0;
1298
1299         /* start after the selected one */
1300         start = frame->entries;
1301         if (frame->selected) {
1302             for (it = start; frame->selected != it->data; it = g_list_next(it))
1303                 g_assert(it != NULL); /* nothing was selected? */
1304             /* next with wraparound */
1305             start = g_list_next(it);
1306             if (start == NULL) start = frame->entries;
1307         }
1308
1309         it = start;
1310         do {
1311             ObMenuEntryFrame *e = it->data;
1312             gunichar entrykey = 0;
1313
1314             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1315                 entrykey = e->entry->data.normal.shortcut;
1316             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1317                 entrykey = e->entry->data.submenu.submenu->shortcut;
1318
1319             if (unikey == entrykey) {
1320                 if (found == NULL) found = e;
1321                 ++num_found;
1322             }
1323
1324             /* next with wraparound */
1325             it = g_list_next(it);
1326             if (it == NULL) it = frame->entries;
1327         } while (it != start);
1328
1329         if (found) {
1330             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1331                 num_found == 1)
1332             {
1333                 menu_frame_select(frame, found, TRUE);
1334                 usleep(50000); /* highlight the item for a short bit so the
1335                                   user can see what happened */
1336                 menu_entry_frame_execute(found, state, ev->xkey.time);
1337             } else {
1338                 menu_frame_select(frame, found, TRUE);
1339                 if (num_found == 1)
1340                     menu_frame_select_next(frame->child);
1341             }
1342         } else
1343             ret = FALSE;
1344     }
1345     else
1346         ret = FALSE;
1347
1348     return ret;
1349 }
1350
1351 static gboolean event_handle_menu(XEvent *ev)
1352 {
1353     ObMenuFrame *f;
1354     ObMenuEntryFrame *e;
1355     gboolean ret = TRUE;
1356
1357     switch (ev->type) {
1358     case ButtonRelease:
1359         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1360             && menu_can_hide)
1361         {
1362             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1363                                             ev->xbutton.y_root)))
1364                 menu_entry_frame_execute(e, ev->xbutton.state,
1365                                          ev->xbutton.time);
1366             else
1367                 menu_frame_hide_all();
1368         }
1369         break;
1370     case EnterNotify:
1371         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1372             if (e->ignore_enters)
1373                 --e->ignore_enters;
1374             else
1375                 menu_frame_select(e->frame, e, FALSE);
1376         }
1377         break;
1378     case LeaveNotify:
1379         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1380             (f = find_active_menu()) && f->selected == e &&
1381             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1382         {
1383             menu_frame_select(e->frame, NULL, FALSE);
1384         }
1385     case MotionNotify:   
1386         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1387                                         ev->xmotion.y_root)))
1388             menu_frame_select(e->frame, e, FALSE);
1389         break;
1390     case KeyPress:
1391         ret = event_handle_menu_keyboard(ev);
1392         break;
1393     }
1394     return ret;
1395 }
1396
1397 static void event_handle_user_input(ObClient *client, XEvent *e)
1398 {
1399     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1400              e->type == MotionNotify || e->type == KeyPress ||
1401              e->type == KeyRelease);
1402
1403     if (menu_frame_visible) {
1404         if (event_handle_menu(e))
1405             /* don't use the event if the menu used it, but if the menu
1406                didn't use it and it's a keypress that is bound, it will
1407                close the menu and be used */
1408             return;
1409     }
1410
1411     /* if the keyboard interactive action uses the event then dont
1412        use it for bindings. likewise is moveresize uses the event. */
1413     if (!keyboard_process_interactive_grab(e, &client) &&
1414         !(moveresize_in_progress && moveresize_event(e)))
1415     {
1416         if (moveresize_in_progress)
1417             /* make further actions work on the client being
1418                moved/resized */
1419             client = moveresize_client;
1420
1421         menu_can_hide = FALSE;
1422         ob_main_loop_timeout_add(ob_main_loop,
1423                                  config_menu_hide_delay * 1000,
1424                                  menu_hide_delay_func,
1425                                  NULL, g_direct_equal, NULL);
1426
1427         if (e->type == ButtonPress ||
1428             e->type == ButtonRelease ||
1429             e->type == MotionNotify)
1430         {
1431             /* the frame may not be "visible" but they can still click on it
1432                in the case where it is animating before disappearing */
1433             if (!client || !frame_iconify_animating(client->frame))
1434                 mouse_event(client, e);
1435         } else if (e->type == KeyPress) {
1436             keyboard_event((focus_cycle_target ? focus_cycle_target :
1437                             (client ? client : focus_client)), e);
1438         }
1439     }
1440 }
1441
1442 static gboolean menu_hide_delay_func(gpointer data)
1443 {
1444     menu_can_hide = TRUE;
1445     return FALSE; /* no repeat */
1446 }
1447
1448 static void focus_delay_dest(gpointer data)
1449 {
1450     g_free(data);
1451 }
1452
1453 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1454 {
1455     const ObFocusDelayData *f1 = d1;
1456     return f1->client == d2;
1457 }
1458
1459 static gboolean focus_delay_func(gpointer data)
1460 {
1461     ObFocusDelayData *d = data;
1462     Time old = event_curtime;
1463
1464     event_curtime = d->time;
1465     if (focus_client != d->client) {
1466         if (client_focus(d->client) && config_focus_raise)
1467             client_raise(d->client);
1468     }
1469     event_curtime = old;
1470     return FALSE; /* no repeat */
1471 }
1472
1473 static void focus_delay_client_dest(ObClient *client, gpointer data)
1474 {
1475     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1476                                      client, FALSE);
1477 }
1478
1479 void event_halt_focus_delay()
1480 {
1481     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1482 }
1483
1484 void event_ignore_queued_enters()
1485 {
1486     GSList *saved = NULL, *it;
1487     XEvent *e;
1488                 
1489     XSync(ob_display, FALSE);
1490
1491     /* count the events */
1492     while (TRUE) {
1493         e = g_new(XEvent, 1);
1494         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1495             ObWindow *win;
1496             
1497             win = g_hash_table_lookup(window_map, &e->xany.window);
1498             if (win && WINDOW_IS_CLIENT(win))
1499                 ++ignore_enter_focus;
1500             
1501             saved = g_slist_append(saved, e);
1502         } else {
1503             g_free(e);
1504             break;
1505         }
1506     }
1507     /* put the events back */
1508     for (it = saved; it; it = g_slist_next(it)) {
1509         XPutBackEvent(ob_display, it->data);
1510         g_free(it->data);
1511     }
1512     g_slist_free(saved);
1513 }
1514
1515 gboolean event_time_after(Time t1, Time t2)
1516 {
1517     g_assert(t1 != CurrentTime);
1518     g_assert(t2 != CurrentTime);
1519
1520     /*
1521       Timestamp values wrap around (after about 49.7 days). The server, given
1522       its current time is represented by timestamp T, always interprets
1523       timestamps from clients by treating half of the timestamp space as being
1524       later in time than T.
1525       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1526     */
1527
1528     /* TIME_HALF is half of the number space of a Time type variable */
1529 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1530
1531     if (t2 >= TIME_HALF)
1532         /* t2 is in the second half so t1 might wrap around and be smaller than
1533            t2 */
1534         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1535     else
1536         /* t2 is in the first half so t1 has to come after it */
1537         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1538 }