]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
a focus fix? konquerer would end up losign focus on close sometimes. this is the...
[mikachu/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "modkeys.h"
35 #include "mouse.h"
36 #include "mainloop.h"
37 #include "framerender.h"
38 #include "focus.h"
39 #include "moveresize.h"
40 #include "group.h"
41 #include "stacking.h"
42 #include "extensions.h"
43 #include "translate.h"
44
45 #include <X11/Xlib.h>
46 #include <X11/keysym.h>
47 #include <X11/Xatom.h>
48 #include <glib.h>
49
50 #ifdef HAVE_SYS_SELECT_H
51 #  include <sys/select.h>
52 #endif
53 #ifdef HAVE_SIGNAL_H
54 #  include <signal.h>
55 #endif
56 #ifdef HAVE_UNISTD_H
57 #  include <unistd.h> /* for usleep() */
58 #endif
59 #ifdef XKB
60 #  include <X11/XKBlib.h>
61 #endif
62
63 #ifdef USE_SM
64 #include <X11/ICE/ICElib.h>
65 #endif
66
67 typedef struct
68 {
69     gboolean ignored;
70 } ObEventData;
71
72 typedef struct
73 {
74     ObClient *client;
75     Time time;
76 } ObFocusDelayData;
77
78 static void event_process(const XEvent *e, gpointer data);
79 static void event_handle_root(XEvent *e);
80 static gboolean event_handle_menu_keyboard(XEvent *e);
81 static gboolean event_handle_menu(XEvent *e);
82 static void event_handle_dock(ObDock *s, XEvent *e);
83 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
84 static void event_handle_client(ObClient *c, XEvent *e);
85 static void event_handle_group(ObGroup *g, XEvent *e);
86 static void event_handle_user_input(ObClient *client, XEvent *e);
87
88 static void focus_delay_dest(gpointer data);
89 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
90 static gboolean focus_delay_func(gpointer data);
91 static void focus_delay_client_dest(ObClient *client, gpointer data);
92
93 static gboolean menu_hide_delay_func(gpointer data);
94
95 /* The time for the current event being processed */
96 Time event_curtime = CurrentTime;
97
98 static guint ignore_enter_focus = 0;
99 static gboolean menu_can_hide;
100 static gboolean focus_left_screen = FALSE;
101
102 #ifdef USE_SM
103 static void ice_handler(gint fd, gpointer conn)
104 {
105     Bool b;
106     IceProcessMessages(conn, NULL, &b);
107 }
108
109 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
110                       IcePointer *watch_data)
111 {
112     static gint fd = -1;
113
114     if (opening) {
115         fd = IceConnectionNumber(conn);
116         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
117     } else {
118         ob_main_loop_fd_remove(ob_main_loop, fd);
119         fd = -1;
120     }
121 }
122 #endif
123
124 void event_startup(gboolean reconfig)
125 {
126     if (reconfig) return;
127
128     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
129
130 #ifdef USE_SM
131     IceAddConnectionWatch(ice_watch, NULL);
132 #endif
133
134     client_add_destructor(focus_delay_client_dest, NULL);
135 }
136
137 void event_shutdown(gboolean reconfig)
138 {
139     if (reconfig) return;
140
141 #ifdef USE_SM
142     IceRemoveConnectionWatch(ice_watch, NULL);
143 #endif
144
145     client_remove_destructor(focus_delay_client_dest);
146 }
147
148 static Window event_get_window(XEvent *e)
149 {
150     Window window;
151
152     /* pick a window */
153     switch (e->type) {
154     case SelectionClear:
155         window = RootWindow(ob_display, ob_screen);
156         break;
157     case MapRequest:
158         window = e->xmap.window;
159         break;
160     case UnmapNotify:
161         window = e->xunmap.window;
162         break;
163     case DestroyNotify:
164         window = e->xdestroywindow.window;
165         break;
166     case ConfigureRequest:
167         window = e->xconfigurerequest.window;
168         break;
169     case ConfigureNotify:
170         window = e->xconfigure.window;
171         break;
172     default:
173 #ifdef XKB
174         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
175             switch (((XkbAnyEvent*)e)->xkb_type) {
176             case XkbBellNotify:
177                 window = ((XkbBellNotifyEvent*)e)->window;
178             default:
179                 window = None;
180             }
181         } else
182 #endif
183 #ifdef SYNC
184         if (extensions_sync &&
185             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
186         {
187             window = None;
188         } else
189 #endif
190             window = e->xany.window;
191     }
192     return window;
193 }
194
195 static void event_set_curtime(XEvent *e)
196 {
197     Time t = CurrentTime;
198
199     /* grab the lasttime and hack up the state */
200     switch (e->type) {
201     case ButtonPress:
202     case ButtonRelease:
203         t = e->xbutton.time;
204         break;
205     case KeyPress:
206         t = e->xkey.time;
207         break;
208     case KeyRelease:
209         t = e->xkey.time;
210         break;
211     case MotionNotify:
212         t = e->xmotion.time;
213         break;
214     case PropertyNotify:
215         t = e->xproperty.time;
216         break;
217     case EnterNotify:
218     case LeaveNotify:
219         t = e->xcrossing.time;
220         break;
221     default:
222 #ifdef SYNC
223         if (extensions_sync &&
224             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
225         {
226             t = ((XSyncAlarmNotifyEvent*)e)->time;
227         }
228 #endif
229         /* if more event types are anticipated, get their timestamp
230            explicitly */
231         break;
232     }
233
234     event_curtime = t;
235 }
236
237 static void event_hack_mods(XEvent *e)
238 {
239 #ifdef XKB
240     XkbStateRec xkb_state;
241 #endif
242
243     switch (e->type) {
244     case ButtonPress:
245     case ButtonRelease:
246         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
247         break;
248     case KeyPress:
249         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
250         break;
251     case KeyRelease:
252         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
253 #ifdef XKB
254         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
255             e->xkey.state = xkb_state.compat_state;
256             break;
257         }
258 #endif
259         /* remove from the state the mask of the modifier key being released,
260            if it is a modifier key being released that is */
261         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
262         break;
263     case MotionNotify:
264         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
265         /* compress events */
266         {
267             XEvent ce;
268             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
269                                           e->type, &ce)) {
270                 e->xmotion.x_root = ce.xmotion.x_root;
271                 e->xmotion.y_root = ce.xmotion.y_root;
272             }
273         }
274         break;
275     }
276 }
277
278 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
279 {
280     gint mode = e->xfocus.mode;
281     gint detail = e->xfocus.detail;
282     Window win = e->xany.window;
283
284     if (e->type == FocusIn) {
285         /* These are ones we never want.. */
286
287         /* This means focus was given by a keyboard/mouse grab. */
288         if (mode == NotifyGrab)
289             return FALSE;
290         /* This means focus was given back from a keyboard/mouse grab. */
291         if (mode == NotifyUngrab)
292             return FALSE;
293
294         /* These are the ones we want.. */
295
296         if (win == RootWindow(ob_display, ob_screen) && !in_client_only) {
297             /* This means focus reverted off of a client */
298             if (detail == NotifyPointerRoot || detail == NotifyDetailNone ||
299                 detail == NotifyInferior)
300                 return TRUE;
301             else
302                 return FALSE;
303         }
304
305         /* This means focus moved from the root window to a client */
306         if (detail == NotifyVirtual)
307             return TRUE;
308         /* This means focus moved from one client to another */
309         if (detail == NotifyNonlinearVirtual)
310             return TRUE;
311         /* This means focus moved to the frame window */
312         if (detail == NotifyInferior && !in_client_only)
313             return TRUE;
314
315         /* Otherwise.. */
316         return FALSE;
317     } else {
318         g_assert(e->type == FocusOut);
319
320         /* These are ones we never want.. */
321
322         /* This means focus was taken by a keyboard/mouse grab. */
323         if (mode == NotifyGrab)
324             return FALSE;
325
326         /* Focus left the root window revertedto state */
327         if (win == RootWindow(ob_display, ob_screen))
328             return FALSE;
329
330         /* These are the ones we want.. */
331
332         /* This means focus moved from a client to the root window */
333         if (detail == NotifyVirtual)
334             return TRUE;
335         /* This means focus moved from one client to another */
336         if (detail == NotifyNonlinearVirtual)
337             return TRUE;
338         /* This means focus had moved to our frame window and now moved off */
339         if (detail == NotifyNonlinear)
340             return TRUE;
341
342         /* Otherwise.. */
343         return FALSE;
344     }
345 }
346
347 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
348 {
349     return e->type == FocusIn && wanted_focusevent(e, FALSE);
350 }
351
352 static Bool look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
353 {
354     return e->type == FocusIn && wanted_focusevent(e, TRUE);
355 }
356
357 static void print_focusevent(XEvent *e)
358 {
359     gint mode = e->xfocus.mode;
360     gint detail = e->xfocus.detail;
361     Window win = e->xany.window;
362     const gchar *modestr, *detailstr;
363
364     switch (mode) {
365     case NotifyNormal:       modestr="NotifyNormal";       break;
366     case NotifyGrab:         modestr="NotifyGrab";         break;
367     case NotifyUngrab:       modestr="NotifyUngrab";       break;
368     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
369     }
370     switch (detail) {
371     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
372     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
373     case NotifyInferior:    detailstr="NotifyInferior";    break;
374     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
375     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
376     case NotifyPointer:     detailstr="NotifyPointer";     break;
377     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
378     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
379     }
380
381     g_assert(modestr);
382     g_assert(detailstr);
383     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
384                   (e->xfocus.type == FocusIn ? "In" : "Out"),
385                   win,
386                   modestr, detailstr);
387
388 }
389
390 static gboolean event_ignore(XEvent *e, ObClient *client)
391 {
392     switch(e->type) {
393     case FocusIn:
394         print_focusevent(e);
395         if (!wanted_focusevent(e, FALSE))
396             return TRUE;
397         break;
398     case FocusOut:
399         print_focusevent(e);
400         if (!wanted_focusevent(e, FALSE))
401             return TRUE;
402         break;
403     }
404     return FALSE;
405 }
406
407 static void event_process(const XEvent *ec, gpointer data)
408 {
409     Window window;
410     ObGroup *group = NULL;
411     ObClient *client = NULL;
412     ObDock *dock = NULL;
413     ObDockApp *dockapp = NULL;
414     ObWindow *obwin = NULL;
415     XEvent ee, *e;
416     ObEventData *ed = data;
417
418     /* make a copy we can mangle */
419     ee = *ec;
420     e = &ee;
421
422     window = event_get_window(e);
423     if (!(e->type == PropertyNotify &&
424           (group = g_hash_table_lookup(group_map, &window))))
425         if ((obwin = g_hash_table_lookup(window_map, &window))) {
426             switch (obwin->type) {
427             case Window_Dock:
428                 dock = WINDOW_AS_DOCK(obwin);
429                 break;
430             case Window_DockApp:
431                 dockapp = WINDOW_AS_DOCKAPP(obwin);
432                 break;
433             case Window_Client:
434                 client = WINDOW_AS_CLIENT(obwin);
435                 break;
436             case Window_Menu:
437             case Window_Internal:
438                 /* not to be used for events */
439                 g_assert_not_reached();
440                 break;
441             }
442         }
443
444     event_set_curtime(e);
445     event_hack_mods(e);
446     if (event_ignore(e, client)) {
447         if (ed)
448             ed->ignored = TRUE;
449         return;
450     } else if (ed)
451             ed->ignored = FALSE;
452
453     /* deal with it in the kernel */
454
455     if (menu_frame_visible &&
456         (e->type == EnterNotify || e->type == LeaveNotify))
457     {
458         /* crossing events for menu */
459         event_handle_menu(e);
460     } else if (e->type == FocusIn) {
461         if (e->xfocus.detail == NotifyPointerRoot ||
462             e->xfocus.detail == NotifyDetailNone ||
463             e->xfocus.detail == NotifyInferior)
464         {
465             XEvent ce;
466             ob_debug_type(OB_DEBUG_FOCUS,
467                           "Focus went to pointer root/none or to our frame "
468                           "window\n");
469
470             /* If another FocusIn is in the queue then don't fallback yet. This
471                fixes the fun case of:
472                window map -> send focusin
473                window unmap -> get focusout
474                window map -> send focusin
475                get first focus out -> fall back to something (new window
476                  hasn't received focus yet, so something else) -> send focusin
477                which means the "something else" is the last thing to get a
478                focusin sent to it, so the new window doesn't end up with focus.
479
480                But if the other focus in is something like PointerRoot then we
481                still want to fall back.
482             */
483             if (XCheckIfEvent(ob_display, &ce, look_for_focusin_client, NULL)){
484                 XPutBackEvent(ob_display, &ce);
485                 ob_debug_type(OB_DEBUG_FOCUS,
486                               "  but another FocusIn is coming\n");
487             } else {
488                 /* Focus has been reverted to the root window, nothing, or to
489                    our frame window.
490
491                    FocusOut events come after UnmapNotify, so we don't need to
492                    worry about focusing an invalid window
493                 */
494
495                 /* In this case we know focus is in our screen */
496                 if (e->xfocus.detail == NotifyInferior)
497                     focus_left_screen = FALSE;
498
499                 if (!focus_left_screen)
500                     focus_fallback(TRUE);
501             }
502         } else if (client && client != focus_client) {
503             focus_left_screen = FALSE;
504             frame_adjust_focus(client->frame, TRUE);
505             focus_set_client(client);
506             client_calc_layer(client);
507         }
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_normal(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 (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
714             con = frame_context(client, e->xbutton.window);
715             con = mouse_button_frame_context(con, e->xbutton.button);
716             switch (con) {
717             case OB_FRAME_CONTEXT_MAXIMIZE:
718                 client->frame->max_press = (e->type == ButtonPress);
719                 framerender_frame(client->frame);
720                 break;
721             case OB_FRAME_CONTEXT_CLOSE:
722                 client->frame->close_press = (e->type == ButtonPress);
723                 framerender_frame(client->frame);
724                 break;
725             case OB_FRAME_CONTEXT_ICONIFY:
726                 client->frame->iconify_press = (e->type == ButtonPress);
727                 framerender_frame(client->frame);
728                 break;
729             case OB_FRAME_CONTEXT_ALLDESKTOPS:
730                 client->frame->desk_press = (e->type == ButtonPress);
731                 framerender_frame(client->frame);
732                 break; 
733             case OB_FRAME_CONTEXT_SHADE:
734                 client->frame->shade_press = (e->type == ButtonPress);
735                 framerender_frame(client->frame);
736                 break;
737             default:
738                 /* nothing changes with clicks for any other contexts */
739                 break;
740             }
741         }
742         break;
743     case LeaveNotify:
744         con = frame_context(client, e->xcrossing.window);
745         switch (con) {
746         case OB_FRAME_CONTEXT_MAXIMIZE:
747             client->frame->max_hover = FALSE;
748             frame_adjust_state(client->frame);
749             break;
750         case OB_FRAME_CONTEXT_ALLDESKTOPS:
751             client->frame->desk_hover = FALSE;
752             frame_adjust_state(client->frame);
753             break;
754         case OB_FRAME_CONTEXT_SHADE:
755             client->frame->shade_hover = FALSE;
756             frame_adjust_state(client->frame);
757             break;
758         case OB_FRAME_CONTEXT_ICONIFY:
759             client->frame->iconify_hover = FALSE;
760             frame_adjust_state(client->frame);
761             break;
762         case OB_FRAME_CONTEXT_CLOSE:
763             client->frame->close_hover = FALSE;
764             frame_adjust_state(client->frame);
765             break;
766         case OB_FRAME_CONTEXT_FRAME:
767             /* When the mouse leaves an animating window, don't use the
768                corresponding enter events. Pretend like the animating window
769                doesn't even exist..! */
770             if (frame_iconify_animating(client->frame))
771                 event_ignore_queued_enters();
772
773             ob_debug_type(OB_DEBUG_FOCUS,
774                           "%sNotify mode %d detail %d on %lx\n",
775                           (e->type == EnterNotify ? "Enter" : "Leave"),
776                           e->xcrossing.mode,
777                           e->xcrossing.detail, (client?client->window:0));
778             if (keyboard_interactively_grabbed())
779                 break;
780             if (config_focus_follow && config_focus_delay &&
781                 /* leave inferior events can happen when the mouse goes onto
782                    the window's border and then into the window before the
783                    delay is up */
784                 e->xcrossing.detail != NotifyInferior)
785             {
786                 ob_main_loop_timeout_remove_data(ob_main_loop,
787                                                  focus_delay_func,
788                                                  client, FALSE);
789             }
790             break;
791         default:
792             break;
793         }
794         break;
795     case EnterNotify:
796     {
797         gboolean nofocus = FALSE;
798
799         if (ignore_enter_focus) {
800             ignore_enter_focus--;
801             nofocus = TRUE;
802         }
803
804         con = frame_context(client, e->xcrossing.window);
805         switch (con) {
806         case OB_FRAME_CONTEXT_MAXIMIZE:
807             client->frame->max_hover = TRUE;
808             frame_adjust_state(client->frame);
809             break;
810         case OB_FRAME_CONTEXT_ALLDESKTOPS:
811             client->frame->desk_hover = TRUE;
812             frame_adjust_state(client->frame);
813             break;
814         case OB_FRAME_CONTEXT_SHADE:
815             client->frame->shade_hover = TRUE;
816             frame_adjust_state(client->frame);
817             break;
818         case OB_FRAME_CONTEXT_ICONIFY:
819             client->frame->iconify_hover = TRUE;
820             frame_adjust_state(client->frame);
821             break;
822         case OB_FRAME_CONTEXT_CLOSE:
823             client->frame->close_hover = TRUE;
824             frame_adjust_state(client->frame);
825             break;
826         case OB_FRAME_CONTEXT_FRAME:
827             if (keyboard_interactively_grabbed())
828                 break;
829             if (e->xcrossing.mode == NotifyGrab ||
830                 e->xcrossing.mode == NotifyUngrab ||
831                 /*ignore enters when we're already in the window */
832                 e->xcrossing.detail == NotifyInferior)
833             {
834                 ob_debug_type(OB_DEBUG_FOCUS,
835                               "%sNotify mode %d detail %d on %lx IGNORED\n",
836                               (e->type == EnterNotify ? "Enter" : "Leave"),
837                               e->xcrossing.mode,
838                               e->xcrossing.detail, client?client->window:0);
839             } else {
840                 ob_debug_type(OB_DEBUG_FOCUS,
841                               "%sNotify mode %d detail %d on %lx, "
842                               "focusing window: %d\n",
843                               (e->type == EnterNotify ? "Enter" : "Leave"),
844                               e->xcrossing.mode,
845                               e->xcrossing.detail, (client?client->window:0),
846                               !nofocus);
847                 if (!nofocus && config_focus_follow)
848                     event_enter_client(client);
849             }
850             break;
851         default:
852             break;
853         }
854         break;
855     }
856     case ConfigureRequest:
857         /* dont compress these unless you're going to watch for property
858            notifies in between (these can change what the configure would
859            do to the window).
860            also you can't compress stacking events
861         */
862
863         ob_debug("ConfigureRequest desktop %d wmstate %d vis %d\n",
864                  screen_desktop, client->wmstate, client->frame->visible);
865
866         /* don't allow clients to move shaded windows (fvwm does this) */
867         if (client->shaded) {
868             e->xconfigurerequest.value_mask &= ~CWX;
869             e->xconfigurerequest.value_mask &= ~CWY;
870         }
871
872         /* resize, then move, as specified in the EWMH section 7.7 */
873         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
874                                                CWX | CWY |
875                                                CWBorderWidth)) {
876             gint x, y, w, h;
877
878             if (e->xconfigurerequest.value_mask & CWBorderWidth)
879                 client->border_width = e->xconfigurerequest.border_width;
880
881             x = (e->xconfigurerequest.value_mask & CWX) ?
882                 e->xconfigurerequest.x : client->area.x;
883             y = (e->xconfigurerequest.value_mask & CWY) ?
884                 e->xconfigurerequest.y : client->area.y;
885             w = (e->xconfigurerequest.value_mask & CWWidth) ?
886                 e->xconfigurerequest.width : client->area.width;
887             h = (e->xconfigurerequest.value_mask & CWHeight) ?
888                 e->xconfigurerequest.height : client->area.height;
889
890             ob_debug("ConfigureRequest x %d %d y %d %d\n",
891                      e->xconfigurerequest.value_mask & CWX, x,
892                      e->xconfigurerequest.value_mask & CWY, y);
893
894             /* check for broken apps moving to their root position
895
896                XXX remove this some day...that would be nice. right now all
897                kde apps do this when they try activate themselves on another
898                desktop. eg. open amarok window on desktop 1, switch to desktop
899                2, click amarok tray icon. it will move by its decoration size.
900             */
901             if (x != client->area.x &&
902                 x == (client->frame->area.x + client->frame->size.left -
903                       (gint)client->border_width) &&
904                 y != client->area.y &&
905                 y == (client->frame->area.y + client->frame->size.top -
906                       (gint)client->border_width))
907             {
908                 ob_debug_type(OB_DEBUG_APP_BUGS,
909                               "Application %s is trying to move via "
910                               "ConfigureRequest to it's root window position "
911                               "but it is not using StaticGravity\n",
912                               client->title);
913                 /* don't move it */
914                 x = client->area.x;
915                 y = client->area.y;
916             }
917
918             client_find_onscreen(client, &x, &y, w, h, FALSE);
919             client_configure_full(client, x, y, w, h, FALSE, TRUE, TRUE);
920         }
921
922         if (e->xconfigurerequest.value_mask & CWStackMode) {
923             switch (e->xconfigurerequest.detail) {
924             case Below:
925             case BottomIf:
926                 /* Apps are so rude. And this is totally disconnected from
927                    activation/focus. Bleh. */
928                 /*client_lower(client);*/
929                 break;
930
931             case Above:
932             case TopIf:
933             default:
934                 /* Apps are so rude. And this is totally disconnected from
935                    activation/focus. Bleh. */
936                 /*client_raise(client);*/
937                 break;
938             }
939         }
940         break;
941     case UnmapNotify:
942         if (client->ignore_unmaps) {
943             client->ignore_unmaps--;
944             break;
945         }
946         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
947                  "ignores left %d\n",
948                  client->window, e->xunmap.event, e->xunmap.from_configure,
949                  client->ignore_unmaps);
950         client_unmanage(client);
951         break;
952     case DestroyNotify:
953         ob_debug("DestroyNotify for window 0x%x\n", client->window);
954         client_unmanage(client);
955         break;
956     case ReparentNotify:
957         /* this is when the client is first taken captive in the frame */
958         if (e->xreparent.parent == client->frame->plate) break;
959
960         /*
961           This event is quite rare and is usually handled in unmapHandler.
962           However, if the window is unmapped when the reparent event occurs,
963           the window manager never sees it because an unmap event is not sent
964           to an already unmapped window.
965         */
966
967         /* we don't want the reparent event, put it back on the stack for the
968            X server to deal with after we unmanage the window */
969         XPutBackEvent(ob_display, e);
970      
971         ob_debug("ReparentNotify for window 0x%x\n", client->window);
972         client_unmanage(client);
973         break;
974     case MapRequest:
975         ob_debug("MapRequest for 0x%lx\n", client->window);
976         if (!client->iconic) break; /* this normally doesn't happen, but if it
977                                        does, we don't want it!
978                                        it can happen now when the window is on
979                                        another desktop, but we still don't
980                                        want it! */
981         client_activate(client, FALSE, TRUE);
982         break;
983     case ClientMessage:
984         /* validate cuz we query stuff off the client here */
985         if (!client_validate(client)) break;
986
987         if (e->xclient.format != 32) return;
988
989         msgtype = e->xclient.message_type;
990         if (msgtype == prop_atoms.wm_change_state) {
991             /* compress changes into a single change */
992             while (XCheckTypedWindowEvent(ob_display, client->window,
993                                           e->type, &ce)) {
994                 /* XXX: it would be nice to compress ALL messages of a
995                    type, not just messages in a row without other
996                    message types between. */
997                 if (ce.xclient.message_type != msgtype) {
998                     XPutBackEvent(ob_display, &ce);
999                     break;
1000                 }
1001                 e->xclient = ce.xclient;
1002             }
1003             client_set_wm_state(client, e->xclient.data.l[0]);
1004         } else if (msgtype == prop_atoms.net_wm_desktop) {
1005             /* compress changes into a single change */
1006             while (XCheckTypedWindowEvent(ob_display, client->window,
1007                                           e->type, &ce)) {
1008                 /* XXX: it would be nice to compress ALL messages of a
1009                    type, not just messages in a row without other
1010                    message types between. */
1011                 if (ce.xclient.message_type != msgtype) {
1012                     XPutBackEvent(ob_display, &ce);
1013                     break;
1014                 }
1015                 e->xclient = ce.xclient;
1016             }
1017             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1018                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1019                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1020                                    FALSE);
1021         } else if (msgtype == prop_atoms.net_wm_state) {
1022             /* can't compress these */
1023             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1024                      (e->xclient.data.l[0] == 0 ? "Remove" :
1025                       e->xclient.data.l[0] == 1 ? "Add" :
1026                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1027                      e->xclient.data.l[1], e->xclient.data.l[2],
1028                      client->window);
1029             client_set_state(client, e->xclient.data.l[0],
1030                              e->xclient.data.l[1], e->xclient.data.l[2]);
1031         } else if (msgtype == prop_atoms.net_close_window) {
1032             ob_debug("net_close_window for 0x%lx\n", client->window);
1033             client_close(client);
1034         } else if (msgtype == prop_atoms.net_active_window) {
1035             ob_debug("net_active_window for 0x%lx source=%s\n",
1036                      client->window,
1037                      (e->xclient.data.l[0] == 0 ? "unknown" :
1038                       (e->xclient.data.l[0] == 1 ? "application" :
1039                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1040             /* XXX make use of data.l[2] !? */
1041             event_curtime = e->xclient.data.l[1];
1042             ob_debug_type(OB_DEBUG_APP_BUGS,
1043                           "_NET_ACTIVE_WINDOW message for window %s is "
1044                           "missing a timestamp\n", client->title);
1045             client_activate(client, FALSE,
1046                             (e->xclient.data.l[0] == 0 ||
1047                              e->xclient.data.l[0] == 2));
1048         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1049             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1050                      client->window, e->xclient.data.l[2]);
1051             if ((Atom)e->xclient.data.l[2] ==
1052                 prop_atoms.net_wm_moveresize_size_topleft ||
1053                 (Atom)e->xclient.data.l[2] ==
1054                 prop_atoms.net_wm_moveresize_size_top ||
1055                 (Atom)e->xclient.data.l[2] ==
1056                 prop_atoms.net_wm_moveresize_size_topright ||
1057                 (Atom)e->xclient.data.l[2] ==
1058                 prop_atoms.net_wm_moveresize_size_right ||
1059                 (Atom)e->xclient.data.l[2] ==
1060                 prop_atoms.net_wm_moveresize_size_right ||
1061                 (Atom)e->xclient.data.l[2] ==
1062                 prop_atoms.net_wm_moveresize_size_bottomright ||
1063                 (Atom)e->xclient.data.l[2] ==
1064                 prop_atoms.net_wm_moveresize_size_bottom ||
1065                 (Atom)e->xclient.data.l[2] ==
1066                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1067                 (Atom)e->xclient.data.l[2] ==
1068                 prop_atoms.net_wm_moveresize_size_left ||
1069                 (Atom)e->xclient.data.l[2] ==
1070                 prop_atoms.net_wm_moveresize_move ||
1071                 (Atom)e->xclient.data.l[2] ==
1072                 prop_atoms.net_wm_moveresize_size_keyboard ||
1073                 (Atom)e->xclient.data.l[2] ==
1074                 prop_atoms.net_wm_moveresize_move_keyboard) {
1075
1076                 moveresize_start(client, e->xclient.data.l[0],
1077                                  e->xclient.data.l[1], e->xclient.data.l[3],
1078                                  e->xclient.data.l[2]);
1079             }
1080             else if ((Atom)e->xclient.data.l[2] ==
1081                      prop_atoms.net_wm_moveresize_cancel)
1082                 moveresize_end(TRUE);
1083         } else if (msgtype == prop_atoms.net_moveresize_window) {
1084             gint grav, x, y, w, h;
1085
1086             if (e->xclient.data.l[0] & 0xff)
1087                 grav = e->xclient.data.l[0] & 0xff;
1088             else 
1089                 grav = client->gravity;
1090
1091             if (e->xclient.data.l[0] & 1 << 8)
1092                 x = e->xclient.data.l[1];
1093             else
1094                 x = client->area.x;
1095             if (e->xclient.data.l[0] & 1 << 9)
1096                 y = e->xclient.data.l[2];
1097             else
1098                 y = client->area.y;
1099             if (e->xclient.data.l[0] & 1 << 10)
1100                 w = e->xclient.data.l[3];
1101             else
1102                 w = client->area.width;
1103             if (e->xclient.data.l[0] & 1 << 11)
1104                 h = e->xclient.data.l[4];
1105             else
1106                 h = client->area.height;
1107
1108             ob_debug("MOVERESIZE x %d %d y %d %d\n",
1109                      e->xclient.data.l[0] & 1 << 8, x,
1110                      e->xclient.data.l[0] & 1 << 9, y);
1111             client_convert_gravity(client, grav, &x, &y, w, h);
1112             client_find_onscreen(client, &x, &y, w, h, FALSE);
1113             client_configure(client, x, y, w, h, FALSE, TRUE);
1114         }
1115         break;
1116     case PropertyNotify:
1117         /* validate cuz we query stuff off the client here */
1118         if (!client_validate(client)) break;
1119   
1120         /* compress changes to a single property into a single change */
1121         while (XCheckTypedWindowEvent(ob_display, client->window,
1122                                       e->type, &ce)) {
1123             Atom a, b;
1124
1125             /* XXX: it would be nice to compress ALL changes to a property,
1126                not just changes in a row without other props between. */
1127
1128             a = ce.xproperty.atom;
1129             b = e->xproperty.atom;
1130
1131             if (a == b)
1132                 continue;
1133             if ((a == prop_atoms.net_wm_name ||
1134                  a == prop_atoms.wm_name ||
1135                  a == prop_atoms.net_wm_icon_name ||
1136                  a == prop_atoms.wm_icon_name)
1137                 &&
1138                 (b == prop_atoms.net_wm_name ||
1139                  b == prop_atoms.wm_name ||
1140                  b == prop_atoms.net_wm_icon_name ||
1141                  b == prop_atoms.wm_icon_name)) {
1142                 continue;
1143             }
1144             if (a == prop_atoms.net_wm_icon &&
1145                 b == prop_atoms.net_wm_icon)
1146                 continue;
1147
1148             XPutBackEvent(ob_display, &ce);
1149             break;
1150         }
1151
1152         msgtype = e->xproperty.atom;
1153         if (msgtype == XA_WM_NORMAL_HINTS) {
1154             client_update_normal_hints(client);
1155             /* normal hints can make a window non-resizable */
1156             client_setup_decor_and_functions(client);
1157         } else if (msgtype == XA_WM_HINTS) {
1158             client_update_wmhints(client);
1159         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1160             client_update_transient_for(client);
1161             client_get_type(client);
1162             /* type may have changed, so update the layer */
1163             client_calc_layer(client);
1164             client_setup_decor_and_functions(client);
1165         } else if (msgtype == prop_atoms.net_wm_name ||
1166                    msgtype == prop_atoms.wm_name ||
1167                    msgtype == prop_atoms.net_wm_icon_name ||
1168                    msgtype == prop_atoms.wm_icon_name) {
1169             client_update_title(client);
1170         } else if (msgtype == prop_atoms.wm_command) {
1171             client_update_command(client);
1172         } else if (msgtype == prop_atoms.wm_class) {
1173             client_update_class(client);
1174         } else if (msgtype == prop_atoms.wm_protocols) {
1175             client_update_protocols(client);
1176             client_setup_decor_and_functions(client);
1177         }
1178         else if (msgtype == prop_atoms.net_wm_strut) {
1179             client_update_strut(client);
1180         }
1181         else if (msgtype == prop_atoms.net_wm_icon) {
1182             client_update_icons(client);
1183         }
1184         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1185             client_update_icon_geometry(client);
1186         }
1187         else if (msgtype == prop_atoms.net_wm_user_time) {
1188             client_update_user_time(client);
1189         }
1190 #ifdef SYNC
1191         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1192             client_update_sync_request_counter(client);
1193         }
1194 #endif
1195         else if (msgtype == prop_atoms.sm_client_id) {
1196             client_update_sm_client_id(client);
1197         }
1198     case ColormapNotify:
1199         client_update_colormap(client, e->xcolormap.colormap);
1200         break;
1201     default:
1202         ;
1203 #ifdef SHAPE
1204         if (extensions_shape && e->type == extensions_shape_event_basep) {
1205             client->shaped = ((XShapeEvent*)e)->shaped;
1206             frame_adjust_shape(client->frame);
1207         }
1208 #endif
1209     }
1210 }
1211
1212 static void event_handle_dock(ObDock *s, XEvent *e)
1213 {
1214     switch (e->type) {
1215     case ButtonPress:
1216         if (e->xbutton.button == 1)
1217             stacking_raise(DOCK_AS_WINDOW(s));
1218         else if (e->xbutton.button == 2)
1219             stacking_lower(DOCK_AS_WINDOW(s));
1220         break;
1221     case EnterNotify:
1222         dock_hide(FALSE);
1223         break;
1224     case LeaveNotify:
1225         dock_hide(TRUE);
1226         break;
1227     }
1228 }
1229
1230 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1231 {
1232     switch (e->type) {
1233     case MotionNotify:
1234         dock_app_drag(app, &e->xmotion);
1235         break;
1236     case UnmapNotify:
1237         if (app->ignore_unmaps) {
1238             app->ignore_unmaps--;
1239             break;
1240         }
1241         dock_remove(app, TRUE);
1242         break;
1243     case DestroyNotify:
1244         dock_remove(app, FALSE);
1245         break;
1246     case ReparentNotify:
1247         dock_remove(app, FALSE);
1248         break;
1249     case ConfigureNotify:
1250         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1251         break;
1252     }
1253 }
1254
1255 static ObMenuFrame* find_active_menu()
1256 {
1257     GList *it;
1258     ObMenuFrame *ret = NULL;
1259
1260     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1261         ret = it->data;
1262         if (ret->selected)
1263             break;
1264         ret = NULL;
1265     }
1266     return ret;
1267 }
1268
1269 static ObMenuFrame* find_active_or_last_menu()
1270 {
1271     ObMenuFrame *ret = NULL;
1272
1273     ret = find_active_menu();
1274     if (!ret && menu_frame_visible)
1275         ret = menu_frame_visible->data;
1276     return ret;
1277 }
1278
1279 static gboolean event_handle_menu_keyboard(XEvent *ev)
1280 {
1281     guint keycode, state;
1282     gunichar unikey;
1283     ObMenuFrame *frame;
1284     gboolean ret = TRUE;
1285
1286     keycode = ev->xkey.keycode;
1287     state = ev->xkey.state;
1288     unikey = translate_unichar(keycode);
1289
1290     frame = find_active_or_last_menu();
1291     if (frame == NULL)
1292         ret = FALSE;
1293
1294     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1295         /* Escape closes the active menu */
1296         menu_frame_hide(frame);
1297     }
1298
1299     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1300                                                       state == ControlMask))
1301     {
1302         /* Enter runs the active item or goes into the submenu.
1303            Control-Enter runs it without closing the menu. */
1304         if (frame->child)
1305             menu_frame_select_next(frame->child);
1306         else
1307             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1308     }
1309
1310     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1311         /* Left goes to the parent menu */
1312         menu_frame_select(frame, NULL, TRUE);
1313     }
1314
1315     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1316         /* Right goes to the selected submenu */
1317         if (frame->child) menu_frame_select_next(frame->child);
1318     }
1319
1320     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1321         menu_frame_select_previous(frame);
1322     }
1323
1324     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1325         menu_frame_select_next(frame);
1326     }
1327
1328     /* keyboard accelerator shortcuts. */
1329     else if (ev->xkey.state == 0 &&
1330              /* was it a valid key? */
1331              unikey != 0 &&
1332              /* don't bother if the menu is empty. */
1333              frame->entries)
1334     {
1335         GList *start;
1336         GList *it;
1337         ObMenuEntryFrame *found = NULL;
1338         guint num_found = 0;
1339
1340         /* start after the selected one */
1341         start = frame->entries;
1342         if (frame->selected) {
1343             for (it = start; frame->selected != it->data; it = g_list_next(it))
1344                 g_assert(it != NULL); /* nothing was selected? */
1345             /* next with wraparound */
1346             start = g_list_next(it);
1347             if (start == NULL) start = frame->entries;
1348         }
1349
1350         it = start;
1351         do {
1352             ObMenuEntryFrame *e = it->data;
1353             gunichar entrykey = 0;
1354
1355             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1356                 entrykey = e->entry->data.normal.shortcut;
1357             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1358                 entrykey = e->entry->data.submenu.submenu->shortcut;
1359
1360             if (unikey == entrykey) {
1361                 if (found == NULL) found = e;
1362                 ++num_found;
1363             }
1364
1365             /* next with wraparound */
1366             it = g_list_next(it);
1367             if (it == NULL) it = frame->entries;
1368         } while (it != start);
1369
1370         if (found) {
1371             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1372                 num_found == 1)
1373             {
1374                 menu_frame_select(frame, found, TRUE);
1375                 usleep(50000); /* highlight the item for a short bit so the
1376                                   user can see what happened */
1377                 menu_entry_frame_execute(found, state, ev->xkey.time);
1378             } else {
1379                 menu_frame_select(frame, found, TRUE);
1380                 if (num_found == 1)
1381                     menu_frame_select_next(frame->child);
1382             }
1383         } else
1384             ret = FALSE;
1385     }
1386     else
1387         ret = FALSE;
1388
1389     return ret;
1390 }
1391
1392 static gboolean event_handle_menu(XEvent *ev)
1393 {
1394     ObMenuFrame *f;
1395     ObMenuEntryFrame *e;
1396     gboolean ret = TRUE;
1397
1398     switch (ev->type) {
1399     case ButtonRelease:
1400         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1401             && menu_can_hide)
1402         {
1403             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1404                                             ev->xbutton.y_root)))
1405                 menu_entry_frame_execute(e, ev->xbutton.state,
1406                                          ev->xbutton.time);
1407             else
1408                 menu_frame_hide_all();
1409         }
1410         break;
1411     case EnterNotify:
1412         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1413             if (e->ignore_enters)
1414                 --e->ignore_enters;
1415             else
1416                 menu_frame_select(e->frame, e, FALSE);
1417         }
1418         break;
1419     case LeaveNotify:
1420         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1421             (f = find_active_menu()) && f->selected == e &&
1422             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1423         {
1424             menu_frame_select(e->frame, NULL, FALSE);
1425         }
1426     case MotionNotify:   
1427         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1428                                         ev->xmotion.y_root)))
1429             menu_frame_select(e->frame, e, FALSE);
1430         break;
1431     case KeyPress:
1432         ret = event_handle_menu_keyboard(ev);
1433         break;
1434     }
1435     return ret;
1436 }
1437
1438 static void event_handle_user_input(ObClient *client, XEvent *e)
1439 {
1440     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1441              e->type == MotionNotify || e->type == KeyPress ||
1442              e->type == KeyRelease);
1443
1444     if (menu_frame_visible) {
1445         if (event_handle_menu(e))
1446             /* don't use the event if the menu used it, but if the menu
1447                didn't use it and it's a keypress that is bound, it will
1448                close the menu and be used */
1449             return;
1450     }
1451
1452     /* if the keyboard interactive action uses the event then dont
1453        use it for bindings. likewise is moveresize uses the event. */
1454     if (!keyboard_process_interactive_grab(e, &client) &&
1455         !(moveresize_in_progress && moveresize_event(e)))
1456     {
1457         if (moveresize_in_progress)
1458             /* make further actions work on the client being
1459                moved/resized */
1460             client = moveresize_client;
1461
1462         menu_can_hide = FALSE;
1463         ob_main_loop_timeout_add(ob_main_loop,
1464                                  config_menu_hide_delay * 1000,
1465                                  menu_hide_delay_func,
1466                                  NULL, g_direct_equal, NULL);
1467
1468         if (e->type == ButtonPress ||
1469             e->type == ButtonRelease ||
1470             e->type == MotionNotify)
1471         {
1472             /* the frame may not be "visible" but they can still click on it
1473                in the case where it is animating before disappearing */
1474             if (!client || !frame_iconify_animating(client->frame))
1475                 mouse_event(client, e);
1476         } else if (e->type == KeyPress) {
1477             keyboard_event((focus_cycle_target ? focus_cycle_target :
1478                             (client ? client : focus_client)), e);
1479         }
1480     }
1481 }
1482
1483 static gboolean menu_hide_delay_func(gpointer data)
1484 {
1485     menu_can_hide = TRUE;
1486     return FALSE; /* no repeat */
1487 }
1488
1489 static void focus_delay_dest(gpointer data)
1490 {
1491     g_free(data);
1492 }
1493
1494 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1495 {
1496     const ObFocusDelayData *f1 = d1;
1497     return f1->client == d2;
1498 }
1499
1500 static gboolean focus_delay_func(gpointer data)
1501 {
1502     ObFocusDelayData *d = data;
1503     Time old = event_curtime;
1504
1505     event_curtime = d->time;
1506     if (focus_client != d->client) {
1507         if (client_focus(d->client) && config_focus_raise)
1508             client_raise(d->client);
1509     }
1510     event_curtime = old;
1511     return FALSE; /* no repeat */
1512 }
1513
1514 static void focus_delay_client_dest(ObClient *client, gpointer data)
1515 {
1516     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1517                                      client, FALSE);
1518 }
1519
1520 void event_halt_focus_delay()
1521 {
1522     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1523 }
1524
1525 void event_ignore_queued_enters()
1526 {
1527     GSList *saved = NULL, *it;
1528     XEvent *e;
1529                 
1530     XSync(ob_display, FALSE);
1531
1532     /* count the events */
1533     while (TRUE) {
1534         e = g_new(XEvent, 1);
1535         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1536             ObWindow *win;
1537             
1538             win = g_hash_table_lookup(window_map, &e->xany.window);
1539             if (win && WINDOW_IS_CLIENT(win))
1540                 ++ignore_enter_focus;
1541             
1542             saved = g_slist_append(saved, e);
1543         } else {
1544             g_free(e);
1545             break;
1546         }
1547     }
1548     /* put the events back */
1549     for (it = saved; it; it = g_slist_next(it)) {
1550         XPutBackEvent(ob_display, it->data);
1551         g_free(it->data);
1552     }
1553     g_slist_free(saved);
1554 }
1555
1556 gboolean event_time_after(Time t1, Time t2)
1557 {
1558     g_assert(t1 != CurrentTime);
1559     g_assert(t2 != CurrentTime);
1560
1561     /*
1562       Timestamp values wrap around (after about 49.7 days). The server, given
1563       its current time is represented by timestamp T, always interprets
1564       timestamps from clients by treating half of the timestamp space as being
1565       later in time than T.
1566       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1567     */
1568
1569     /* TIME_HALF is half of the number space of a Time type variable */
1570 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1571
1572     if (t2 >= TIME_HALF)
1573         /* t2 is in the second half so t1 might wrap around and be smaller than
1574            t2 */
1575         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1576     else
1577         /* t2 is in the first half so t1 has to come after it */
1578         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1579 }