]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
make helper windows share desktops with all their application top level windows
[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, 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_non_application_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             ob_debug_type(OB_DEBUG_APP_BUGS,
1051                           "_NET_ACTIVE_WINDOW message for window %s is "
1052                           "missing a timestamp\n", client->title);
1053             client_activate(client, FALSE,
1054                             (e->xclient.data.l[0] == 0 ||
1055                              e->xclient.data.l[0] == 2));
1056         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1057             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1058                      client->window, e->xclient.data.l[2]);
1059             if ((Atom)e->xclient.data.l[2] ==
1060                 prop_atoms.net_wm_moveresize_size_topleft ||
1061                 (Atom)e->xclient.data.l[2] ==
1062                 prop_atoms.net_wm_moveresize_size_top ||
1063                 (Atom)e->xclient.data.l[2] ==
1064                 prop_atoms.net_wm_moveresize_size_topright ||
1065                 (Atom)e->xclient.data.l[2] ==
1066                 prop_atoms.net_wm_moveresize_size_right ||
1067                 (Atom)e->xclient.data.l[2] ==
1068                 prop_atoms.net_wm_moveresize_size_right ||
1069                 (Atom)e->xclient.data.l[2] ==
1070                 prop_atoms.net_wm_moveresize_size_bottomright ||
1071                 (Atom)e->xclient.data.l[2] ==
1072                 prop_atoms.net_wm_moveresize_size_bottom ||
1073                 (Atom)e->xclient.data.l[2] ==
1074                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1075                 (Atom)e->xclient.data.l[2] ==
1076                 prop_atoms.net_wm_moveresize_size_left ||
1077                 (Atom)e->xclient.data.l[2] ==
1078                 prop_atoms.net_wm_moveresize_move ||
1079                 (Atom)e->xclient.data.l[2] ==
1080                 prop_atoms.net_wm_moveresize_size_keyboard ||
1081                 (Atom)e->xclient.data.l[2] ==
1082                 prop_atoms.net_wm_moveresize_move_keyboard) {
1083
1084                 moveresize_start(client, e->xclient.data.l[0],
1085                                  e->xclient.data.l[1], e->xclient.data.l[3],
1086                                  e->xclient.data.l[2]);
1087             }
1088             else if ((Atom)e->xclient.data.l[2] ==
1089                      prop_atoms.net_wm_moveresize_cancel)
1090                 moveresize_end(TRUE);
1091         } else if (msgtype == prop_atoms.net_moveresize_window) {
1092             gint grav, x, y, w, h;
1093
1094             if (e->xclient.data.l[0] & 0xff)
1095                 grav = e->xclient.data.l[0] & 0xff;
1096             else 
1097                 grav = client->gravity;
1098
1099             if (e->xclient.data.l[0] & 1 << 8)
1100                 x = e->xclient.data.l[1];
1101             else
1102                 x = client->area.x;
1103             if (e->xclient.data.l[0] & 1 << 9)
1104                 y = e->xclient.data.l[2];
1105             else
1106                 y = client->area.y;
1107             if (e->xclient.data.l[0] & 1 << 10)
1108                 w = e->xclient.data.l[3];
1109             else
1110                 w = client->area.width;
1111             if (e->xclient.data.l[0] & 1 << 11)
1112                 h = e->xclient.data.l[4];
1113             else
1114                 h = client->area.height;
1115
1116             ob_debug("MOVERESIZE x %d %d y %d %d\n",
1117                      e->xclient.data.l[0] & 1 << 8, x,
1118                      e->xclient.data.l[0] & 1 << 9, y);
1119             client_convert_gravity(client, grav, &x, &y, w, h);
1120             client_find_onscreen(client, &x, &y, w, h, FALSE);
1121             client_configure(client, x, y, w, h, FALSE, TRUE);
1122         }
1123         break;
1124     case PropertyNotify:
1125         /* validate cuz we query stuff off the client here */
1126         if (!client_validate(client)) break;
1127   
1128         /* compress changes to a single property into a single change */
1129         while (XCheckTypedWindowEvent(ob_display, client->window,
1130                                       e->type, &ce)) {
1131             Atom a, b;
1132
1133             /* XXX: it would be nice to compress ALL changes to a property,
1134                not just changes in a row without other props between. */
1135
1136             a = ce.xproperty.atom;
1137             b = e->xproperty.atom;
1138
1139             if (a == b)
1140                 continue;
1141             if ((a == prop_atoms.net_wm_name ||
1142                  a == prop_atoms.wm_name ||
1143                  a == prop_atoms.net_wm_icon_name ||
1144                  a == prop_atoms.wm_icon_name)
1145                 &&
1146                 (b == prop_atoms.net_wm_name ||
1147                  b == prop_atoms.wm_name ||
1148                  b == prop_atoms.net_wm_icon_name ||
1149                  b == prop_atoms.wm_icon_name)) {
1150                 continue;
1151             }
1152             if (a == prop_atoms.net_wm_icon &&
1153                 b == prop_atoms.net_wm_icon)
1154                 continue;
1155
1156             XPutBackEvent(ob_display, &ce);
1157             break;
1158         }
1159
1160         msgtype = e->xproperty.atom;
1161         if (msgtype == XA_WM_NORMAL_HINTS) {
1162             client_update_normal_hints(client);
1163             /* normal hints can make a window non-resizable */
1164             client_setup_decor_and_functions(client);
1165         } else if (msgtype == XA_WM_HINTS) {
1166             client_update_wmhints(client);
1167         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1168             client_update_transient_for(client);
1169             client_get_type(client);
1170             /* type may have changed, so update the layer */
1171             client_calc_layer(client);
1172             client_setup_decor_and_functions(client);
1173         } else if (msgtype == prop_atoms.net_wm_name ||
1174                    msgtype == prop_atoms.wm_name ||
1175                    msgtype == prop_atoms.net_wm_icon_name ||
1176                    msgtype == prop_atoms.wm_icon_name) {
1177             client_update_title(client);
1178         } else if (msgtype == prop_atoms.wm_protocols) {
1179             client_update_protocols(client);
1180             client_setup_decor_and_functions(client);
1181         }
1182         else if (msgtype == prop_atoms.net_wm_strut) {
1183             client_update_strut(client);
1184         }
1185         else if (msgtype == prop_atoms.net_wm_icon) {
1186             client_update_icons(client);
1187         }
1188         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1189             client_update_icon_geometry(client);
1190         }
1191         else if (msgtype == prop_atoms.net_wm_user_time) {
1192             client_update_user_time(client);
1193         }
1194 #ifdef SYNC
1195         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1196             client_update_sync_request_counter(client);
1197         }
1198 #endif
1199     case ColormapNotify:
1200         client_update_colormap(client, e->xcolormap.colormap);
1201         break;
1202     default:
1203         ;
1204 #ifdef SHAPE
1205         if (extensions_shape && e->type == extensions_shape_event_basep) {
1206             client->shaped = ((XShapeEvent*)e)->shaped;
1207             frame_adjust_shape(client->frame);
1208         }
1209 #endif
1210     }
1211 }
1212
1213 static void event_handle_dock(ObDock *s, XEvent *e)
1214 {
1215     switch (e->type) {
1216     case ButtonPress:
1217         if (e->xbutton.button == 1)
1218             stacking_raise(DOCK_AS_WINDOW(s));
1219         else if (e->xbutton.button == 2)
1220             stacking_lower(DOCK_AS_WINDOW(s));
1221         break;
1222     case EnterNotify:
1223         dock_hide(FALSE);
1224         break;
1225     case LeaveNotify:
1226         dock_hide(TRUE);
1227         break;
1228     }
1229 }
1230
1231 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1232 {
1233     switch (e->type) {
1234     case MotionNotify:
1235         dock_app_drag(app, &e->xmotion);
1236         break;
1237     case UnmapNotify:
1238         if (app->ignore_unmaps) {
1239             app->ignore_unmaps--;
1240             break;
1241         }
1242         dock_remove(app, TRUE);
1243         break;
1244     case DestroyNotify:
1245         dock_remove(app, FALSE);
1246         break;
1247     case ReparentNotify:
1248         dock_remove(app, FALSE);
1249         break;
1250     case ConfigureNotify:
1251         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1252         break;
1253     }
1254 }
1255
1256 static ObMenuFrame* find_active_menu()
1257 {
1258     GList *it;
1259     ObMenuFrame *ret = NULL;
1260
1261     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1262         ret = it->data;
1263         if (ret->selected)
1264             break;
1265         ret = NULL;
1266     }
1267     return ret;
1268 }
1269
1270 static ObMenuFrame* find_active_or_last_menu()
1271 {
1272     ObMenuFrame *ret = NULL;
1273
1274     ret = find_active_menu();
1275     if (!ret && menu_frame_visible)
1276         ret = menu_frame_visible->data;
1277     return ret;
1278 }
1279
1280 static gboolean event_handle_menu_keyboard(XEvent *ev)
1281 {
1282     guint keycode, state;
1283     gunichar unikey;
1284     ObMenuFrame *frame;
1285     gboolean ret = TRUE;
1286
1287     keycode = ev->xkey.keycode;
1288     state = ev->xkey.state;
1289     unikey = translate_unichar(keycode);
1290
1291     frame = find_active_or_last_menu();
1292     if (frame == NULL)
1293         ret = FALSE;
1294
1295     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1296         /* Escape closes the active menu */
1297         menu_frame_hide(frame);
1298     }
1299
1300     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1301                                                       state == ControlMask))
1302     {
1303         /* Enter runs the active item or goes into the submenu.
1304            Control-Enter runs it without closing the menu. */
1305         if (frame->child)
1306             menu_frame_select_next(frame->child);
1307         else
1308             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1309     }
1310
1311     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1312         /* Left goes to the parent menu */
1313         menu_frame_select(frame, NULL, TRUE);
1314     }
1315
1316     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1317         /* Right goes to the selected submenu */
1318         if (frame->child) menu_frame_select_next(frame->child);
1319     }
1320
1321     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1322         menu_frame_select_previous(frame);
1323     }
1324
1325     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1326         menu_frame_select_next(frame);
1327     }
1328
1329     /* keyboard accelerator shortcuts. */
1330     else if (ev->xkey.state == 0 &&
1331              /* was it a valid key? */
1332              unikey != 0 &&
1333              /* don't bother if the menu is empty. */
1334              frame->entries)
1335     {
1336         GList *start;
1337         GList *it;
1338         ObMenuEntryFrame *found = NULL;
1339         guint num_found = 0;
1340
1341         /* start after the selected one */
1342         start = frame->entries;
1343         if (frame->selected) {
1344             for (it = start; frame->selected != it->data; it = g_list_next(it))
1345                 g_assert(it != NULL); /* nothing was selected? */
1346             /* next with wraparound */
1347             start = g_list_next(it);
1348             if (start == NULL) start = frame->entries;
1349         }
1350
1351         it = start;
1352         do {
1353             ObMenuEntryFrame *e = it->data;
1354             gunichar entrykey = 0;
1355
1356             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1357                 entrykey = e->entry->data.normal.shortcut;
1358             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1359                 entrykey = e->entry->data.submenu.submenu->shortcut;
1360
1361             if (unikey == entrykey) {
1362                 if (found == NULL) found = e;
1363                 ++num_found;
1364             }
1365
1366             /* next with wraparound */
1367             it = g_list_next(it);
1368             if (it == NULL) it = frame->entries;
1369         } while (it != start);
1370
1371         if (found) {
1372             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1373                 num_found == 1)
1374             {
1375                 menu_frame_select(frame, found, TRUE);
1376                 usleep(50000); /* highlight the item for a short bit so the
1377                                   user can see what happened */
1378                 menu_entry_frame_execute(found, state, ev->xkey.time);
1379             } else {
1380                 menu_frame_select(frame, found, TRUE);
1381                 if (num_found == 1)
1382                     menu_frame_select_next(frame->child);
1383             }
1384         } else
1385             ret = FALSE;
1386     }
1387     else
1388         ret = FALSE;
1389
1390     return ret;
1391 }
1392
1393 static gboolean event_handle_menu(XEvent *ev)
1394 {
1395     ObMenuFrame *f;
1396     ObMenuEntryFrame *e;
1397     gboolean ret = TRUE;
1398
1399     switch (ev->type) {
1400     case ButtonRelease:
1401         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1402             && menu_can_hide)
1403         {
1404             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1405                                             ev->xbutton.y_root)))
1406                 menu_entry_frame_execute(e, ev->xbutton.state,
1407                                          ev->xbutton.time);
1408             else
1409                 menu_frame_hide_all();
1410         }
1411         break;
1412     case EnterNotify:
1413         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1414             if (e->ignore_enters)
1415                 --e->ignore_enters;
1416             else
1417                 menu_frame_select(e->frame, e, FALSE);
1418         }
1419         break;
1420     case LeaveNotify:
1421         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1422             (f = find_active_menu()) && f->selected == e &&
1423             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1424         {
1425             menu_frame_select(e->frame, NULL, FALSE);
1426         }
1427     case MotionNotify:   
1428         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1429                                         ev->xmotion.y_root)))
1430             menu_frame_select(e->frame, e, FALSE);
1431         break;
1432     case KeyPress:
1433         ret = event_handle_menu_keyboard(ev);
1434         break;
1435     }
1436     return ret;
1437 }
1438
1439 static void event_handle_user_input(ObClient *client, XEvent *e)
1440 {
1441     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1442              e->type == MotionNotify || e->type == KeyPress ||
1443              e->type == KeyRelease);
1444
1445     if (menu_frame_visible) {
1446         if (event_handle_menu(e))
1447             /* don't use the event if the menu used it, but if the menu
1448                didn't use it and it's a keypress that is bound, it will
1449                close the menu and be used */
1450             return;
1451     }
1452
1453     /* if the keyboard interactive action uses the event then dont
1454        use it for bindings. likewise is moveresize uses the event. */
1455     if (!keyboard_process_interactive_grab(e, &client) &&
1456         !(moveresize_in_progress && moveresize_event(e)))
1457     {
1458         if (moveresize_in_progress)
1459             /* make further actions work on the client being
1460                moved/resized */
1461             client = moveresize_client;
1462
1463         menu_can_hide = FALSE;
1464         ob_main_loop_timeout_add(ob_main_loop,
1465                                  config_menu_hide_delay * 1000,
1466                                  menu_hide_delay_func,
1467                                  NULL, g_direct_equal, NULL);
1468
1469         if (e->type == ButtonPress ||
1470             e->type == ButtonRelease ||
1471             e->type == MotionNotify)
1472         {
1473             /* the frame may not be "visible" but they can still click on it
1474                in the case where it is animating before disappearing */
1475             if (!client || !frame_iconify_animating(client->frame))
1476                 mouse_event(client, e);
1477         } else if (e->type == KeyPress) {
1478             keyboard_event((focus_cycle_target ? focus_cycle_target :
1479                             (client ? client : focus_client)), e);
1480         }
1481     }
1482 }
1483
1484 static gboolean menu_hide_delay_func(gpointer data)
1485 {
1486     menu_can_hide = TRUE;
1487     return FALSE; /* no repeat */
1488 }
1489
1490 static void focus_delay_dest(gpointer data)
1491 {
1492     g_free(data);
1493 }
1494
1495 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1496 {
1497     const ObFocusDelayData *f1 = d1;
1498     return f1->client == d2;
1499 }
1500
1501 static gboolean focus_delay_func(gpointer data)
1502 {
1503     ObFocusDelayData *d = data;
1504     Time old = event_curtime;
1505
1506     event_curtime = d->time;
1507     if (focus_client != d->client) {
1508         if (client_focus(d->client) && config_focus_raise)
1509             client_raise(d->client);
1510     }
1511     event_curtime = old;
1512     return FALSE; /* no repeat */
1513 }
1514
1515 static void focus_delay_client_dest(ObClient *client, gpointer data)
1516 {
1517     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1518                                      client, FALSE);
1519 }
1520
1521 void event_halt_focus_delay()
1522 {
1523     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1524 }
1525
1526 void event_ignore_queued_enters()
1527 {
1528     GSList *saved = NULL, *it;
1529     XEvent *e;
1530                 
1531     XSync(ob_display, FALSE);
1532
1533     /* count the events */
1534     while (TRUE) {
1535         e = g_new(XEvent, 1);
1536         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1537             ObWindow *win;
1538             
1539             win = g_hash_table_lookup(window_map, &e->xany.window);
1540             if (win && WINDOW_IS_CLIENT(win))
1541                 ++ignore_enter_focus;
1542             
1543             saved = g_slist_append(saved, e);
1544         } else {
1545             g_free(e);
1546             break;
1547         }
1548     }
1549     /* put the events back */
1550     for (it = saved; it; it = g_slist_next(it)) {
1551         XPutBackEvent(ob_display, it->data);
1552         g_free(it->data);
1553     }
1554     g_slist_free(saved);
1555 }
1556
1557 gboolean event_time_after(Time t1, Time t2)
1558 {
1559     g_assert(t1 != CurrentTime);
1560     g_assert(t2 != CurrentTime);
1561
1562     /*
1563       Timestamp values wrap around (after about 49.7 days). The server, given
1564       its current time is represented by timestamp T, always interprets
1565       timestamps from clients by treating half of the timestamp space as being
1566       later in time than T.
1567       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1568     */
1569
1570     /* TIME_HALF is half of the number space of a Time type variable */
1571 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1572
1573     if (t2 >= TIME_HALF)
1574         /* t2 is in the second half so t1 might wrap around and be smaller than
1575            t2 */
1576         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1577     else
1578         /* t2 is in the first half so t1 has to come after it */
1579         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1580 }