]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
support for _NET_WM_USER_TIME_WINDOW round 2 ! yay abstraction
[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 "propwin.h"
36 #include "mouse.h"
37 #include "mainloop.h"
38 #include "framerender.h"
39 #include "focus.h"
40 #include "moveresize.h"
41 #include "group.h"
42 #include "stacking.h"
43 #include "extensions.h"
44 #include "translate.h"
45
46 #include <X11/Xlib.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_user_time_window_clients(GSList *l, 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     ObClient *client = NULL;
411     ObDock *dock = NULL;
412     ObDockApp *dockapp = NULL;
413     ObWindow *obwin = NULL;
414     GSList *timewinclients = 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         !(timewinclients = propwin_get_clients(window,
425                                                OB_PROPWIN_USER_TIME)))
426         if ((obwin = g_hash_table_lookup(window_map, &window))) {
427             switch (obwin->type) {
428             case Window_Dock:
429                 dock = WINDOW_AS_DOCK(obwin);
430                 break;
431             case Window_DockApp:
432                 dockapp = WINDOW_AS_DOCKAPP(obwin);
433                 break;
434             case Window_Client:
435                 client = WINDOW_AS_CLIENT(obwin);
436                 break;
437             case Window_Menu:
438             case Window_Internal:
439                 /* not to be used for events */
440                 g_assert_not_reached();
441                 break;
442             }
443         }
444
445     event_set_curtime(e);
446     event_hack_mods(e);
447     if (event_ignore(e, client)) {
448         if (ed)
449             ed->ignored = TRUE;
450         return;
451     } else if (ed)
452             ed->ignored = FALSE;
453
454     /* deal with it in the kernel */
455
456     if (menu_frame_visible &&
457         (e->type == EnterNotify || e->type == LeaveNotify))
458     {
459         /* crossing events for menu */
460         event_handle_menu(e);
461     } else if (e->type == FocusIn) {
462         if (e->xfocus.detail == NotifyPointerRoot ||
463             e->xfocus.detail == NotifyDetailNone ||
464             e->xfocus.detail == NotifyInferior)
465         {
466             XEvent ce;
467             ob_debug_type(OB_DEBUG_FOCUS,
468                           "Focus went to pointer root/none or to our frame "
469                           "window\n");
470
471             /* If another FocusIn is in the queue then don't fallback yet. This
472                fixes the fun case of:
473                window map -> send focusin
474                window unmap -> get focusout
475                window map -> send focusin
476                get first focus out -> fall back to something (new window
477                  hasn't received focus yet, so something else) -> send focusin
478                which means the "something else" is the last thing to get a
479                focusin sent to it, so the new window doesn't end up with focus.
480
481                But if the other focus in is something like PointerRoot then we
482                still want to fall back.
483             */
484             if (XCheckIfEvent(ob_display, &ce, look_for_focusin_client, NULL)){
485                 XPutBackEvent(ob_display, &ce);
486                 ob_debug_type(OB_DEBUG_FOCUS,
487                               "  but another FocusIn is coming\n");
488             } else {
489                 /* Focus has been reverted to the root window, nothing, or to
490                    our frame window.
491
492                    FocusOut events come after UnmapNotify, so we don't need to
493                    worry about focusing an invalid window
494                 */
495
496                 /* In this case we know focus is in our screen */
497                 if (e->xfocus.detail == NotifyInferior)
498                     focus_left_screen = FALSE;
499
500                 if (!focus_left_screen)
501                     focus_fallback(TRUE);
502             }
503         } else if (client && client != focus_client) {
504             focus_left_screen = FALSE;
505             frame_adjust_focus(client->frame, TRUE);
506             focus_set_client(client);
507             client_calc_layer(client);
508             client_bring_helper_windows(client);
509         }
510     } else if (e->type == FocusOut) {
511         gboolean nomove = FALSE;
512         XEvent ce;
513
514         /* Look for the followup FocusIn */
515         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
516             /* There is no FocusIn, this means focus went to a window that
517                is not being managed, or a window on another screen. */
518             Window win, root;
519             gint i;
520             guint u;
521             xerror_set_ignore(TRUE);
522             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
523                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
524                 root != RootWindow(ob_display, ob_screen))
525             {
526                 ob_debug_type(OB_DEBUG_FOCUS,
527                               "Focus went to another screen !\n");
528                 focus_left_screen = TRUE;
529             }
530             else
531                 ob_debug_type(OB_DEBUG_FOCUS,
532                               "Focus went to a black hole !\n");
533             xerror_set_ignore(FALSE);
534             /* nothing is focused */
535             focus_set_client(NULL);
536         } else if (ce.xany.window == e->xany.window) {
537             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
538             /* If focus didn't actually move anywhere, there is nothing to do*/
539             nomove = TRUE;
540         } else {
541             /* Focus did move, so process the FocusIn event */
542             ObEventData ed = { .ignored = FALSE };
543             event_process(&ce, &ed);
544             if (ed.ignored) {
545                 /* The FocusIn was ignored, this means it was on a window
546                    that isn't a client. */
547                 ob_debug_type(OB_DEBUG_FOCUS,
548                               "Focus went to an unmanaged window 0x%x !\n",
549                               ce.xfocus.window);
550                 focus_fallback(TRUE);
551             }
552         }
553
554         if (client && !nomove) {
555             frame_adjust_focus(client->frame, FALSE);
556             /* focus_set_client has already been called for sure */
557             client_calc_layer(client);
558         }
559     } else if (timewinclients)
560         event_handle_user_time_window_clients(timewinclients, e);
561     else if (client)
562         event_handle_client(client, e);
563     else if (dockapp)
564         event_handle_dockapp(dockapp, e);
565     else if (dock)
566         event_handle_dock(dock, e);
567     else if (window == RootWindow(ob_display, ob_screen))
568         event_handle_root(e);
569     else if (e->type == MapRequest)
570         client_manage(window);
571     else if (e->type == ConfigureRequest) {
572         /* unhandled configure requests must be used to configure the
573            window directly */
574         XWindowChanges xwc;
575
576         xwc.x = e->xconfigurerequest.x;
577         xwc.y = e->xconfigurerequest.y;
578         xwc.width = e->xconfigurerequest.width;
579         xwc.height = e->xconfigurerequest.height;
580         xwc.border_width = e->xconfigurerequest.border_width;
581         xwc.sibling = e->xconfigurerequest.above;
582         xwc.stack_mode = e->xconfigurerequest.detail;
583        
584         /* we are not to be held responsible if someone sends us an
585            invalid request! */
586         xerror_set_ignore(TRUE);
587         XConfigureWindow(ob_display, window,
588                          e->xconfigurerequest.value_mask, &xwc);
589         xerror_set_ignore(FALSE);
590     }
591 #ifdef SYNC
592     else if (extensions_sync &&
593         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
594     {
595         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
596         if (se->alarm == moveresize_alarm && moveresize_in_progress)
597             moveresize_event(e);
598     }
599 #endif
600
601     if (e->type == ButtonPress || e->type == ButtonRelease ||
602         e->type == MotionNotify || e->type == KeyPress ||
603         e->type == KeyRelease)
604     {
605         event_handle_user_input(client, e);
606     }
607
608     /* if something happens and it's not from an XEvent, then we don't know
609        the time */
610     event_curtime = CurrentTime;
611 }
612
613 static void event_handle_root(XEvent *e)
614 {
615     Atom msgtype;
616      
617     switch(e->type) {
618     case SelectionClear:
619         ob_debug("Another WM has requested to replace us. Exiting.\n");
620         ob_exit_replace();
621         break;
622
623     case ClientMessage:
624         if (e->xclient.format != 32) break;
625
626         msgtype = e->xclient.message_type;
627         if (msgtype == prop_atoms.net_current_desktop) {
628             guint d = e->xclient.data.l[0];
629             if (d < screen_num_desktops) {
630                 event_curtime = e->xclient.data.l[1];
631                 if (event_curtime == 0)
632                     ob_debug_type(OB_DEBUG_APP_BUGS,
633                                   "_NET_CURRENT_DESKTOP message is missing "
634                                   "a timestamp\n");
635                 screen_set_desktop(d, TRUE);
636             }
637         } else if (msgtype == prop_atoms.net_number_of_desktops) {
638             guint d = e->xclient.data.l[0];
639             if (d > 0)
640                 screen_set_num_desktops(d);
641         } else if (msgtype == prop_atoms.net_showing_desktop) {
642             screen_show_desktop(e->xclient.data.l[0] != 0, TRUE);
643         } else if (msgtype == prop_atoms.openbox_control) {
644             if (e->xclient.data.l[0] == 1)
645                 ob_reconfigure();
646             else if (e->xclient.data.l[0] == 2)
647                 ob_restart();
648         }
649         break;
650     case PropertyNotify:
651         if (e->xproperty.atom == prop_atoms.net_desktop_names)
652             screen_update_desktop_names();
653         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
654             screen_update_layout();
655         break;
656     case ConfigureNotify:
657 #ifdef XRANDR
658         XRRUpdateConfiguration(e);
659 #endif
660         screen_resize();
661         break;
662     default:
663         ;
664     }
665 }
666
667 void event_enter_client(ObClient *client)
668 {
669     g_assert(config_focus_follow);
670
671     if (client_enter_focusable(client) && client_can_focus(client)) {
672         if (config_focus_delay) {
673             ObFocusDelayData *data;
674
675             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
676
677             data = g_new(ObFocusDelayData, 1);
678             data->client = client;
679             data->time = event_curtime;
680
681             ob_main_loop_timeout_add(ob_main_loop,
682                                      config_focus_delay,
683                                      focus_delay_func,
684                                      data, focus_delay_cmp, focus_delay_dest);
685         } else {
686             ObFocusDelayData data;
687             data.client = client;
688             data.time = event_curtime;
689             focus_delay_func(&data);
690         }
691     }
692 }
693
694 static void event_handle_user_time_window_clients(GSList *l, XEvent *e)
695 {
696     g_assert(e->type == PropertyNotify);
697     if (e->xproperty.atom == prop_atoms.net_wm_user_time) {
698         for (; l; l = g_slist_next(l))
699             client_update_user_time(l->data);
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);
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         else if (msgtype == prop_atoms.net_wm_user_time_window) {
1196             client_update_user_time_window(client);
1197         }
1198 #ifdef SYNC
1199         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1200             client_update_sync_request_counter(client);
1201         }
1202 #endif
1203     case ColormapNotify:
1204         client_update_colormap(client, e->xcolormap.colormap);
1205         break;
1206     default:
1207         ;
1208 #ifdef SHAPE
1209         if (extensions_shape && e->type == extensions_shape_event_basep) {
1210             client->shaped = ((XShapeEvent*)e)->shaped;
1211             frame_adjust_shape(client->frame);
1212         }
1213 #endif
1214     }
1215 }
1216
1217 static void event_handle_dock(ObDock *s, XEvent *e)
1218 {
1219     switch (e->type) {
1220     case ButtonPress:
1221         if (e->xbutton.button == 1)
1222             stacking_raise(DOCK_AS_WINDOW(s));
1223         else if (e->xbutton.button == 2)
1224             stacking_lower(DOCK_AS_WINDOW(s));
1225         break;
1226     case EnterNotify:
1227         dock_hide(FALSE);
1228         break;
1229     case LeaveNotify:
1230         dock_hide(TRUE);
1231         break;
1232     }
1233 }
1234
1235 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1236 {
1237     switch (e->type) {
1238     case MotionNotify:
1239         dock_app_drag(app, &e->xmotion);
1240         break;
1241     case UnmapNotify:
1242         if (app->ignore_unmaps) {
1243             app->ignore_unmaps--;
1244             break;
1245         }
1246         dock_remove(app, TRUE);
1247         break;
1248     case DestroyNotify:
1249         dock_remove(app, FALSE);
1250         break;
1251     case ReparentNotify:
1252         dock_remove(app, FALSE);
1253         break;
1254     case ConfigureNotify:
1255         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1256         break;
1257     }
1258 }
1259
1260 static ObMenuFrame* find_active_menu()
1261 {
1262     GList *it;
1263     ObMenuFrame *ret = NULL;
1264
1265     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1266         ret = it->data;
1267         if (ret->selected)
1268             break;
1269         ret = NULL;
1270     }
1271     return ret;
1272 }
1273
1274 static ObMenuFrame* find_active_or_last_menu()
1275 {
1276     ObMenuFrame *ret = NULL;
1277
1278     ret = find_active_menu();
1279     if (!ret && menu_frame_visible)
1280         ret = menu_frame_visible->data;
1281     return ret;
1282 }
1283
1284 static gboolean event_handle_menu_keyboard(XEvent *ev)
1285 {
1286     guint keycode, state;
1287     gunichar unikey;
1288     ObMenuFrame *frame;
1289     gboolean ret = TRUE;
1290
1291     keycode = ev->xkey.keycode;
1292     state = ev->xkey.state;
1293     unikey = translate_unichar(keycode);
1294
1295     frame = find_active_or_last_menu();
1296     if (frame == NULL)
1297         ret = FALSE;
1298
1299     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1300         /* Escape closes the active menu */
1301         menu_frame_hide(frame);
1302     }
1303
1304     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1305                                                       state == ControlMask))
1306     {
1307         /* Enter runs the active item or goes into the submenu.
1308            Control-Enter runs it without closing the menu. */
1309         if (frame->child)
1310             menu_frame_select_next(frame->child);
1311         else
1312             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1313     }
1314
1315     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1316         /* Left goes to the parent menu */
1317         menu_frame_select(frame, NULL, TRUE);
1318     }
1319
1320     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1321         /* Right goes to the selected submenu */
1322         if (frame->child) menu_frame_select_next(frame->child);
1323     }
1324
1325     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1326         menu_frame_select_previous(frame);
1327     }
1328
1329     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1330         menu_frame_select_next(frame);
1331     }
1332
1333     /* keyboard accelerator shortcuts. */
1334     else if (ev->xkey.state == 0 &&
1335              /* was it a valid key? */
1336              unikey != 0 &&
1337              /* don't bother if the menu is empty. */
1338              frame->entries)
1339     {
1340         GList *start;
1341         GList *it;
1342         ObMenuEntryFrame *found = NULL;
1343         guint num_found = 0;
1344
1345         /* start after the selected one */
1346         start = frame->entries;
1347         if (frame->selected) {
1348             for (it = start; frame->selected != it->data; it = g_list_next(it))
1349                 g_assert(it != NULL); /* nothing was selected? */
1350             /* next with wraparound */
1351             start = g_list_next(it);
1352             if (start == NULL) start = frame->entries;
1353         }
1354
1355         it = start;
1356         do {
1357             ObMenuEntryFrame *e = it->data;
1358             gunichar entrykey = 0;
1359
1360             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1361                 entrykey = e->entry->data.normal.shortcut;
1362             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1363                 entrykey = e->entry->data.submenu.submenu->shortcut;
1364
1365             if (unikey == entrykey) {
1366                 if (found == NULL) found = e;
1367                 ++num_found;
1368             }
1369
1370             /* next with wraparound */
1371             it = g_list_next(it);
1372             if (it == NULL) it = frame->entries;
1373         } while (it != start);
1374
1375         if (found) {
1376             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1377                 num_found == 1)
1378             {
1379                 menu_frame_select(frame, found, TRUE);
1380                 usleep(50000); /* highlight the item for a short bit so the
1381                                   user can see what happened */
1382                 menu_entry_frame_execute(found, state, ev->xkey.time);
1383             } else {
1384                 menu_frame_select(frame, found, TRUE);
1385                 if (num_found == 1)
1386                     menu_frame_select_next(frame->child);
1387             }
1388         } else
1389             ret = FALSE;
1390     }
1391     else
1392         ret = FALSE;
1393
1394     return ret;
1395 }
1396
1397 static gboolean event_handle_menu(XEvent *ev)
1398 {
1399     ObMenuFrame *f;
1400     ObMenuEntryFrame *e;
1401     gboolean ret = TRUE;
1402
1403     switch (ev->type) {
1404     case ButtonRelease:
1405         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1406             && menu_can_hide)
1407         {
1408             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1409                                             ev->xbutton.y_root)))
1410                 menu_entry_frame_execute(e, ev->xbutton.state,
1411                                          ev->xbutton.time);
1412             else
1413                 menu_frame_hide_all();
1414         }
1415         break;
1416     case EnterNotify:
1417         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1418             if (e->ignore_enters)
1419                 --e->ignore_enters;
1420             else
1421                 menu_frame_select(e->frame, e, FALSE);
1422         }
1423         break;
1424     case LeaveNotify:
1425         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1426             (f = find_active_menu()) && f->selected == e &&
1427             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1428         {
1429             menu_frame_select(e->frame, NULL, FALSE);
1430         }
1431     case MotionNotify:   
1432         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1433                                         ev->xmotion.y_root)))
1434             menu_frame_select(e->frame, e, FALSE);
1435         break;
1436     case KeyPress:
1437         ret = event_handle_menu_keyboard(ev);
1438         break;
1439     }
1440     return ret;
1441 }
1442
1443 static void event_handle_user_input(ObClient *client, XEvent *e)
1444 {
1445     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1446              e->type == MotionNotify || e->type == KeyPress ||
1447              e->type == KeyRelease);
1448
1449     if (menu_frame_visible) {
1450         if (event_handle_menu(e))
1451             /* don't use the event if the menu used it, but if the menu
1452                didn't use it and it's a keypress that is bound, it will
1453                close the menu and be used */
1454             return;
1455     }
1456
1457     /* if the keyboard interactive action uses the event then dont
1458        use it for bindings. likewise is moveresize uses the event. */
1459     if (!keyboard_process_interactive_grab(e, &client) &&
1460         !(moveresize_in_progress && moveresize_event(e)))
1461     {
1462         if (moveresize_in_progress)
1463             /* make further actions work on the client being
1464                moved/resized */
1465             client = moveresize_client;
1466
1467         menu_can_hide = FALSE;
1468         ob_main_loop_timeout_add(ob_main_loop,
1469                                  config_menu_hide_delay * 1000,
1470                                  menu_hide_delay_func,
1471                                  NULL, g_direct_equal, NULL);
1472
1473         if (e->type == ButtonPress ||
1474             e->type == ButtonRelease ||
1475             e->type == MotionNotify)
1476         {
1477             /* the frame may not be "visible" but they can still click on it
1478                in the case where it is animating before disappearing */
1479             if (!client || !frame_iconify_animating(client->frame))
1480                 mouse_event(client, e);
1481         } else if (e->type == KeyPress) {
1482             keyboard_event((focus_cycle_target ? focus_cycle_target :
1483                             (client ? client : focus_client)), e);
1484         }
1485     }
1486 }
1487
1488 static gboolean menu_hide_delay_func(gpointer data)
1489 {
1490     menu_can_hide = TRUE;
1491     return FALSE; /* no repeat */
1492 }
1493
1494 static void focus_delay_dest(gpointer data)
1495 {
1496     g_free(data);
1497 }
1498
1499 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1500 {
1501     const ObFocusDelayData *f1 = d1;
1502     return f1->client == d2;
1503 }
1504
1505 static gboolean focus_delay_func(gpointer data)
1506 {
1507     ObFocusDelayData *d = data;
1508     Time old = event_curtime;
1509
1510     event_curtime = d->time;
1511     if (focus_client != d->client) {
1512         if (client_focus(d->client) && config_focus_raise)
1513             client_raise(d->client);
1514     }
1515     event_curtime = old;
1516     return FALSE; /* no repeat */
1517 }
1518
1519 static void focus_delay_client_dest(ObClient *client, gpointer data)
1520 {
1521     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1522                                      client, FALSE);
1523 }
1524
1525 void event_halt_focus_delay()
1526 {
1527     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1528 }
1529
1530 void event_ignore_queued_enters()
1531 {
1532     GSList *saved = NULL, *it;
1533     XEvent *e;
1534                 
1535     XSync(ob_display, FALSE);
1536
1537     /* count the events */
1538     while (TRUE) {
1539         e = g_new(XEvent, 1);
1540         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1541             ObWindow *win;
1542             
1543             win = g_hash_table_lookup(window_map, &e->xany.window);
1544             if (win && WINDOW_IS_CLIENT(win))
1545                 ++ignore_enter_focus;
1546             
1547             saved = g_slist_append(saved, e);
1548         } else {
1549             g_free(e);
1550             break;
1551         }
1552     }
1553     /* put the events back */
1554     for (it = saved; it; it = g_slist_next(it)) {
1555         XPutBackEvent(ob_display, it->data);
1556         g_free(it->data);
1557     }
1558     g_slist_free(saved);
1559 }
1560
1561 gboolean event_time_after(Time t1, Time t2)
1562 {
1563     g_assert(t1 != CurrentTime);
1564     g_assert(t2 != CurrentTime);
1565
1566     /*
1567       Timestamp values wrap around (after about 49.7 days). The server, given
1568       its current time is represented by timestamp T, always interprets
1569       timestamps from clients by treating half of the timestamp space as being
1570       later in time than T.
1571       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1572     */
1573
1574     /* TIME_HALF is half of the number space of a Time type variable */
1575 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1576
1577     if (t2 >= TIME_HALF)
1578         /* t2 is in the second half so t1 might wrap around and be smaller than
1579            t2 */
1580         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1581     else
1582         /* t2 is in the first half so t1 has to come after it */
1583         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1584 }