]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
make the modifier key code a lot better...
[dana/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "modkeys.h"
35 #include "mouse.h"
36 #include "mainloop.h"
37 #include "framerender.h"
38 #include "focus.h"
39 #include "moveresize.h"
40 #include "group.h"
41 #include "stacking.h"
42 #include "extensions.h"
43 #include "translate.h"
44
45 #include <X11/Xlib.h>
46 #include <X11/keysym.h>
47 #include <X11/Xatom.h>
48 #include <glib.h>
49
50 #ifdef HAVE_SYS_SELECT_H
51 #  include <sys/select.h>
52 #endif
53 #ifdef HAVE_SIGNAL_H
54 #  include <signal.h>
55 #endif
56 #ifdef HAVE_UNISTD_H
57 #  include <unistd.h> /* for usleep() */
58 #endif
59 #ifdef XKB
60 #  include <X11/XKBlib.h>
61 #endif
62
63 #ifdef USE_SM
64 #include <X11/ICE/ICElib.h>
65 #endif
66
67 typedef struct
68 {
69     gboolean ignored;
70 } ObEventData;
71
72 typedef struct
73 {
74     ObClient *client;
75     Time time;
76 } ObFocusDelayData;
77
78 static void event_process(const XEvent *e, gpointer data);
79 static void event_handle_root(XEvent *e);
80 static gboolean event_handle_menu_keyboard(XEvent *e);
81 static gboolean event_handle_menu(XEvent *e);
82 static void event_handle_dock(ObDock *s, XEvent *e);
83 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
84 static void event_handle_client(ObClient *c, XEvent *e);
85 static void event_handle_group(ObGroup *g, XEvent *e);
86
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
100 #ifdef USE_SM
101 static void ice_handler(gint fd, gpointer conn)
102 {
103     Bool b;
104     IceProcessMessages(conn, NULL, &b);
105 }
106
107 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
108                       IcePointer *watch_data)
109 {
110     static gint fd = -1;
111
112     if (opening) {
113         fd = IceConnectionNumber(conn);
114         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
115     } else {
116         ob_main_loop_fd_remove(ob_main_loop, fd);
117         fd = -1;
118     }
119 }
120 #endif
121
122 void event_startup(gboolean reconfig)
123 {
124     if (reconfig) return;
125
126     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
127
128 #ifdef USE_SM
129     IceAddConnectionWatch(ice_watch, NULL);
130 #endif
131
132     client_add_destructor(focus_delay_client_dest, NULL);
133 }
134
135 void event_shutdown(gboolean reconfig)
136 {
137     if (reconfig) return;
138
139 #ifdef USE_SM
140     IceRemoveConnectionWatch(ice_watch, NULL);
141 #endif
142
143     client_remove_destructor(focus_delay_client_dest);
144 }
145
146 static Window event_get_window(XEvent *e)
147 {
148     Window window;
149
150     /* pick a window */
151     switch (e->type) {
152     case SelectionClear:
153         window = RootWindow(ob_display, ob_screen);
154         break;
155     case MapRequest:
156         window = e->xmap.window;
157         break;
158     case UnmapNotify:
159         window = e->xunmap.window;
160         break;
161     case DestroyNotify:
162         window = e->xdestroywindow.window;
163         break;
164     case ConfigureRequest:
165         window = e->xconfigurerequest.window;
166         break;
167     case ConfigureNotify:
168         window = e->xconfigure.window;
169         break;
170     default:
171 #ifdef XKB
172         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
173             switch (((XkbAnyEvent*)e)->xkb_type) {
174             case XkbBellNotify:
175                 window = ((XkbBellNotifyEvent*)e)->window;
176             default:
177                 window = None;
178             }
179         } else
180 #endif
181 #ifdef SYNC
182         if (extensions_sync &&
183             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
184         {
185             window = None;
186         } else
187 #endif
188             window = e->xany.window;
189     }
190     return window;
191 }
192
193 static void event_set_curtime(XEvent *e)
194 {
195     Time t = CurrentTime;
196
197     /* grab the lasttime and hack up the state */
198     switch (e->type) {
199     case ButtonPress:
200     case ButtonRelease:
201         t = e->xbutton.time;
202         break;
203     case KeyPress:
204         t = e->xkey.time;
205         break;
206     case KeyRelease:
207         t = e->xkey.time;
208         break;
209     case MotionNotify:
210         t = e->xmotion.time;
211         break;
212     case PropertyNotify:
213         t = e->xproperty.time;
214         break;
215     case EnterNotify:
216     case LeaveNotify:
217         t = e->xcrossing.time;
218         break;
219     default:
220 #ifdef SYNC
221         if (extensions_sync &&
222             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
223         {
224             t = ((XSyncAlarmNotifyEvent*)e)->time;
225         }
226 #endif
227         /* if more event types are anticipated, get their timestamp
228            explicitly */
229         break;
230     }
231
232     event_curtime = t;
233 }
234
235 static void event_hack_mods(XEvent *e)
236 {
237 #ifdef XKB
238     XkbStateRec xkb_state;
239 #endif
240
241     switch (e->type) {
242     case ButtonPress:
243     case ButtonRelease:
244         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
245         break;
246     case KeyPress:
247         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
248         break;
249     case KeyRelease:
250         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
251 #ifdef XKB
252         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
253             e->xkey.state = xkb_state.compat_state;
254             break;
255         }
256 #endif
257         /* remove from the state the mask of the modifier key being released,
258            if it is a modifier key being released that is */
259         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
260         break;
261     case MotionNotify:
262         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
263         /* compress events */
264         {
265             XEvent ce;
266             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
267                                           e->type, &ce)) {
268                 e->xmotion.x_root = ce.xmotion.x_root;
269                 e->xmotion.y_root = ce.xmotion.y_root;
270             }
271         }
272         break;
273     }
274 }
275
276 static gboolean wanted_focusevent(XEvent *e)
277 {
278     gint mode = e->xfocus.mode;
279     gint detail = e->xfocus.detail;
280     Window win = e->xany.window;
281
282     if (e->type == FocusIn) {
283
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)) {
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
311         /* Otherwise.. */
312         return FALSE;
313     } else {
314         g_assert(e->type == FocusOut);
315
316
317         /* These are ones we never want.. */
318
319         /* This means focus was taken by a keyboard/mouse grab. */
320         if (mode == NotifyGrab)
321             return FALSE;
322
323         /* Focus left the root window revertedto state */
324         if (win == RootWindow(ob_display, ob_screen))
325             return FALSE;
326
327         /* These are the ones we want.. */
328
329         /* This means focus moved from a client to the root window */
330         if (detail == NotifyVirtual)
331             return TRUE;
332         /* This means focus moved from one client to another */
333         if (detail == NotifyNonlinearVirtual)
334             return TRUE;
335         /* This means focus had moved to our frame window and now moved off */
336         if (detail == NotifyNonlinear)
337             return TRUE;
338
339         /* Otherwise.. */
340         return FALSE;
341     }
342 }
343
344 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
345 {
346     return e->type == FocusIn && wanted_focusevent(e);
347 }
348
349 static gboolean event_ignore(XEvent *e, ObClient *client)
350 {
351     switch(e->type) {
352     case FocusIn:
353         if (!wanted_focusevent(e))
354             return TRUE;
355         break;
356     case FocusOut:
357         if (!wanted_focusevent(e))
358             return TRUE;
359         break;
360     }
361     return FALSE;
362 }
363
364 static void event_process(const XEvent *ec, gpointer data)
365 {
366     Window window;
367     ObGroup *group = NULL;
368     ObClient *client = NULL;
369     ObDock *dock = NULL;
370     ObDockApp *dockapp = NULL;
371     ObWindow *obwin = NULL;
372     XEvent ee, *e;
373     ObEventData *ed = data;
374
375     /* make a copy we can mangle */
376     ee = *ec;
377     e = &ee;
378
379     window = event_get_window(e);
380     if (!(e->type == PropertyNotify &&
381           (group = g_hash_table_lookup(group_map, &window))))
382         if ((obwin = g_hash_table_lookup(window_map, &window))) {
383             switch (obwin->type) {
384             case Window_Dock:
385                 dock = WINDOW_AS_DOCK(obwin);
386                 break;
387             case Window_DockApp:
388                 dockapp = WINDOW_AS_DOCKAPP(obwin);
389                 break;
390             case Window_Client:
391                 client = WINDOW_AS_CLIENT(obwin);
392                 break;
393             case Window_Menu:
394             case Window_Internal:
395                 /* not to be used for events */
396                 g_assert_not_reached();
397                 break;
398             }
399         }
400
401     event_set_curtime(e);
402     event_hack_mods(e);
403     if (event_ignore(e, client)) {
404         if (ed)
405             ed->ignored = TRUE;
406         return;
407     } else if (ed)
408             ed->ignored = FALSE;
409
410     /* deal with it in the kernel */
411
412     if (menu_frame_visible &&
413         (e->type == EnterNotify || e->type == LeaveNotify))
414     {
415         /* crossing events for menu */
416         event_handle_menu(e);
417     } else if (e->type == FocusIn) {
418         if (client && client != focus_client) {
419             frame_adjust_focus(client->frame, TRUE);
420             focus_set_client(client);
421             client_calc_layer(client);
422         }
423     } else if (e->type == FocusOut) {
424         gboolean nomove = FALSE;
425         XEvent ce;
426
427         ob_debug_type(OB_DEBUG_FOCUS, "FocusOut Event\n");
428
429         /* Look for the followup FocusIn */
430         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
431             /* There is no FocusIn, this means focus went to a window that
432                is not being managed, or a window on another screen. */
433             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to a black hole !\n");
434             /* nothing is focused */
435             focus_set_client(NULL);
436         } else if (ce.xany.window == e->xany.window) {
437             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
438             /* If focus didn't actually move anywhere, there is nothing to do*/
439             nomove = TRUE;
440         } else if (ce.xfocus.detail == NotifyPointerRoot ||
441                    ce.xfocus.detail == NotifyDetailNone ||
442                    ce.xfocus.detail == NotifyInferior) {
443             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to root\n");
444             /* Focus has been reverted to the root window or nothing
445                FocusOut events come after UnmapNotify, so we don't need to
446                worry about focusing an invalid window
447              */
448             focus_fallback(TRUE);
449         } else {
450             /* Focus did move, so process the FocusIn event */
451             ObEventData ed = { .ignored = FALSE };
452             event_process(&ce, &ed);
453             if (ed.ignored) {
454                 /* The FocusIn was ignored, this means it was on a window
455                    that isn't a client. */
456                 ob_debug_type(OB_DEBUG_FOCUS,
457                               "Focus went to an unmanaged window 0x%x !\n",
458                               ce.xfocus.window);
459                 focus_fallback(TRUE);
460             }
461         }
462
463         if (client && !nomove) {
464             frame_adjust_focus(client->frame, FALSE);
465             /* focus_set_client has already been called for sure */
466             client_calc_layer(client);
467         }
468     } else if (group)
469         event_handle_group(group, e);
470     else if (client)
471         event_handle_client(client, e);
472     else if (dockapp)
473         event_handle_dockapp(dockapp, e);
474     else if (dock)
475         event_handle_dock(dock, e);
476     else if (window == RootWindow(ob_display, ob_screen))
477         event_handle_root(e);
478     else if (e->type == MapRequest)
479         client_manage(window);
480     else if (e->type == ConfigureRequest) {
481         /* unhandled configure requests must be used to configure the
482            window directly */
483         XWindowChanges xwc;
484
485         xwc.x = e->xconfigurerequest.x;
486         xwc.y = e->xconfigurerequest.y;
487         xwc.width = e->xconfigurerequest.width;
488         xwc.height = e->xconfigurerequest.height;
489         xwc.border_width = e->xconfigurerequest.border_width;
490         xwc.sibling = e->xconfigurerequest.above;
491         xwc.stack_mode = e->xconfigurerequest.detail;
492        
493         /* we are not to be held responsible if someone sends us an
494            invalid request! */
495         xerror_set_ignore(TRUE);
496         XConfigureWindow(ob_display, window,
497                          e->xconfigurerequest.value_mask, &xwc);
498         xerror_set_ignore(FALSE);
499     }
500 #ifdef SYNC
501     else if (extensions_sync &&
502         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
503     {
504         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
505         if (se->alarm == moveresize_alarm && moveresize_in_progress)
506             moveresize_event(e);
507     }
508 #endif
509
510     /* user input (action-bound) events */
511     if (e->type == ButtonPress || e->type == ButtonRelease ||
512         e->type == MotionNotify || e->type == KeyPress ||
513         e->type == KeyRelease)
514     {
515         gboolean useevent = TRUE;
516
517         if (menu_frame_visible) {
518             if (event_handle_menu(e))
519                 /* don't use the event if the menu used it, but if the menu
520                    didn't use it and it's a keypress that is bound, it will
521                    close the menu and be used */
522                 useevent = FALSE;
523         }
524
525         if (useevent) {
526             if (!keyboard_process_interactive_grab(e, &client)) {
527                 if (moveresize_in_progress) {
528                     moveresize_event(e);
529
530                     /* make further actions work on the client being
531                        moved/resized */
532                     client = moveresize_client;
533                 }
534
535                 menu_can_hide = FALSE;
536                 ob_main_loop_timeout_add(ob_main_loop,
537                                          config_menu_hide_delay * 1000,
538                                          menu_hide_delay_func,
539                                          NULL, g_direct_equal, NULL);
540
541                 if (e->type == ButtonPress || e->type == ButtonRelease ||
542                     e->type == MotionNotify) {
543                     mouse_event(client, e);
544                 } else if (e->type == KeyPress) {
545                     keyboard_event((focus_cycle_target ? focus_cycle_target :
546                                     (client ? client : focus_client)), e);
547                 }
548             }
549         }
550     }
551     /* if something happens and it's not from an XEvent, then we don't know
552        the time */
553     event_curtime = CurrentTime;
554 }
555
556 static void event_handle_root(XEvent *e)
557 {
558     Atom msgtype;
559      
560     switch(e->type) {
561     case SelectionClear:
562         ob_debug("Another WM has requested to replace us. Exiting.\n");
563         ob_exit_replace();
564         break;
565
566     case ClientMessage:
567         if (e->xclient.format != 32) break;
568
569         msgtype = e->xclient.message_type;
570         if (msgtype == prop_atoms.net_current_desktop) {
571             guint d = e->xclient.data.l[0];
572             if (d < screen_num_desktops) {
573                 event_curtime = e->xclient.data.l[1];
574                 ob_debug("SWITCH DESKTOP TIME: %d\n", event_curtime);
575                 screen_set_desktop(d);
576             }
577         } else if (msgtype == prop_atoms.net_number_of_desktops) {
578             guint d = e->xclient.data.l[0];
579             if (d > 0)
580                 screen_set_num_desktops(d);
581         } else if (msgtype == prop_atoms.net_showing_desktop) {
582             screen_show_desktop(e->xclient.data.l[0] != 0);
583         } else if (msgtype == prop_atoms.ob_control) {
584             if (e->xclient.data.l[0] == 1)
585                 ob_reconfigure();
586             else if (e->xclient.data.l[0] == 2)
587                 ob_restart();
588         }
589         break;
590     case PropertyNotify:
591         if (e->xproperty.atom == prop_atoms.net_desktop_names)
592             screen_update_desktop_names();
593         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
594             screen_update_layout();
595         break;
596     case ConfigureNotify:
597 #ifdef XRANDR
598         XRRUpdateConfiguration(e);
599 #endif
600         screen_resize();
601         break;
602     default:
603         ;
604     }
605 }
606
607 static void event_handle_group(ObGroup *group, XEvent *e)
608 {
609     GSList *it;
610
611     g_assert(e->type == PropertyNotify);
612
613     for (it = group->members; it; it = g_slist_next(it))
614         event_handle_client(it->data, e);
615 }
616
617 void event_enter_client(ObClient *client)
618 {
619     g_assert(config_focus_follow);
620
621     if (client_normal(client) && client_can_focus(client)) {
622         if (config_focus_delay) {
623             ObFocusDelayData *data;
624
625             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
626
627             data = g_new(ObFocusDelayData, 1);
628             data->client = client;
629             data->time = event_curtime;
630
631             ob_main_loop_timeout_add(ob_main_loop,
632                                      config_focus_delay,
633                                      focus_delay_func,
634                                      data, focus_delay_cmp, focus_delay_dest);
635         } else {
636             ObFocusDelayData data;
637             data.client = client;
638             data.time = event_curtime;
639             focus_delay_func(&data);
640         }
641     }
642 }
643
644 static void event_handle_client(ObClient *client, XEvent *e)
645 {
646     XEvent ce;
647     Atom msgtype;
648     gint i=0;
649     ObFrameContext con;
650      
651     switch (e->type) {
652     case ButtonPress:
653     case ButtonRelease:
654         /* Wheel buttons don't draw because they are an instant click, so it
655            is a waste of resources to go drawing it. */
656         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
657             con = frame_context(client, e->xbutton.window);
658             con = mouse_button_frame_context(con, e->xbutton.button);
659             switch (con) {
660             case OB_FRAME_CONTEXT_MAXIMIZE:
661                 client->frame->max_press = (e->type == ButtonPress);
662                 framerender_frame(client->frame);
663                 break;
664             case OB_FRAME_CONTEXT_CLOSE:
665                 client->frame->close_press = (e->type == ButtonPress);
666                 framerender_frame(client->frame);
667                 break;
668             case OB_FRAME_CONTEXT_ICONIFY:
669                 client->frame->iconify_press = (e->type == ButtonPress);
670                 framerender_frame(client->frame);
671                 break;
672             case OB_FRAME_CONTEXT_ALLDESKTOPS:
673                 client->frame->desk_press = (e->type == ButtonPress);
674                 framerender_frame(client->frame);
675                 break; 
676             case OB_FRAME_CONTEXT_SHADE:
677                 client->frame->shade_press = (e->type == ButtonPress);
678                 framerender_frame(client->frame);
679                 break;
680             default:
681                 /* nothing changes with clicks for any other contexts */
682                 break;
683             }
684         }
685         break;
686     case LeaveNotify:
687         con = frame_context(client, e->xcrossing.window);
688         switch (con) {
689         case OB_FRAME_CONTEXT_MAXIMIZE:
690             client->frame->max_hover = FALSE;
691             frame_adjust_state(client->frame);
692             break;
693         case OB_FRAME_CONTEXT_ALLDESKTOPS:
694             client->frame->desk_hover = FALSE;
695             frame_adjust_state(client->frame);
696             break;
697         case OB_FRAME_CONTEXT_SHADE:
698             client->frame->shade_hover = FALSE;
699             frame_adjust_state(client->frame);
700             break;
701         case OB_FRAME_CONTEXT_ICONIFY:
702             client->frame->iconify_hover = FALSE;
703             frame_adjust_state(client->frame);
704             break;
705         case OB_FRAME_CONTEXT_CLOSE:
706             client->frame->close_hover = FALSE;
707             frame_adjust_state(client->frame);
708             break;
709         case OB_FRAME_CONTEXT_FRAME:
710             ob_debug_type(OB_DEBUG_FOCUS,
711                           "%sNotify mode %d detail %d on %lx\n",
712                           (e->type == EnterNotify ? "Enter" : "Leave"),
713                           e->xcrossing.mode,
714                           e->xcrossing.detail, (client?client->window:0));
715             if (keyboard_interactively_grabbed())
716                 break;
717             if (config_focus_follow && config_focus_delay &&
718                 /* leave inferior events can happen when the mouse goes onto
719                    the window's border and then into the window before the
720                    delay is up */
721                 e->xcrossing.detail != NotifyInferior)
722             {
723                 ob_main_loop_timeout_remove_data(ob_main_loop,
724                                                  focus_delay_func,
725                                                  client, FALSE);
726             }
727             break;
728         default:
729             break;
730         }
731         break;
732     case EnterNotify:
733     {
734         gboolean nofocus = FALSE;
735
736         if (ignore_enter_focus) {
737             ignore_enter_focus--;
738             nofocus = TRUE;
739         }
740
741         con = frame_context(client, e->xcrossing.window);
742         switch (con) {
743         case OB_FRAME_CONTEXT_MAXIMIZE:
744             client->frame->max_hover = TRUE;
745             frame_adjust_state(client->frame);
746             break;
747         case OB_FRAME_CONTEXT_ALLDESKTOPS:
748             client->frame->desk_hover = TRUE;
749             frame_adjust_state(client->frame);
750             break;
751         case OB_FRAME_CONTEXT_SHADE:
752             client->frame->shade_hover = TRUE;
753             frame_adjust_state(client->frame);
754             break;
755         case OB_FRAME_CONTEXT_ICONIFY:
756             client->frame->iconify_hover = TRUE;
757             frame_adjust_state(client->frame);
758             break;
759         case OB_FRAME_CONTEXT_CLOSE:
760             client->frame->close_hover = TRUE;
761             frame_adjust_state(client->frame);
762             break;
763         case OB_FRAME_CONTEXT_FRAME:
764             if (keyboard_interactively_grabbed())
765                 break;
766             if (e->xcrossing.mode == NotifyGrab ||
767                 e->xcrossing.mode == NotifyUngrab ||
768                 /*ignore enters when we're already in the window */
769                 e->xcrossing.detail == NotifyInferior)
770             {
771                 ob_debug_type(OB_DEBUG_FOCUS,
772                               "%sNotify mode %d detail %d on %lx IGNORED\n",
773                               (e->type == EnterNotify ? "Enter" : "Leave"),
774                               e->xcrossing.mode,
775                               e->xcrossing.detail, client?client->window:0);
776             } else {
777                 ob_debug_type(OB_DEBUG_FOCUS,
778                               "%sNotify mode %d detail %d on %lx, "
779                               "focusing window: %d\n",
780                               (e->type == EnterNotify ? "Enter" : "Leave"),
781                               e->xcrossing.mode,
782                               e->xcrossing.detail, (client?client->window:0),
783                               !nofocus);
784                 if (!nofocus && config_focus_follow)
785                     event_enter_client(client);
786             }
787             break;
788         default:
789             break;
790         }
791         break;
792     }
793     case ConfigureRequest:
794         /* compress these */
795         while (XCheckTypedWindowEvent(ob_display, client->window,
796                                       ConfigureRequest, &ce)) {
797             ++i;
798             /* XXX if this causes bad things.. we can compress config req's
799                with the same mask. */
800             e->xconfigurerequest.value_mask |=
801                 ce.xconfigurerequest.value_mask;
802             if (ce.xconfigurerequest.value_mask & CWX)
803                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
804             if (ce.xconfigurerequest.value_mask & CWY)
805                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
806             if (ce.xconfigurerequest.value_mask & CWWidth)
807                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
808             if (ce.xconfigurerequest.value_mask & CWHeight)
809                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
810             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
811                 e->xconfigurerequest.border_width =
812                     ce.xconfigurerequest.border_width;
813             if (ce.xconfigurerequest.value_mask & CWStackMode)
814                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
815         }
816
817         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
818         if (client->iconic || client->shaded) return;
819
820         /* resize, then move, as specified in the EWMH section 7.7 */
821         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
822                                                CWX | CWY |
823                                                CWBorderWidth)) {
824             gint x, y, w, h;
825
826             if (e->xconfigurerequest.value_mask & CWBorderWidth)
827                 client->border_width = e->xconfigurerequest.border_width;
828
829             x = (e->xconfigurerequest.value_mask & CWX) ?
830                 e->xconfigurerequest.x : client->area.x;
831             y = (e->xconfigurerequest.value_mask & CWY) ?
832                 e->xconfigurerequest.y : client->area.y;
833             w = (e->xconfigurerequest.value_mask & CWWidth) ?
834                 e->xconfigurerequest.width : client->area.width;
835             h = (e->xconfigurerequest.value_mask & CWHeight) ?
836                 e->xconfigurerequest.height : client->area.height;
837
838             ob_debug("ConfigureRequest x %d %d y %d %d\n",
839                      e->xconfigurerequest.value_mask & CWX, x,
840                      e->xconfigurerequest.value_mask & CWY, y);
841
842             client_find_onscreen(client, &x, &y, w, h, FALSE);
843             client_configure_full(client, x, y, w, h, FALSE, TRUE, TRUE);
844         }
845
846         if (e->xconfigurerequest.value_mask & CWStackMode) {
847             switch (e->xconfigurerequest.detail) {
848             case Below:
849             case BottomIf:
850                 /* Apps are so rude. And this is totally disconnected from
851                    activation/focus. Bleh. */
852                 /*client_lower(client);*/
853                 break;
854
855             case Above:
856             case TopIf:
857             default:
858                 /* Apps are so rude. And this is totally disconnected from
859                    activation/focus. Bleh. */
860                 /*client_raise(client);*/
861                 break;
862             }
863         }
864         break;
865     case UnmapNotify:
866         if (client->ignore_unmaps) {
867             client->ignore_unmaps--;
868             break;
869         }
870         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
871                  "ignores left %d\n",
872                  client->window, e->xunmap.event, e->xunmap.from_configure,
873                  client->ignore_unmaps);
874         client_unmanage(client);
875         break;
876     case DestroyNotify:
877         ob_debug("DestroyNotify for window 0x%x\n", client->window);
878         client_unmanage(client);
879         break;
880     case ReparentNotify:
881         /* this is when the client is first taken captive in the frame */
882         if (e->xreparent.parent == client->frame->plate) break;
883
884         /*
885           This event is quite rare and is usually handled in unmapHandler.
886           However, if the window is unmapped when the reparent event occurs,
887           the window manager never sees it because an unmap event is not sent
888           to an already unmapped window.
889         */
890
891         /* we don't want the reparent event, put it back on the stack for the
892            X server to deal with after we unmanage the window */
893         XPutBackEvent(ob_display, e);
894      
895         ob_debug("ReparentNotify for window 0x%x\n", client->window);
896         client_unmanage(client);
897         break;
898     case MapRequest:
899         ob_debug("MapRequest for 0x%lx\n", client->window);
900         if (!client->iconic) break; /* this normally doesn't happen, but if it
901                                        does, we don't want it!
902                                        it can happen now when the window is on
903                                        another desktop, but we still don't
904                                        want it! */
905         client_activate(client, FALSE, TRUE);
906         break;
907     case ClientMessage:
908         /* validate cuz we query stuff off the client here */
909         if (!client_validate(client)) break;
910
911         if (e->xclient.format != 32) return;
912
913         msgtype = e->xclient.message_type;
914         if (msgtype == prop_atoms.wm_change_state) {
915             /* compress changes into a single change */
916             while (XCheckTypedWindowEvent(ob_display, client->window,
917                                           e->type, &ce)) {
918                 /* XXX: it would be nice to compress ALL messages of a
919                    type, not just messages in a row without other
920                    message types between. */
921                 if (ce.xclient.message_type != msgtype) {
922                     XPutBackEvent(ob_display, &ce);
923                     break;
924                 }
925                 e->xclient = ce.xclient;
926             }
927             client_set_wm_state(client, e->xclient.data.l[0]);
928         } else if (msgtype == prop_atoms.net_wm_desktop) {
929             /* compress changes into a single change */
930             while (XCheckTypedWindowEvent(ob_display, client->window,
931                                           e->type, &ce)) {
932                 /* XXX: it would be nice to compress ALL messages of a
933                    type, not just messages in a row without other
934                    message types between. */
935                 if (ce.xclient.message_type != msgtype) {
936                     XPutBackEvent(ob_display, &ce);
937                     break;
938                 }
939                 e->xclient = ce.xclient;
940             }
941             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
942                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
943                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
944                                    FALSE);
945         } else if (msgtype == prop_atoms.net_wm_state) {
946             /* can't compress these */
947             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
948                      (e->xclient.data.l[0] == 0 ? "Remove" :
949                       e->xclient.data.l[0] == 1 ? "Add" :
950                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
951                      e->xclient.data.l[1], e->xclient.data.l[2],
952                      client->window);
953             client_set_state(client, e->xclient.data.l[0],
954                              e->xclient.data.l[1], e->xclient.data.l[2]);
955         } else if (msgtype == prop_atoms.net_close_window) {
956             ob_debug("net_close_window for 0x%lx\n", client->window);
957             client_close(client);
958         } else if (msgtype == prop_atoms.net_active_window) {
959             ob_debug("net_active_window for 0x%lx source=%s\n",
960                      client->window,
961                      (e->xclient.data.l[0] == 0 ? "unknown" :
962                       (e->xclient.data.l[0] == 1 ? "application" :
963                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
964             /* XXX make use of data.l[2] ! */
965             event_curtime = e->xclient.data.l[1];
966             client_activate(client, FALSE,
967                             (e->xclient.data.l[0] == 0 ||
968                              e->xclient.data.l[0] == 2));
969         } else if (msgtype == prop_atoms.net_wm_moveresize) {
970             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
971                      client->window, e->xclient.data.l[2]);
972             if ((Atom)e->xclient.data.l[2] ==
973                 prop_atoms.net_wm_moveresize_size_topleft ||
974                 (Atom)e->xclient.data.l[2] ==
975                 prop_atoms.net_wm_moveresize_size_top ||
976                 (Atom)e->xclient.data.l[2] ==
977                 prop_atoms.net_wm_moveresize_size_topright ||
978                 (Atom)e->xclient.data.l[2] ==
979                 prop_atoms.net_wm_moveresize_size_right ||
980                 (Atom)e->xclient.data.l[2] ==
981                 prop_atoms.net_wm_moveresize_size_right ||
982                 (Atom)e->xclient.data.l[2] ==
983                 prop_atoms.net_wm_moveresize_size_bottomright ||
984                 (Atom)e->xclient.data.l[2] ==
985                 prop_atoms.net_wm_moveresize_size_bottom ||
986                 (Atom)e->xclient.data.l[2] ==
987                 prop_atoms.net_wm_moveresize_size_bottomleft ||
988                 (Atom)e->xclient.data.l[2] ==
989                 prop_atoms.net_wm_moveresize_size_left ||
990                 (Atom)e->xclient.data.l[2] ==
991                 prop_atoms.net_wm_moveresize_move ||
992                 (Atom)e->xclient.data.l[2] ==
993                 prop_atoms.net_wm_moveresize_size_keyboard ||
994                 (Atom)e->xclient.data.l[2] ==
995                 prop_atoms.net_wm_moveresize_move_keyboard) {
996
997                 moveresize_start(client, e->xclient.data.l[0],
998                                  e->xclient.data.l[1], e->xclient.data.l[3],
999                                  e->xclient.data.l[2]);
1000             }
1001             else if ((Atom)e->xclient.data.l[2] ==
1002                      prop_atoms.net_wm_moveresize_cancel)
1003                 moveresize_end(TRUE);
1004         } else if (msgtype == prop_atoms.net_moveresize_window) {
1005             gint grav, x, y, w, h;
1006
1007             if (e->xclient.data.l[0] & 0xff)
1008                 grav = e->xclient.data.l[0] & 0xff;
1009             else 
1010                 grav = client->gravity;
1011
1012             if (e->xclient.data.l[0] & 1 << 8)
1013                 x = e->xclient.data.l[1];
1014             else
1015                 x = client->area.x;
1016             if (e->xclient.data.l[0] & 1 << 9)
1017                 y = e->xclient.data.l[2];
1018             else
1019                 y = client->area.y;
1020             if (e->xclient.data.l[0] & 1 << 10)
1021                 w = e->xclient.data.l[3];
1022             else
1023                 w = client->area.width;
1024             if (e->xclient.data.l[0] & 1 << 11)
1025                 h = e->xclient.data.l[4];
1026             else
1027                 h = client->area.height;
1028
1029             ob_debug("MOVERESIZE x %d %d y %d %d\n",
1030                      e->xclient.data.l[0] & 1 << 8, x,
1031                      e->xclient.data.l[0] & 1 << 9, y);
1032             client_convert_gravity(client, grav, &x, &y, w, h);
1033             client_find_onscreen(client, &x, &y, w, h, FALSE);
1034             client_configure(client, x, y, w, h, FALSE, TRUE);
1035         }
1036         break;
1037     case PropertyNotify:
1038         /* validate cuz we query stuff off the client here */
1039         if (!client_validate(client)) break;
1040   
1041         /* compress changes to a single property into a single change */
1042         while (XCheckTypedWindowEvent(ob_display, client->window,
1043                                       e->type, &ce)) {
1044             Atom a, b;
1045
1046             /* XXX: it would be nice to compress ALL changes to a property,
1047                not just changes in a row without other props between. */
1048
1049             a = ce.xproperty.atom;
1050             b = e->xproperty.atom;
1051
1052             if (a == b)
1053                 continue;
1054             if ((a == prop_atoms.net_wm_name ||
1055                  a == prop_atoms.wm_name ||
1056                  a == prop_atoms.net_wm_icon_name ||
1057                  a == prop_atoms.wm_icon_name)
1058                 &&
1059                 (b == prop_atoms.net_wm_name ||
1060                  b == prop_atoms.wm_name ||
1061                  b == prop_atoms.net_wm_icon_name ||
1062                  b == prop_atoms.wm_icon_name)) {
1063                 continue;
1064             }
1065             if (a == prop_atoms.net_wm_icon &&
1066                 b == prop_atoms.net_wm_icon)
1067                 continue;
1068
1069             XPutBackEvent(ob_display, &ce);
1070             break;
1071         }
1072
1073         msgtype = e->xproperty.atom;
1074         if (msgtype == XA_WM_NORMAL_HINTS) {
1075             client_update_normal_hints(client);
1076             /* normal hints can make a window non-resizable */
1077             client_setup_decor_and_functions(client);
1078         } else if (msgtype == XA_WM_HINTS) {
1079             client_update_wmhints(client);
1080         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1081             client_update_transient_for(client);
1082             client_get_type(client);
1083             /* type may have changed, so update the layer */
1084             client_calc_layer(client);
1085             client_setup_decor_and_functions(client);
1086         } else if (msgtype == prop_atoms.net_wm_name ||
1087                    msgtype == prop_atoms.wm_name ||
1088                    msgtype == prop_atoms.net_wm_icon_name ||
1089                    msgtype == prop_atoms.wm_icon_name) {
1090             client_update_title(client);
1091         } else if (msgtype == prop_atoms.wm_class) {
1092             client_update_class(client);
1093         } else if (msgtype == prop_atoms.wm_protocols) {
1094             client_update_protocols(client);
1095             client_setup_decor_and_functions(client);
1096         }
1097         else if (msgtype == prop_atoms.net_wm_strut) {
1098             client_update_strut(client);
1099         }
1100         else if (msgtype == prop_atoms.net_wm_icon) {
1101             client_update_icons(client);
1102         }
1103         else if (msgtype == prop_atoms.net_wm_user_time) {
1104             client_update_user_time(client);
1105         }
1106 #ifdef SYNC
1107         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1108             client_update_sync_request_counter(client);
1109         }
1110 #endif
1111         else if (msgtype == prop_atoms.sm_client_id) {
1112             client_update_sm_client_id(client);
1113         }
1114     case ColormapNotify:
1115         client_update_colormap(client, e->xcolormap.colormap);
1116         break;
1117     default:
1118         ;
1119 #ifdef SHAPE
1120         if (extensions_shape && e->type == extensions_shape_event_basep) {
1121             client->shaped = ((XShapeEvent*)e)->shaped;
1122             frame_adjust_shape(client->frame);
1123         }
1124 #endif
1125     }
1126 }
1127
1128 static void event_handle_dock(ObDock *s, XEvent *e)
1129 {
1130     switch (e->type) {
1131     case ButtonPress:
1132         if (e->xbutton.button == 1)
1133             stacking_raise(DOCK_AS_WINDOW(s));
1134         else if (e->xbutton.button == 2)
1135             stacking_lower(DOCK_AS_WINDOW(s));
1136         break;
1137     case EnterNotify:
1138         dock_hide(FALSE);
1139         break;
1140     case LeaveNotify:
1141         dock_hide(TRUE);
1142         break;
1143     }
1144 }
1145
1146 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1147 {
1148     switch (e->type) {
1149     case MotionNotify:
1150         dock_app_drag(app, &e->xmotion);
1151         break;
1152     case UnmapNotify:
1153         if (app->ignore_unmaps) {
1154             app->ignore_unmaps--;
1155             break;
1156         }
1157         dock_remove(app, TRUE);
1158         break;
1159     case DestroyNotify:
1160         dock_remove(app, FALSE);
1161         break;
1162     case ReparentNotify:
1163         dock_remove(app, FALSE);
1164         break;
1165     case ConfigureNotify:
1166         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1167         break;
1168     }
1169 }
1170
1171 static ObMenuFrame* find_active_menu()
1172 {
1173     GList *it;
1174     ObMenuFrame *ret = NULL;
1175
1176     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1177         ret = it->data;
1178         if (ret->selected)
1179             break;
1180         ret = NULL;
1181     }
1182     return ret;
1183 }
1184
1185 static ObMenuFrame* find_active_or_last_menu()
1186 {
1187     ObMenuFrame *ret = NULL;
1188
1189     ret = find_active_menu();
1190     if (!ret && menu_frame_visible)
1191         ret = menu_frame_visible->data;
1192     return ret;
1193 }
1194
1195 static gboolean event_handle_menu_keyboard(XEvent *ev)
1196 {
1197     guint keycode, state;
1198     gunichar unikey;
1199     ObMenuFrame *frame;
1200     gboolean ret = TRUE;
1201
1202     keycode = ev->xkey.keycode;
1203     state = ev->xkey.state;
1204     unikey = translate_unichar(keycode);
1205
1206     frame = find_active_or_last_menu();
1207     if (frame == NULL)
1208         ret = FALSE;
1209
1210     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1211         /* Escape closes the active menu */
1212         menu_frame_hide(frame);
1213     }
1214
1215     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1216                                                       state == ControlMask))
1217     {
1218         /* Enter runs the active item or goes into the submenu.
1219            Control-Enter runs it without closing the menu. */
1220         if (frame->child)
1221             menu_frame_select_next(frame->child);
1222         else
1223             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1224     }
1225
1226     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1227         /* Left goes to the parent menu */
1228         menu_frame_select(frame, NULL, TRUE);
1229     }
1230
1231     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1232         /* Right goes to the selected submenu */
1233         if (frame->child) menu_frame_select_next(frame->child);
1234     }
1235
1236     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1237         menu_frame_select_previous(frame);
1238     }
1239
1240     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1241         menu_frame_select_next(frame);
1242     }
1243
1244     /* keyboard accelerator shortcuts. */
1245     else if (ev->xkey.state == 0 &&
1246              /* was it a valid key? */
1247              unikey != 0 &&
1248              /* don't bother if the menu is empty. */
1249              frame->entries)
1250     {
1251         GList *start;
1252         GList *it;
1253         ObMenuEntryFrame *found = NULL;
1254         guint num_found = 0;
1255
1256         /* start after the selected one */
1257         start = frame->entries;
1258         if (frame->selected) {
1259             for (it = start; frame->selected != it->data; it = g_list_next(it))
1260                 g_assert(it != NULL); /* nothing was selected? */
1261             /* next with wraparound */
1262             start = g_list_next(it);
1263             if (start == NULL) start = frame->entries;
1264         }
1265
1266         it = start;
1267         do {
1268             ObMenuEntryFrame *e = it->data;
1269             gunichar entrykey = 0;
1270
1271             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1272                 e->entry->data.normal.enabled)
1273                 entrykey = e->entry->data.normal.shortcut;
1274             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1275                 entrykey = e->entry->data.submenu.submenu->shortcut;
1276
1277             if (unikey == entrykey) {
1278                 if (found == NULL) found = e;
1279                 ++num_found;
1280             }
1281
1282             /* next with wraparound */
1283             it = g_list_next(it);
1284             if (it == NULL) it = frame->entries;
1285         } while (it != start);
1286
1287         if (found) {
1288             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1289                 num_found == 1)
1290             {
1291                 menu_frame_select(frame, found, TRUE);
1292                 usleep(50000);
1293                 menu_entry_frame_execute(found, state, ev->xkey.time);
1294             } else {
1295                 menu_frame_select(frame, found, TRUE);
1296                 if (num_found == 1)
1297                     menu_frame_select_next(frame->child);
1298             }
1299         } else
1300             ret = FALSE;
1301     }
1302     else
1303         ret = FALSE;
1304
1305     return ret;
1306 }
1307
1308 static gboolean event_handle_menu(XEvent *ev)
1309 {
1310     ObMenuFrame *f;
1311     ObMenuEntryFrame *e;
1312     gboolean ret = TRUE;
1313
1314     switch (ev->type) {
1315     case ButtonRelease:
1316         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1317             && menu_can_hide)
1318         {
1319             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1320                                             ev->xbutton.y_root)))
1321                 menu_entry_frame_execute(e, ev->xbutton.state,
1322                                          ev->xbutton.time);
1323             else
1324                 menu_frame_hide_all();
1325         }
1326         break;
1327     case EnterNotify:
1328         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1329             if (e->ignore_enters)
1330                 --e->ignore_enters;
1331             else
1332                 menu_frame_select(e->frame, e, FALSE);
1333         }
1334         break;
1335     case LeaveNotify:
1336         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1337             (f = find_active_menu()) && f->selected == e &&
1338             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1339         {
1340             menu_frame_select(e->frame, NULL, FALSE);
1341         }
1342     case MotionNotify:   
1343         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1344                                         ev->xmotion.y_root)))
1345             menu_frame_select(e->frame, e, FALSE);
1346         break;
1347     case KeyPress:
1348         ret = event_handle_menu_keyboard(ev);
1349         break;
1350     }
1351     return ret;
1352 }
1353
1354 static gboolean menu_hide_delay_func(gpointer data)
1355 {
1356     menu_can_hide = TRUE;
1357     return FALSE; /* no repeat */
1358 }
1359
1360 static void focus_delay_dest(gpointer data)
1361 {
1362     g_free(data);
1363 }
1364
1365 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1366 {
1367     const ObFocusDelayData *f1 = d1;
1368     return f1->client == d2;
1369 }
1370
1371 static gboolean focus_delay_func(gpointer data)
1372 {
1373     ObFocusDelayData *d = data;
1374     Time old = event_curtime;
1375
1376     event_curtime = d->time;
1377     if (focus_client != d->client) {
1378         if (client_focus(d->client) && config_focus_raise)
1379             client_raise(d->client);
1380     }
1381     event_curtime = old;
1382     return FALSE; /* no repeat */
1383 }
1384
1385 static void focus_delay_client_dest(ObClient *client, gpointer data)
1386 {
1387     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1388                                      client, FALSE);
1389 }
1390
1391 void event_halt_focus_delay()
1392 {
1393     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1394 }
1395
1396 void event_ignore_queued_enters()
1397 {
1398     GSList *saved = NULL, *it;
1399     XEvent *e;
1400                 
1401     XSync(ob_display, FALSE);
1402
1403     /* count the events */
1404     while (TRUE) {
1405         e = g_new(XEvent, 1);
1406         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1407             ObWindow *win;
1408             
1409             win = g_hash_table_lookup(window_map, &e->xany.window);
1410             if (win && WINDOW_IS_CLIENT(win))
1411                 ++ignore_enter_focus;
1412             
1413             saved = g_slist_append(saved, e);
1414         } else {
1415             g_free(e);
1416             break;
1417         }
1418     }
1419     /* put the events back */
1420     for (it = saved; it; it = g_slist_next(it)) {
1421         XPutBackEvent(ob_display, it->data);
1422         g_free(it->data);
1423     }
1424     g_slist_free(saved);
1425 }
1426
1427 gboolean event_time_after(Time t1, Time t2)
1428 {
1429     g_assert(t1 != CurrentTime);
1430     g_assert(t2 != CurrentTime);
1431
1432     /*
1433       Timestamp values wrap around (after about 49.7 days). The server, given
1434       its current time is represented by timestamp T, always interprets
1435       timestamps from clients by treating half of the timestamp space as being
1436       later in time than T.
1437       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1438     */
1439
1440     /* TIME_HALF is half of the number space of a Time type variable */
1441 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1442
1443     if (t2 >= TIME_HALF)
1444         /* t2 is in the second half so t1 might wrap around and be smaller than
1445            t2 */
1446         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1447     else
1448         /* t2 is in the first half so t1 has to come after it */
1449         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1450 }