]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
don't focus splash screens and toolbars and menus when you click on them. or enter...
[mikachu/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "modkeys.h"
35 #include "mouse.h"
36 #include "mainloop.h"
37 #include "framerender.h"
38 #include "focus.h"
39 #include "moveresize.h"
40 #include "group.h"
41 #include "stacking.h"
42 #include "extensions.h"
43 #include "translate.h"
44
45 #include <X11/Xlib.h>
46 #include <X11/Xatom.h>
47 #include <glib.h>
48
49 #ifdef HAVE_SYS_SELECT_H
50 #  include <sys/select.h>
51 #endif
52 #ifdef HAVE_SIGNAL_H
53 #  include <signal.h>
54 #endif
55 #ifdef HAVE_UNISTD_H
56 #  include <unistd.h> /* for usleep() */
57 #endif
58 #ifdef XKB
59 #  include <X11/XKBlib.h>
60 #endif
61
62 #ifdef USE_SM
63 #include <X11/ICE/ICElib.h>
64 #endif
65
66 typedef struct
67 {
68     gboolean ignored;
69 } ObEventData;
70
71 typedef struct
72 {
73     ObClient *client;
74     Time time;
75 } ObFocusDelayData;
76
77 static void event_process(const XEvent *e, gpointer data);
78 static void event_handle_root(XEvent *e);
79 static gboolean event_handle_menu_keyboard(XEvent *e);
80 static gboolean event_handle_menu(XEvent *e);
81 static void event_handle_dock(ObDock *s, XEvent *e);
82 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
83 static void event_handle_client(ObClient *c, XEvent *e);
84 static void event_handle_group(ObGroup *g, XEvent *e);
85 static void event_handle_user_input(ObClient *client, XEvent *e);
86
87 static void focus_delay_dest(gpointer data);
88 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
89 static gboolean focus_delay_func(gpointer data);
90 static void focus_delay_client_dest(ObClient *client, gpointer data);
91
92 static gboolean menu_hide_delay_func(gpointer data);
93
94 /* The time for the current event being processed */
95 Time event_curtime = CurrentTime;
96
97 static guint ignore_enter_focus = 0;
98 static gboolean menu_can_hide;
99 static gboolean focus_left_screen = FALSE;
100
101 #ifdef USE_SM
102 static void ice_handler(gint fd, gpointer conn)
103 {
104     Bool b;
105     IceProcessMessages(conn, NULL, &b);
106 }
107
108 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
109                       IcePointer *watch_data)
110 {
111     static gint fd = -1;
112
113     if (opening) {
114         fd = IceConnectionNumber(conn);
115         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
116     } else {
117         ob_main_loop_fd_remove(ob_main_loop, fd);
118         fd = -1;
119     }
120 }
121 #endif
122
123 void event_startup(gboolean reconfig)
124 {
125     if (reconfig) return;
126
127     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
128
129 #ifdef USE_SM
130     IceAddConnectionWatch(ice_watch, NULL);
131 #endif
132
133     client_add_destructor(focus_delay_client_dest, NULL);
134 }
135
136 void event_shutdown(gboolean reconfig)
137 {
138     if (reconfig) return;
139
140 #ifdef USE_SM
141     IceRemoveConnectionWatch(ice_watch, NULL);
142 #endif
143
144     client_remove_destructor(focus_delay_client_dest);
145 }
146
147 static Window event_get_window(XEvent *e)
148 {
149     Window window;
150
151     /* pick a window */
152     switch (e->type) {
153     case SelectionClear:
154         window = RootWindow(ob_display, ob_screen);
155         break;
156     case MapRequest:
157         window = e->xmap.window;
158         break;
159     case UnmapNotify:
160         window = e->xunmap.window;
161         break;
162     case DestroyNotify:
163         window = e->xdestroywindow.window;
164         break;
165     case ConfigureRequest:
166         window = e->xconfigurerequest.window;
167         break;
168     case ConfigureNotify:
169         window = e->xconfigure.window;
170         break;
171     default:
172 #ifdef XKB
173         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
174             switch (((XkbAnyEvent*)e)->xkb_type) {
175             case XkbBellNotify:
176                 window = ((XkbBellNotifyEvent*)e)->window;
177             default:
178                 window = None;
179             }
180         } else
181 #endif
182 #ifdef SYNC
183         if (extensions_sync &&
184             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
185         {
186             window = None;
187         } else
188 #endif
189             window = e->xany.window;
190     }
191     return window;
192 }
193
194 static void event_set_curtime(XEvent *e)
195 {
196     Time t = CurrentTime;
197
198     /* grab the lasttime and hack up the state */
199     switch (e->type) {
200     case ButtonPress:
201     case ButtonRelease:
202         t = e->xbutton.time;
203         break;
204     case KeyPress:
205         t = e->xkey.time;
206         break;
207     case KeyRelease:
208         t = e->xkey.time;
209         break;
210     case MotionNotify:
211         t = e->xmotion.time;
212         break;
213     case PropertyNotify:
214         t = e->xproperty.time;
215         break;
216     case EnterNotify:
217     case LeaveNotify:
218         t = e->xcrossing.time;
219         break;
220     default:
221 #ifdef SYNC
222         if (extensions_sync &&
223             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
224         {
225             t = ((XSyncAlarmNotifyEvent*)e)->time;
226         }
227 #endif
228         /* if more event types are anticipated, get their timestamp
229            explicitly */
230         break;
231     }
232
233     event_curtime = t;
234 }
235
236 static void event_hack_mods(XEvent *e)
237 {
238 #ifdef XKB
239     XkbStateRec xkb_state;
240 #endif
241
242     switch (e->type) {
243     case ButtonPress:
244     case ButtonRelease:
245         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
246         break;
247     case KeyPress:
248         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
249         break;
250     case KeyRelease:
251         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
252 #ifdef XKB
253         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
254             e->xkey.state = xkb_state.compat_state;
255             break;
256         }
257 #endif
258         /* remove from the state the mask of the modifier key being released,
259            if it is a modifier key being released that is */
260         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
261         break;
262     case MotionNotify:
263         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
264         /* compress events */
265         {
266             XEvent ce;
267             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
268                                           e->type, &ce)) {
269                 e->xmotion.x_root = ce.xmotion.x_root;
270                 e->xmotion.y_root = ce.xmotion.y_root;
271             }
272         }
273         break;
274     }
275 }
276
277 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
278 {
279     gint mode = e->xfocus.mode;
280     gint detail = e->xfocus.detail;
281     Window win = e->xany.window;
282
283     if (e->type == FocusIn) {
284         /* These are ones we never want.. */
285
286         /* This means focus was given by a keyboard/mouse grab. */
287         if (mode == NotifyGrab)
288             return FALSE;
289         /* This means focus was given back from a keyboard/mouse grab. */
290         if (mode == NotifyUngrab)
291             return FALSE;
292
293         /* These are the ones we want.. */
294
295         if (win == RootWindow(ob_display, ob_screen) && !in_client_only) {
296             /* This means focus reverted off of a client */
297             if (detail == NotifyPointerRoot || detail == NotifyDetailNone ||
298                 detail == NotifyInferior)
299                 return TRUE;
300             else
301                 return FALSE;
302         }
303
304         /* This means focus moved from the root window to a client */
305         if (detail == NotifyVirtual)
306             return TRUE;
307         /* This means focus moved from one client to another */
308         if (detail == NotifyNonlinearVirtual)
309             return TRUE;
310         /* This means focus moved to the frame window */
311         if (detail == NotifyInferior && !in_client_only)
312             return TRUE;
313
314         /* Otherwise.. */
315         return FALSE;
316     } else {
317         g_assert(e->type == FocusOut);
318
319         /* These are ones we never want.. */
320
321         /* This means focus was taken by a keyboard/mouse grab. */
322         if (mode == NotifyGrab)
323             return FALSE;
324
325         /* Focus left the root window revertedto state */
326         if (win == RootWindow(ob_display, ob_screen))
327             return FALSE;
328
329         /* These are the ones we want.. */
330
331         /* This means focus moved from a client to the root window */
332         if (detail == NotifyVirtual)
333             return TRUE;
334         /* This means focus moved from one client to another */
335         if (detail == NotifyNonlinearVirtual)
336             return TRUE;
337         /* This means focus had moved to our frame window and now moved off */
338         if (detail == NotifyNonlinear)
339             return TRUE;
340
341         /* Otherwise.. */
342         return FALSE;
343     }
344 }
345
346 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
347 {
348     return e->type == FocusIn && wanted_focusevent(e, FALSE);
349 }
350
351 static Bool look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
352 {
353     return e->type == FocusIn && wanted_focusevent(e, TRUE);
354 }
355
356 static void print_focusevent(XEvent *e)
357 {
358     gint mode = e->xfocus.mode;
359     gint detail = e->xfocus.detail;
360     Window win = e->xany.window;
361     const gchar *modestr, *detailstr;
362
363     switch (mode) {
364     case NotifyNormal:       modestr="NotifyNormal";       break;
365     case NotifyGrab:         modestr="NotifyGrab";         break;
366     case NotifyUngrab:       modestr="NotifyUngrab";       break;
367     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
368     }
369     switch (detail) {
370     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
371     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
372     case NotifyInferior:    detailstr="NotifyInferior";    break;
373     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
374     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
375     case NotifyPointer:     detailstr="NotifyPointer";     break;
376     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
377     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
378     }
379
380     g_assert(modestr);
381     g_assert(detailstr);
382     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
383                   (e->xfocus.type == FocusIn ? "In" : "Out"),
384                   win,
385                   modestr, detailstr);
386
387 }
388
389 static gboolean event_ignore(XEvent *e, ObClient *client)
390 {
391     switch(e->type) {
392     case FocusIn:
393         print_focusevent(e);
394         if (!wanted_focusevent(e, FALSE))
395             return TRUE;
396         break;
397     case FocusOut:
398         print_focusevent(e);
399         if (!wanted_focusevent(e, FALSE))
400             return TRUE;
401         break;
402     }
403     return FALSE;
404 }
405
406 static void event_process(const XEvent *ec, gpointer data)
407 {
408     Window window;
409     ObGroup *group = NULL;
410     ObClient *client = NULL;
411     ObDock *dock = NULL;
412     ObDockApp *dockapp = NULL;
413     ObWindow *obwin = NULL;
414     XEvent ee, *e;
415     ObEventData *ed = data;
416
417     /* make a copy we can mangle */
418     ee = *ec;
419     e = &ee;
420
421     window = event_get_window(e);
422     if (!(e->type == PropertyNotify &&
423           (group = g_hash_table_lookup(group_map, &window))))
424         if ((obwin = g_hash_table_lookup(window_map, &window))) {
425             switch (obwin->type) {
426             case Window_Dock:
427                 dock = WINDOW_AS_DOCK(obwin);
428                 break;
429             case Window_DockApp:
430                 dockapp = WINDOW_AS_DOCKAPP(obwin);
431                 break;
432             case Window_Client:
433                 client = WINDOW_AS_CLIENT(obwin);
434                 break;
435             case Window_Menu:
436             case Window_Internal:
437                 /* not to be used for events */
438                 g_assert_not_reached();
439                 break;
440             }
441         }
442
443     event_set_curtime(e);
444     event_hack_mods(e);
445     if (event_ignore(e, client)) {
446         if (ed)
447             ed->ignored = TRUE;
448         return;
449     } else if (ed)
450             ed->ignored = FALSE;
451
452     /* deal with it in the kernel */
453
454     if (menu_frame_visible &&
455         (e->type == EnterNotify || e->type == LeaveNotify))
456     {
457         /* crossing events for menu */
458         event_handle_menu(e);
459     } else if (e->type == FocusIn) {
460         if (e->xfocus.detail == NotifyPointerRoot ||
461             e->xfocus.detail == NotifyDetailNone ||
462             e->xfocus.detail == NotifyInferior)
463         {
464             XEvent ce;
465             ob_debug_type(OB_DEBUG_FOCUS,
466                           "Focus went to pointer root/none or to our frame "
467                           "window\n");
468
469             /* If another FocusIn is in the queue then don't fallback yet. This
470                fixes the fun case of:
471                window map -> send focusin
472                window unmap -> get focusout
473                window map -> send focusin
474                get first focus out -> fall back to something (new window
475                  hasn't received focus yet, so something else) -> send focusin
476                which means the "something else" is the last thing to get a
477                focusin sent to it, so the new window doesn't end up with focus.
478
479                But if the other focus in is something like PointerRoot then we
480                still want to fall back.
481             */
482             if (XCheckIfEvent(ob_display, &ce, look_for_focusin_client, NULL)){
483                 XPutBackEvent(ob_display, &ce);
484                 ob_debug_type(OB_DEBUG_FOCUS,
485                               "  but another FocusIn is coming\n");
486             } else {
487                 /* Focus has been reverted to the root window, nothing, or to
488                    our frame window.
489
490                    FocusOut events come after UnmapNotify, so we don't need to
491                    worry about focusing an invalid window
492                 */
493
494                 /* In this case we know focus is in our screen */
495                 if (e->xfocus.detail == NotifyInferior)
496                     focus_left_screen = FALSE;
497
498                 if (!focus_left_screen)
499                     focus_fallback(TRUE);
500             }
501         } else if (client && client != focus_client) {
502             focus_left_screen = FALSE;
503             frame_adjust_focus(client->frame, TRUE);
504             focus_set_client(client);
505             client_calc_layer(client);
506             client_bring_helper_windows(client);
507         }
508     } else if (e->type == FocusOut) {
509         gboolean nomove = FALSE;
510         XEvent ce;
511
512         /* Look for the followup FocusIn */
513         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
514             /* There is no FocusIn, this means focus went to a window that
515                is not being managed, or a window on another screen. */
516             Window win, root;
517             gint i;
518             guint u;
519             xerror_set_ignore(TRUE);
520             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
521                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
522                 root != RootWindow(ob_display, ob_screen))
523             {
524                 ob_debug_type(OB_DEBUG_FOCUS,
525                               "Focus went to another screen !\n");
526                 focus_left_screen = TRUE;
527             }
528             else
529                 ob_debug_type(OB_DEBUG_FOCUS,
530                               "Focus went to a black hole !\n");
531             xerror_set_ignore(FALSE);
532             /* nothing is focused */
533             focus_set_client(NULL);
534         } else if (ce.xany.window == e->xany.window) {
535             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
536             /* If focus didn't actually move anywhere, there is nothing to do*/
537             nomove = TRUE;
538         } else {
539             /* Focus did move, so process the FocusIn event */
540             ObEventData ed = { .ignored = FALSE };
541             event_process(&ce, &ed);
542             if (ed.ignored) {
543                 /* The FocusIn was ignored, this means it was on a window
544                    that isn't a client. */
545                 ob_debug_type(OB_DEBUG_FOCUS,
546                               "Focus went to an unmanaged window 0x%x !\n",
547                               ce.xfocus.window);
548                 focus_fallback(TRUE);
549             }
550         }
551
552         if (client && !nomove) {
553             frame_adjust_focus(client->frame, FALSE);
554             /* focus_set_client has already been called for sure */
555             client_calc_layer(client);
556         }
557     } else if (group)
558         event_handle_group(group, e);
559     else if (client)
560         event_handle_client(client, e);
561     else if (dockapp)
562         event_handle_dockapp(dockapp, e);
563     else if (dock)
564         event_handle_dock(dock, e);
565     else if (window == RootWindow(ob_display, ob_screen))
566         event_handle_root(e);
567     else if (e->type == MapRequest)
568         client_manage(window);
569     else if (e->type == ConfigureRequest) {
570         /* unhandled configure requests must be used to configure the
571            window directly */
572         XWindowChanges xwc;
573
574         xwc.x = e->xconfigurerequest.x;
575         xwc.y = e->xconfigurerequest.y;
576         xwc.width = e->xconfigurerequest.width;
577         xwc.height = e->xconfigurerequest.height;
578         xwc.border_width = e->xconfigurerequest.border_width;
579         xwc.sibling = e->xconfigurerequest.above;
580         xwc.stack_mode = e->xconfigurerequest.detail;
581        
582         /* we are not to be held responsible if someone sends us an
583            invalid request! */
584         xerror_set_ignore(TRUE);
585         XConfigureWindow(ob_display, window,
586                          e->xconfigurerequest.value_mask, &xwc);
587         xerror_set_ignore(FALSE);
588     }
589 #ifdef SYNC
590     else if (extensions_sync &&
591         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
592     {
593         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
594         if (se->alarm == moveresize_alarm && moveresize_in_progress)
595             moveresize_event(e);
596     }
597 #endif
598
599     if (e->type == ButtonPress || e->type == ButtonRelease ||
600         e->type == MotionNotify || e->type == KeyPress ||
601         e->type == KeyRelease)
602     {
603         event_handle_user_input(client, e);
604     }
605
606     /* if something happens and it's not from an XEvent, then we don't know
607        the time */
608     event_curtime = CurrentTime;
609 }
610
611 static void event_handle_root(XEvent *e)
612 {
613     Atom msgtype;
614      
615     switch(e->type) {
616     case SelectionClear:
617         ob_debug("Another WM has requested to replace us. Exiting.\n");
618         ob_exit_replace();
619         break;
620
621     case ClientMessage:
622         if (e->xclient.format != 32) break;
623
624         msgtype = e->xclient.message_type;
625         if (msgtype == prop_atoms.net_current_desktop) {
626             guint d = e->xclient.data.l[0];
627             if (d < screen_num_desktops) {
628                 event_curtime = e->xclient.data.l[1];
629                 if (event_curtime == 0)
630                     ob_debug_type(OB_DEBUG_APP_BUGS,
631                                   "_NET_CURRENT_DESKTOP message is missing "
632                                   "a timestamp\n");
633                 screen_set_desktop(d, TRUE);
634             }
635         } else if (msgtype == prop_atoms.net_number_of_desktops) {
636             guint d = e->xclient.data.l[0];
637             if (d > 0)
638                 screen_set_num_desktops(d);
639         } else if (msgtype == prop_atoms.net_showing_desktop) {
640             screen_show_desktop(e->xclient.data.l[0] != 0, TRUE);
641         } else if (msgtype == prop_atoms.openbox_control) {
642             if (e->xclient.data.l[0] == 1)
643                 ob_reconfigure();
644             else if (e->xclient.data.l[0] == 2)
645                 ob_restart();
646         }
647         break;
648     case PropertyNotify:
649         if (e->xproperty.atom == prop_atoms.net_desktop_names)
650             screen_update_desktop_names();
651         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
652             screen_update_layout();
653         break;
654     case ConfigureNotify:
655 #ifdef XRANDR
656         XRRUpdateConfiguration(e);
657 #endif
658         screen_resize();
659         break;
660     default:
661         ;
662     }
663 }
664
665 static void event_handle_group(ObGroup *group, XEvent *e)
666 {
667     GSList *it;
668
669     g_assert(e->type == PropertyNotify);
670
671     for (it = group->members; it; it = g_slist_next(it))
672         event_handle_client(it->data, e);
673 }
674
675 void event_enter_client(ObClient *client)
676 {
677     g_assert(config_focus_follow);
678
679     if (client_enter_focusable(client) && client_can_focus(client)) {
680         if (config_focus_delay) {
681             ObFocusDelayData *data;
682
683             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
684
685             data = g_new(ObFocusDelayData, 1);
686             data->client = client;
687             data->time = event_curtime;
688
689             ob_main_loop_timeout_add(ob_main_loop,
690                                      config_focus_delay,
691                                      focus_delay_func,
692                                      data, focus_delay_cmp, focus_delay_dest);
693         } else {
694             ObFocusDelayData data;
695             data.client = client;
696             data.time = event_curtime;
697             focus_delay_func(&data);
698         }
699     }
700 }
701
702 static void event_handle_client(ObClient *client, XEvent *e)
703 {
704     XEvent ce;
705     Atom msgtype;
706     ObFrameContext con;
707      
708     switch (e->type) {
709     case ButtonPress:
710     case ButtonRelease:
711         /* Wheel buttons don't draw because they are an instant click, so it
712            is a waste of resources to go drawing it.
713            if the user is doing an intereactive thing, or has a menu open then
714            the mouse is grabbed (possibly) and if we get these events we don't
715            want to deal with them
716         */
717         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
718             !keyboard_interactively_grabbed() &&
719             !menu_frame_visible)
720         {
721             con = frame_context(client, e->xbutton.window);
722             con = mouse_button_frame_context(con, e->xbutton.button);
723             switch (con) {
724             case OB_FRAME_CONTEXT_MAXIMIZE:
725                 client->frame->max_press = (e->type == ButtonPress);
726                 framerender_frame(client->frame);
727                 break;
728             case OB_FRAME_CONTEXT_CLOSE:
729                 client->frame->close_press = (e->type == ButtonPress);
730                 framerender_frame(client->frame);
731                 break;
732             case OB_FRAME_CONTEXT_ICONIFY:
733                 client->frame->iconify_press = (e->type == ButtonPress);
734                 framerender_frame(client->frame);
735                 break;
736             case OB_FRAME_CONTEXT_ALLDESKTOPS:
737                 client->frame->desk_press = (e->type == ButtonPress);
738                 framerender_frame(client->frame);
739                 break; 
740             case OB_FRAME_CONTEXT_SHADE:
741                 client->frame->shade_press = (e->type == ButtonPress);
742                 framerender_frame(client->frame);
743                 break;
744             default:
745                 /* nothing changes with clicks for any other contexts */
746                 break;
747             }
748         }
749         break;
750     case LeaveNotify:
751         con = frame_context(client, e->xcrossing.window);
752         switch (con) {
753         case OB_FRAME_CONTEXT_MAXIMIZE:
754             client->frame->max_hover = FALSE;
755             frame_adjust_state(client->frame);
756             break;
757         case OB_FRAME_CONTEXT_ALLDESKTOPS:
758             client->frame->desk_hover = FALSE;
759             frame_adjust_state(client->frame);
760             break;
761         case OB_FRAME_CONTEXT_SHADE:
762             client->frame->shade_hover = FALSE;
763             frame_adjust_state(client->frame);
764             break;
765         case OB_FRAME_CONTEXT_ICONIFY:
766             client->frame->iconify_hover = FALSE;
767             frame_adjust_state(client->frame);
768             break;
769         case OB_FRAME_CONTEXT_CLOSE:
770             client->frame->close_hover = FALSE;
771             frame_adjust_state(client->frame);
772             break;
773         case OB_FRAME_CONTEXT_FRAME:
774             /* When the mouse leaves an animating window, don't use the
775                corresponding enter events. Pretend like the animating window
776                doesn't even exist..! */
777             if (frame_iconify_animating(client->frame))
778                 event_ignore_queued_enters();
779
780             ob_debug_type(OB_DEBUG_FOCUS,
781                           "%sNotify mode %d detail %d on %lx\n",
782                           (e->type == EnterNotify ? "Enter" : "Leave"),
783                           e->xcrossing.mode,
784                           e->xcrossing.detail, (client?client->window:0));
785             if (keyboard_interactively_grabbed())
786                 break;
787             if (config_focus_follow && config_focus_delay &&
788                 /* leave inferior events can happen when the mouse goes onto
789                    the window's border and then into the window before the
790                    delay is up */
791                 e->xcrossing.detail != NotifyInferior)
792             {
793                 ob_main_loop_timeout_remove_data(ob_main_loop,
794                                                  focus_delay_func,
795                                                  client, FALSE);
796             }
797             break;
798         default:
799             break;
800         }
801         break;
802     case EnterNotify:
803     {
804         gboolean nofocus = FALSE;
805
806         if (ignore_enter_focus) {
807             ignore_enter_focus--;
808             nofocus = TRUE;
809         }
810
811         con = frame_context(client, e->xcrossing.window);
812         switch (con) {
813         case OB_FRAME_CONTEXT_MAXIMIZE:
814             client->frame->max_hover = TRUE;
815             frame_adjust_state(client->frame);
816             break;
817         case OB_FRAME_CONTEXT_ALLDESKTOPS:
818             client->frame->desk_hover = TRUE;
819             frame_adjust_state(client->frame);
820             break;
821         case OB_FRAME_CONTEXT_SHADE:
822             client->frame->shade_hover = TRUE;
823             frame_adjust_state(client->frame);
824             break;
825         case OB_FRAME_CONTEXT_ICONIFY:
826             client->frame->iconify_hover = TRUE;
827             frame_adjust_state(client->frame);
828             break;
829         case OB_FRAME_CONTEXT_CLOSE:
830             client->frame->close_hover = TRUE;
831             frame_adjust_state(client->frame);
832             break;
833         case OB_FRAME_CONTEXT_FRAME:
834             if (keyboard_interactively_grabbed())
835                 break;
836             if (e->xcrossing.mode == NotifyGrab ||
837                 e->xcrossing.mode == NotifyUngrab ||
838                 /*ignore enters when we're already in the window */
839                 e->xcrossing.detail == NotifyInferior)
840             {
841                 ob_debug_type(OB_DEBUG_FOCUS,
842                               "%sNotify mode %d detail %d on %lx IGNORED\n",
843                               (e->type == EnterNotify ? "Enter" : "Leave"),
844                               e->xcrossing.mode,
845                               e->xcrossing.detail, client?client->window:0);
846             } else {
847                 ob_debug_type(OB_DEBUG_FOCUS,
848                               "%sNotify mode %d detail %d on %lx, "
849                               "focusing window: %d\n",
850                               (e->type == EnterNotify ? "Enter" : "Leave"),
851                               e->xcrossing.mode,
852                               e->xcrossing.detail, (client?client->window:0),
853                               !nofocus);
854                 if (!nofocus && config_focus_follow)
855                     event_enter_client(client);
856             }
857             break;
858         default:
859             break;
860         }
861         break;
862     }
863     case ConfigureRequest:
864         /* dont compress these unless you're going to watch for property
865            notifies in between (these can change what the configure would
866            do to the window).
867            also you can't compress stacking events
868         */
869
870         ob_debug("ConfigureRequest desktop %d wmstate %d vis %d\n",
871                  screen_desktop, client->wmstate, client->frame->visible);
872
873         /* don't allow clients to move shaded windows (fvwm does this) */
874         if (client->shaded) {
875             e->xconfigurerequest.value_mask &= ~CWX;
876             e->xconfigurerequest.value_mask &= ~CWY;
877         }
878
879         /* resize, then move, as specified in the EWMH section 7.7 */
880         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
881                                                CWX | CWY |
882                                                CWBorderWidth)) {
883             gint x, y, w, h;
884
885             if (e->xconfigurerequest.value_mask & CWBorderWidth)
886                 client->border_width = e->xconfigurerequest.border_width;
887
888             x = (e->xconfigurerequest.value_mask & CWX) ?
889                 e->xconfigurerequest.x : client->area.x;
890             y = (e->xconfigurerequest.value_mask & CWY) ?
891                 e->xconfigurerequest.y : client->area.y;
892             w = (e->xconfigurerequest.value_mask & CWWidth) ?
893                 e->xconfigurerequest.width : client->area.width;
894             h = (e->xconfigurerequest.value_mask & CWHeight) ?
895                 e->xconfigurerequest.height : client->area.height;
896
897             ob_debug("ConfigureRequest x %d %d y %d %d\n",
898                      e->xconfigurerequest.value_mask & CWX, x,
899                      e->xconfigurerequest.value_mask & CWY, y);
900
901             /* check for broken apps moving to their root position
902
903                XXX remove this some day...that would be nice. right now all
904                kde apps do this when they try activate themselves on another
905                desktop. eg. open amarok window on desktop 1, switch to desktop
906                2, click amarok tray icon. it will move by its decoration size.
907             */
908             if (x != client->area.x &&
909                 x == (client->frame->area.x + client->frame->size.left -
910                       (gint)client->border_width) &&
911                 y != client->area.y &&
912                 y == (client->frame->area.y + client->frame->size.top -
913                       (gint)client->border_width))
914             {
915                 ob_debug_type(OB_DEBUG_APP_BUGS,
916                               "Application %s is trying to move via "
917                               "ConfigureRequest to it's root window position "
918                               "but it is not using StaticGravity\n",
919                               client->title);
920                 /* don't move it */
921                 x = client->area.x;
922                 y = client->area.y;
923             }
924
925             client_find_onscreen(client, &x, &y, w, h, FALSE);
926             client_configure_full(client, x, y, w, h, FALSE, TRUE, TRUE);
927         }
928
929         if (e->xconfigurerequest.value_mask & CWStackMode) {
930             switch (e->xconfigurerequest.detail) {
931             case Below:
932             case BottomIf:
933                 /* Apps are so rude. And this is totally disconnected from
934                    activation/focus. Bleh. */
935                 /*client_lower(client);*/
936                 break;
937
938             case Above:
939             case TopIf:
940             default:
941                 /* Apps are so rude. And this is totally disconnected from
942                    activation/focus. Bleh. */
943                 /*client_raise(client);*/
944                 break;
945             }
946         }
947         break;
948     case UnmapNotify:
949         if (client->ignore_unmaps) {
950             client->ignore_unmaps--;
951             break;
952         }
953         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
954                  "ignores left %d\n",
955                  client->window, e->xunmap.event, e->xunmap.from_configure,
956                  client->ignore_unmaps);
957         client_unmanage(client);
958         break;
959     case DestroyNotify:
960         ob_debug("DestroyNotify for window 0x%x\n", client->window);
961         client_unmanage(client);
962         break;
963     case ReparentNotify:
964         /* this is when the client is first taken captive in the frame */
965         if (e->xreparent.parent == client->frame->plate) break;
966
967         /*
968           This event is quite rare and is usually handled in unmapHandler.
969           However, if the window is unmapped when the reparent event occurs,
970           the window manager never sees it because an unmap event is not sent
971           to an already unmapped window.
972         */
973
974         /* we don't want the reparent event, put it back on the stack for the
975            X server to deal with after we unmanage the window */
976         XPutBackEvent(ob_display, e);
977      
978         ob_debug("ReparentNotify for window 0x%x\n", client->window);
979         client_unmanage(client);
980         break;
981     case MapRequest:
982         ob_debug("MapRequest for 0x%lx\n", client->window);
983         if (!client->iconic) break; /* this normally doesn't happen, but if it
984                                        does, we don't want it!
985                                        it can happen now when the window is on
986                                        another desktop, but we still don't
987                                        want it! */
988         client_activate(client, FALSE, TRUE);
989         break;
990     case ClientMessage:
991         /* validate cuz we query stuff off the client here */
992         if (!client_validate(client)) break;
993
994         if (e->xclient.format != 32) return;
995
996         msgtype = e->xclient.message_type;
997         if (msgtype == prop_atoms.wm_change_state) {
998             /* compress changes into a single change */
999             while (XCheckTypedWindowEvent(ob_display, client->window,
1000                                           e->type, &ce)) {
1001                 /* XXX: it would be nice to compress ALL messages of a
1002                    type, not just messages in a row without other
1003                    message types between. */
1004                 if (ce.xclient.message_type != msgtype) {
1005                     XPutBackEvent(ob_display, &ce);
1006                     break;
1007                 }
1008                 e->xclient = ce.xclient;
1009             }
1010             client_set_wm_state(client, e->xclient.data.l[0]);
1011         } else if (msgtype == prop_atoms.net_wm_desktop) {
1012             /* compress changes into a single change */
1013             while (XCheckTypedWindowEvent(ob_display, client->window,
1014                                           e->type, &ce)) {
1015                 /* XXX: it would be nice to compress ALL messages of a
1016                    type, not just messages in a row without other
1017                    message types between. */
1018                 if (ce.xclient.message_type != msgtype) {
1019                     XPutBackEvent(ob_display, &ce);
1020                     break;
1021                 }
1022                 e->xclient = ce.xclient;
1023             }
1024             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1025                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1026                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1027                                    FALSE);
1028         } else if (msgtype == prop_atoms.net_wm_state) {
1029             /* can't compress these */
1030             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1031                      (e->xclient.data.l[0] == 0 ? "Remove" :
1032                       e->xclient.data.l[0] == 1 ? "Add" :
1033                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1034                      e->xclient.data.l[1], e->xclient.data.l[2],
1035                      client->window);
1036             client_set_state(client, e->xclient.data.l[0],
1037                              e->xclient.data.l[1], e->xclient.data.l[2]);
1038         } else if (msgtype == prop_atoms.net_close_window) {
1039             ob_debug("net_close_window for 0x%lx\n", client->window);
1040             client_close(client);
1041         } else if (msgtype == prop_atoms.net_active_window) {
1042             ob_debug("net_active_window for 0x%lx source=%s\n",
1043                      client->window,
1044                      (e->xclient.data.l[0] == 0 ? "unknown" :
1045                       (e->xclient.data.l[0] == 1 ? "application" :
1046                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1047             /* XXX make use of data.l[2] !? */
1048             event_curtime = e->xclient.data.l[1];
1049             if (event_curtime == 0)
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 }