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