We shouldn't enforce incr when the app resizes itself. But it's so confusing.
[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 "actions.h"
26 #include "client.h"
27 #include "config.h"
28 #include "screen.h"
29 #include "frame.h"
30 #include "grab.h"
31 #include "menu.h"
32 #include "prompt.h"
33 #include "menuframe.h"
34 #include "keyboard.h"
35 #include "mouse.h"
36 #include "hooks.h"
37 #include "focus.h"
38 #include "focus_cycle.h"
39 #include "moveresize.h"
40 #include "group.h"
41 #include "stacking.h"
42 #include "ping.h"
43 #include "obt/display.h"
44 #include "obt/prop.h"
45 #include "obt/keyboard.h"
46
47 #include <X11/Xlib.h>
48 #include <X11/Xatom.h>
49 #include <glib.h>
50
51 #ifdef HAVE_SYS_SELECT_H
52 #  include <sys/select.h>
53 #endif
54 #ifdef HAVE_SIGNAL_H
55 #  include <signal.h>
56 #endif
57 #ifdef HAVE_UNISTD_H
58 #  include <unistd.h> /* for usleep() */
59 #endif
60 #ifdef XKB
61 #  include <X11/XKBlib.h>
62 #endif
63
64 #ifdef USE_SM
65 #include <X11/ICE/ICElib.h>
66 #endif
67
68 typedef struct
69 {
70     gboolean ignored;
71 } ObEventData;
72
73 typedef struct
74 {
75     ObClient *client;
76     Time time;
77     gulong serial;
78 } ObFocusDelayData;
79
80 typedef struct
81 {
82     gulong start; /* inclusive */
83     gulong end;   /* inclusive */
84 } ObSerialRange;
85
86 static void event_process(const XEvent *e, gpointer data);
87 static void event_handle_root(XEvent *e);
88 static gboolean event_handle_menu_input(XEvent *e);
89 static void event_handle_menu(ObMenuFrame *frame, XEvent *e);
90 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e);
91 static void event_handle_dock(ObDock *s, XEvent *e);
92 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
93 static void event_handle_client(ObClient *c, XEvent *e);
94 static void event_handle_user_input(ObClient *client, XEvent *e);
95 static gboolean is_enter_focus_event_ignored(gulong serial);
96 static void event_ignore_enter_range(gulong start, gulong end);
97
98 static void focus_delay_dest(gpointer data);
99 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
100 static gboolean focus_delay_func(gpointer data);
101 static void focus_delay_client_dest(ObClient *client, gpointer data);
102
103 Time event_curtime = CurrentTime;
104 Time event_last_user_time = CurrentTime;
105 Time client_swoon = CurrentTime;
106
107 extern int button;
108
109 /*! The serial of the current X event */
110 static gulong event_curserial;
111 static gboolean focus_left_screen = FALSE;
112 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
113 static GSList *ignore_serials = NULL;
114
115 #ifdef USE_SM
116 static void ice_handler(gint fd, gpointer conn)
117 {
118     Bool b;
119     IceProcessMessages(conn, NULL, &b);
120 }
121
122 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
123                       IcePointer *watch_data)
124 {
125     static gint fd = -1;
126
127     if (opening) {
128         fd = IceConnectionNumber(conn);
129         obt_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
130     } else {
131         obt_main_loop_fd_remove(ob_main_loop, fd);
132         fd = -1;
133     }
134 }
135 #endif
136
137 void event_startup(gboolean reconfig)
138 {
139     if (reconfig) return;
140
141     obt_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
142
143 #ifdef USE_SM
144     IceAddConnectionWatch(ice_watch, NULL);
145 #endif
146
147     client_add_destroy_notify(focus_delay_client_dest, NULL);
148 }
149
150 void event_shutdown(gboolean reconfig)
151 {
152     if (reconfig) return;
153
154 #ifdef USE_SM
155     IceRemoveConnectionWatch(ice_watch, NULL);
156 #endif
157
158     client_remove_destroy_notify(focus_delay_client_dest);
159 }
160
161 static Window event_get_window(XEvent *e)
162 {
163     Window window;
164
165     /* pick a window */
166     switch (e->type) {
167     case SelectionClear:
168         window = obt_root(ob_screen);
169         break;
170     case CreateNotify:
171         window = e->xcreatewindow.window;
172         break;
173     case MapRequest:
174         window = e->xmaprequest.window;
175         break;
176     case MapNotify:
177         window = e->xmap.window;
178         break;
179     case UnmapNotify:
180         window = e->xunmap.window;
181         break;
182     case DestroyNotify:
183         window = e->xdestroywindow.window;
184         break;
185     case ConfigureRequest:
186         window = e->xconfigurerequest.window;
187         break;
188     case ConfigureNotify:
189         window = e->xconfigure.window;
190         break;
191     default:
192 #ifdef XKB
193         if (obt_display_extension_xkb &&
194             e->type == obt_display_extension_xkb_basep)
195         {
196             switch (((XkbAnyEvent*)e)->xkb_type) {
197             case XkbBellNotify:
198                 window = ((XkbBellNotifyEvent*)e)->window;
199             default:
200                 window = None;
201             }
202         } else
203 #endif
204 #ifdef SYNC
205         if (obt_display_extension_sync &&
206             e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
207         {
208             window = None;
209         } else
210 #endif
211             window = e->xany.window;
212     }
213     return window;
214 }
215
216 static void event_set_curtime(XEvent *e)
217 {
218     Time t = CurrentTime;
219
220     /* grab the lasttime and hack up the state */
221     switch (e->type) {
222     case ButtonPress:
223     case ButtonRelease:
224         t = e->xbutton.time;
225         break;
226     case KeyPress:
227         t = e->xkey.time;
228         break;
229     case KeyRelease:
230         t = e->xkey.time;
231         break;
232     case MotionNotify:
233         t = e->xmotion.time;
234         break;
235     case PropertyNotify:
236         t = e->xproperty.time;
237         break;
238     case EnterNotify:
239     case LeaveNotify:
240         t = e->xcrossing.time;
241         break;
242     default:
243 #ifdef SYNC
244         if (obt_display_extension_sync &&
245             e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
246         {
247             t = ((XSyncAlarmNotifyEvent*)e)->time;
248         }
249 #endif
250         /* if more event types are anticipated, get their timestamp
251            explicitly */
252         break;
253     }
254
255     /* watch that if we get an event earlier than the last specified user_time,
256        which can happen if the clock goes backwards, we erase the last
257        specified user_time */
258     if (t && event_last_user_time && event_time_after(event_last_user_time, t))
259         event_last_user_time = CurrentTime;
260
261     event_curtime = t;
262 }
263
264 static void event_hack_mods(XEvent *e)
265 {
266 #ifdef XKB
267     XkbStateRec xkb_state;
268 #endif
269
270     switch (e->type) {
271     case ButtonPress:
272     case ButtonRelease:
273         e->xbutton.state = obt_keyboard_only_modmasks(e->xbutton.state);
274         break;
275     case KeyPress:
276         e->xkey.state = obt_keyboard_only_modmasks(e->xkey.state);
277         break;
278     case KeyRelease:
279 #ifdef XKB
280         /* If XKB is present, then the modifiers are all strange from its
281            magic.  Our X core protocol stuff won't work, so we use this to
282            find what the modifier state is instead. */
283         if (XkbGetState(obt_display, XkbUseCoreKbd, &xkb_state) == Success)
284             e->xkey.state =
285                 obt_keyboard_only_modmasks(xkb_state.compat_state);
286         else
287 #endif
288         {
289             e->xkey.state = obt_keyboard_only_modmasks(e->xkey.state);
290             /* remove from the state the mask of the modifier key being
291                released, if it is a modifier key being released that is */
292             e->xkey.state &= ~obt_keyboard_keycode_to_modmask(e->xkey.keycode);
293         }
294         break;
295     case MotionNotify:
296         e->xmotion.state = obt_keyboard_only_modmasks(e->xmotion.state);
297         /* compress events */
298         {
299             XEvent ce;
300             while (XCheckTypedWindowEvent(obt_display, e->xmotion.window,
301                                           e->type, &ce)) {
302                 e->xmotion.x = ce.xmotion.x;
303                 e->xmotion.y = ce.xmotion.y;
304                 e->xmotion.x_root = ce.xmotion.x_root;
305                 e->xmotion.y_root = ce.xmotion.y_root;
306             }
307         }
308         break;
309     }
310 }
311
312 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
313 {
314     gint mode = e->xfocus.mode;
315     gint detail = e->xfocus.detail;
316     Window win = e->xany.window;
317
318     if (e->type == FocusIn) {
319         /* These are ones we never want.. */
320
321         /* This means focus was given by a keyboard/mouse grab. */
322         if (mode == NotifyGrab)
323             return FALSE;
324         /* This means focus was given back from a keyboard/mouse grab. */
325         if (mode == NotifyUngrab)
326             return FALSE;
327
328         /* These are the ones we want.. */
329
330         if (win == obt_root(ob_screen)) {
331             /* If looking for a focus in on a client, then always return
332                FALSE for focus in's to the root window */
333             if (in_client_only)
334                 return FALSE;
335             /* This means focus reverted off of a client */
336             else if (detail == NotifyPointerRoot ||
337                      detail == NotifyDetailNone ||
338                      detail == NotifyInferior ||
339                      /* This means focus got here from another screen */
340                      detail == NotifyNonlinear)
341                 return TRUE;
342             else
343                 return FALSE;
344         }
345
346         /* It was on a client, was it a valid one?
347            It's possible to get a FocusIn event for a client that was managed
348            but has disappeared.
349         */
350         if (in_client_only) {
351             ObWindow *w = window_find(e->xfocus.window);
352             if (!w || !WINDOW_IS_CLIENT(w))
353                 return FALSE;
354         }
355         else {
356             /* This means focus reverted to parent from the client (this
357                happens often during iconify animation) */
358             if (detail == NotifyInferior)
359                 return TRUE;
360         }
361
362         /* This means focus moved from the root window to a client */
363         if (detail == NotifyVirtual)
364             return TRUE;
365         /* This means focus moved from one client to another */
366         if (detail == NotifyNonlinearVirtual)
367             return TRUE;
368
369         /* Otherwise.. */
370         return FALSE;
371     } else {
372         g_assert(e->type == FocusOut);
373
374         /* These are ones we never want.. */
375
376         /* This means focus was taken by a keyboard/mouse grab. */
377         if (mode == NotifyGrab)
378             return FALSE;
379         /* This means focus was grabbed on a window and it was released. */
380         if (mode == NotifyUngrab)
381             return FALSE;
382
383         /* Focus left the root window revertedto state */
384         if (win == obt_root(ob_screen))
385             return FALSE;
386
387         /* These are the ones we want.. */
388
389         /* This means focus moved from a client to the root window */
390         if (detail == NotifyVirtual)
391             return TRUE;
392         /* This means focus moved from one client to another */
393         if (detail == NotifyNonlinearVirtual)
394             return TRUE;
395
396         /* Otherwise.. */
397         return FALSE;
398     }
399 }
400
401 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
402 {
403     return e->type == FocusIn && wanted_focusevent(e, FALSE);
404 }
405
406 static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
407 {
408     return e->type == FocusIn && wanted_focusevent(e, TRUE);
409 }
410
411 static void print_focusevent(XEvent *e)
412 {
413     gint mode = e->xfocus.mode;
414     gint detail = e->xfocus.detail;
415     Window win = e->xany.window;
416     const gchar *modestr, *detailstr;
417
418     switch (mode) {
419     case NotifyNormal:       modestr="NotifyNormal";       break;
420     case NotifyGrab:         modestr="NotifyGrab";         break;
421     case NotifyUngrab:       modestr="NotifyUngrab";       break;
422     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
423     }
424     switch (detail) {
425     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
426     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
427     case NotifyInferior:    detailstr="NotifyInferior";    break;
428     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
429     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
430     case NotifyPointer:     detailstr="NotifyPointer";     break;
431     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
432     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
433     }
434
435     if (mode == NotifyGrab || mode == NotifyUngrab)
436         return;
437
438     g_assert(modestr);
439     g_assert(detailstr);
440     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s",
441                   (e->xfocus.type == FocusIn ? "In" : "Out"),
442                   win,
443                   modestr, detailstr);
444
445 }
446
447 static gboolean event_ignore(XEvent *e, ObClient *client)
448 {
449     switch(e->type) {
450     case FocusIn:
451         print_focusevent(e);
452         if (!wanted_focusevent(e, FALSE))
453             return TRUE;
454         break;
455     case FocusOut:
456         print_focusevent(e);
457         if (!wanted_focusevent(e, FALSE))
458             return TRUE;
459         break;
460     }
461     return FALSE;
462 }
463
464 static void event_process(const XEvent *ec, gpointer data)
465 {
466     XEvent ee, *e;
467     ObEventData *ed = data;
468
469     Window window;
470     ObClient *client = NULL;
471     ObDock *dock = NULL;
472     ObDockApp *dockapp = NULL;
473     ObWindow *obwin = NULL;
474     ObMenuFrame *menu = NULL;
475     ObPrompt *prompt = NULL;
476
477     /* make a copy we can mangle */
478     ee = *ec;
479     e = &ee;
480
481     window = event_get_window(e);
482     if (window == obt_root(ob_screen))
483         /* don't do any lookups, waste of cpu */;
484     else if ((obwin = window_find(window))) {
485         switch (obwin->type) {
486         case OB_WINDOW_CLASS_DOCK:
487             dock = WINDOW_AS_DOCK(obwin);
488             break;
489         case OB_WINDOW_CLASS_CLIENT:
490             client = WINDOW_AS_CLIENT(obwin);
491             /* events on clients can be events on prompt windows too */
492             prompt = client->prompt;
493             break;
494         case OB_WINDOW_CLASS_MENUFRAME:
495             menu = WINDOW_AS_MENUFRAME(obwin);
496             break;
497         case OB_WINDOW_CLASS_INTERNAL:
498             /* we don't do anything with events directly on these windows */
499             break;
500         case OB_WINDOW_CLASS_PROMPT:
501             prompt = WINDOW_AS_PROMPT(obwin);
502             break;
503         }
504     }
505     else
506         dockapp = dock_find_dockapp(window);
507
508     event_set_curtime(e);
509     event_curserial = e->xany.serial;
510     event_hack_mods(e);
511     if (event_ignore(e, client)) {
512         if (ed)
513             ed->ignored = TRUE;
514         return;
515     } else if (ed)
516             ed->ignored = FALSE;
517
518     /* deal with it in the kernel */
519
520     if (e->type == FocusIn) {
521         if (client &&
522             e->xfocus.detail == NotifyInferior)
523         {
524             ob_debug_type(OB_DEBUG_FOCUS,
525                           "Focus went to the frame window");
526
527             focus_left_screen = FALSE;
528
529             focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
530
531             /* We don't get a FocusOut for this case, because it's just moving
532                from our Inferior up to us. This happens when iconifying a
533                window with RevertToParent focus */
534             frame_adjust_focus(client->frame, FALSE);
535             /* focus_set_client(NULL) has already been called */
536         }
537         else if (e->xfocus.detail == NotifyPointerRoot ||
538                  e->xfocus.detail == NotifyDetailNone ||
539                  e->xfocus.detail == NotifyInferior ||
540                  e->xfocus.detail == NotifyNonlinear)
541         {
542             XEvent ce;
543
544             ob_debug_type(OB_DEBUG_FOCUS,
545                           "Focus went to root or pointer root/none");
546
547             if (e->xfocus.detail == NotifyInferior ||
548                 e->xfocus.detail == NotifyNonlinear)
549             {
550                 focus_left_screen = FALSE;
551             }
552
553             /* If another FocusIn is in the queue then don't fallback yet. This
554                fixes the fun case of:
555                window map -> send focusin
556                window unmap -> get focusout
557                window map -> send focusin
558                get first focus out -> fall back to something (new window
559                  hasn't received focus yet, so something else) -> send focusin
560                which means the "something else" is the last thing to get a
561                focusin sent to it, so the new window doesn't end up with focus.
562
563                But if the other focus in is something like PointerRoot then we
564                still want to fall back.
565             */
566             if (XCheckIfEvent(obt_display, &ce, event_look_for_focusin_client,
567                               NULL))
568             {
569                 XPutBackEvent(obt_display, &ce);
570                 ob_debug_type(OB_DEBUG_FOCUS,
571                               "  but another FocusIn is coming");
572             } else {
573                 /* Focus has been reverted.
574
575                    FocusOut events come after UnmapNotify, so we don't need to
576                    worry about focusing an invalid window
577                 */
578
579                 if (!focus_left_screen)
580                     focus_fallback(FALSE, config_focus_under_mouse,
581                                    TRUE, TRUE);
582             }
583         }
584         else if (!client)
585         {
586             ob_debug_type(OB_DEBUG_FOCUS,
587                           "Focus went to a window that is already gone");
588
589             /* If you send focus to a window and then it disappears, you can
590                get the FocusIn for it, after it is unmanaged.
591                Just wait for the next FocusOut/FocusIn pair, but make note that
592                the window that was focused no longer is. */
593             focus_set_client(NULL);
594         }
595         else if (client != focus_client) {
596             focus_left_screen = FALSE;
597             if (!focus_cycle_target || !config_focus_dontstop) {
598                 frame_adjust_focus(client->frame, TRUE);
599                 focus_set_client(client);
600                 client_calc_layer(client);
601                 client_bring_helper_windows(client);
602             }
603         }
604     } else if (e->type == FocusOut) {
605         XEvent ce;
606
607         /* Look for the followup FocusIn */
608         if (!XCheckIfEvent(obt_display, &ce, event_look_for_focusin, NULL)) {
609             /* There is no FocusIn, this means focus went to a window that
610                is not being managed, or a window on another screen. */
611             Window win, root;
612             gint i;
613             guint u;
614             obt_display_ignore_errors(TRUE);
615             if (XGetInputFocus(obt_display, &win, &i) &&
616                 XGetGeometry(obt_display, win, &root, &i,&i,&u,&u,&u,&u) &&
617                 root != obt_root(ob_screen))
618             {
619                 ob_debug_type(OB_DEBUG_FOCUS,
620                               "Focus went to another screen !");
621                 focus_left_screen = TRUE;
622             }
623             else
624                 ob_debug_type(OB_DEBUG_FOCUS,
625                               "Focus went to a black hole !");
626             obt_display_ignore_errors(FALSE);
627             /* nothing is focused */
628             focus_set_client(NULL);
629         } else {
630             /* Focus moved, so process the FocusIn event */
631             ObEventData ed = { .ignored = FALSE };
632             event_process(&ce, &ed);
633             if (ed.ignored) {
634                 /* The FocusIn was ignored, this means it was on a window
635                    that isn't a client. */
636                 ob_debug_type(OB_DEBUG_FOCUS,
637                               "Focus went to an unmanaged window 0x%x !",
638                               ce.xfocus.window);
639                 focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE);
640             }
641         }
642
643         if (client && client != focus_client) {
644             frame_adjust_focus(client->frame, FALSE);
645             /* focus_set_client(NULL) has already been called in this
646                section or by focus_fallback */
647         }
648     }
649     else if (client)
650         event_handle_client(client, e);
651     else if (dockapp)
652         event_handle_dockapp(dockapp, e);
653     else if (dock)
654         event_handle_dock(dock, e);
655     else if (menu)
656         event_handle_menu(menu, e);
657     else if (window == obt_root(ob_screen))
658         event_handle_root(e);
659     else if (e->type == MapRequest)
660         window_manage(window);
661     else if (e->type == MappingNotify) {
662         /* keyboard layout changes for modifier mapping changes. reload the
663            modifier map, and rebind all the key bindings as appropriate */
664         ob_debug("Kepboard map changed. Reloading keyboard bindings.");
665         ob_set_state(OB_STATE_RECONFIGURING);
666         obt_keyboard_reload();
667         keyboard_rebind();
668         ob_set_state(OB_STATE_RUNNING);
669     }
670     else if (e->type == ClientMessage) {
671         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
672            windows that are not managed yet. */
673         if (e->xclient.message_type ==
674             OBT_PROP_ATOM(NET_REQUEST_FRAME_EXTENTS))
675         {
676             /* Pretend to manage the client, getting information used to
677                determine its decorations */
678             ObClient *c = client_fake_manage(e->xclient.window);
679             gulong vals[4];
680
681             /* set the frame extents on the window */
682             vals[0] = c->frame->size.left;
683             vals[1] = c->frame->size.right;
684             vals[2] = c->frame->size.top;
685             vals[3] = c->frame->size.bottom;
686             OBT_PROP_SETA32(e->xclient.window, NET_FRAME_EXTENTS,
687                             CARDINAL, vals, 4);
688
689             /* Free the pretend client */
690             client_fake_unmanage(c);
691         }
692     }
693     else if (e->type == ConfigureRequest) {
694         /* unhandled configure requests must be used to configure the
695            window directly */
696         XWindowChanges xwc;
697
698         xwc.x = e->xconfigurerequest.x;
699         xwc.y = e->xconfigurerequest.y;
700         xwc.width = e->xconfigurerequest.width;
701         xwc.height = e->xconfigurerequest.height;
702         xwc.border_width = e->xconfigurerequest.border_width;
703         xwc.sibling = e->xconfigurerequest.above;
704         xwc.stack_mode = e->xconfigurerequest.detail;
705
706         /* we are not to be held responsible if someone sends us an
707            invalid request! */
708         obt_display_ignore_errors(TRUE);
709         XConfigureWindow(obt_display, window,
710                          e->xconfigurerequest.value_mask, &xwc);
711         obt_display_ignore_errors(FALSE);
712     }
713 #ifdef SYNC
714     else if (obt_display_extension_sync &&
715              e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
716     {
717         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
718         if (se->alarm == moveresize_alarm && moveresize_in_progress)
719             moveresize_event(e);
720     }
721 #endif
722
723     if (prompt && event_handle_prompt(prompt, e))
724         ;
725     else if (e->type == ButtonPress || e->type == ButtonRelease) {
726         /* If the button press was on some non-root window, or was physically
727            on the root window, then process it */
728         if (window != obt_root(ob_screen) ||
729             e->xbutton.subwindow == None)
730         {
731             event_handle_user_input(client, e);
732         }
733         /* Otherwise only process it if it was physically on an openbox
734            internal window */
735         else {
736             ObWindow *w;
737
738             if ((w = window_find(e->xbutton.subwindow)) &&
739                 WINDOW_IS_INTERNAL(w))
740             {
741                 event_handle_user_input(client, e);
742             }
743             /* We ignored the release event so make sure we don't think
744                the button is still pressed */
745             else if (e->type == ButtonRelease)
746                 button = 0;
747         }
748     }
749     else if (e->type == KeyPress || e->type == KeyRelease ||
750              e->type == MotionNotify)
751         event_handle_user_input(client, e);
752
753     XFlush(obt_display);
754
755     /* run all the hooks at once */
756     hooks_run_queue();
757
758     /* if something happens and it's not from an XEvent, then we don't know
759        the time */
760     event_curtime = CurrentTime;
761     event_curserial = 0;
762 }
763
764 static void event_handle_root(XEvent *e)
765 {
766     Atom msgtype;
767
768     switch(e->type) {
769     case SelectionClear:
770         ob_debug("Another WM has requested to replace us. Exiting.");
771         ob_exit_replace();
772         break;
773
774     case ClientMessage:
775         if (e->xclient.format != 32) break;
776
777         msgtype = e->xclient.message_type;
778         if (msgtype == OBT_PROP_ATOM(NET_CURRENT_DESKTOP)) {
779             guint d = e->xclient.data.l[0];
780             if (d < screen_num_desktops) {
781                 event_curtime = e->xclient.data.l[1];
782                 if (event_curtime == 0)
783                     ob_debug_type(OB_DEBUG_APP_BUGS,
784                                   "_NET_CURRENT_DESKTOP message is missing "
785                                   "a timestamp");
786                 screen_set_desktop(d, TRUE);
787             }
788         } else if (msgtype == OBT_PROP_ATOM(NET_NUMBER_OF_DESKTOPS)) {
789             guint d = e->xclient.data.l[0];
790             if (d > 0 && d <= 1000)
791                 screen_set_num_desktops(d);
792         } else if (msgtype == OBT_PROP_ATOM(NET_SHOWING_DESKTOP)) {
793             screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
794         } else if (msgtype == OBT_PROP_ATOM(OB_CONTROL)) {
795             ob_debug("OB_CONTROL: %d", e->xclient.data.l[0]);
796             if (e->xclient.data.l[0] == 1)
797                 ob_reconfigure();
798             else if (e->xclient.data.l[0] == 2)
799                 ob_restart();
800             else if (e->xclient.data.l[0] == 3)
801                 ob_exit(0);
802         } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
803             if ((Atom)e->xclient.data.l[0] == OBT_PROP_ATOM(NET_WM_PING))
804                 ping_got_pong(e->xclient.data.l[1]);
805         }
806         break;
807     case PropertyNotify:
808         if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_NAMES)) {
809             ob_debug("UPDATE DESKTOP NAMES");
810             screen_update_desktop_names();
811         }
812         else if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_LAYOUT))
813             screen_update_layout();
814         break;
815     case ConfigureNotify:
816 #ifdef XRANDR
817         XRRUpdateConfiguration(e);
818 #endif
819         screen_resize();
820         break;
821     default:
822         ;
823     }
824 }
825
826 void event_enter_client(ObClient *client)
827 {
828     g_assert(config_focus_follow);
829
830     if (is_enter_focus_event_ignored(event_curserial)) {
831         ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n"
832                       "on client 0x%x", event_curserial, client->window);
833         return;
834     }
835
836     if (client_enter_focusable(client) && client_can_focus(client) && event_time_after(client_swoon, event_curtime - config_focus_delay /*milliseconds here, so not *1000 */)) {
837         if (config_focus_delay) {
838             ObFocusDelayData *data;
839
840             obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
841
842             data = g_new(ObFocusDelayData, 1);
843             data->client = client;
844             data->time = event_curtime;
845             data->serial = event_curserial;
846
847             obt_main_loop_timeout_add(ob_main_loop,
848                                       config_focus_delay * 1000,
849                                       focus_delay_func,
850                                       data, focus_delay_cmp, focus_delay_dest);
851         } else {
852             ObFocusDelayData data;
853             data.client = client;
854             data.time = event_curtime;
855             data.serial = event_curserial;
856             focus_delay_func(&data);
857         }
858     }
859 }
860
861 static gboolean *context_to_button(ObFrame *f, ObFrameContext con, gboolean press)
862 {
863     if (press) {
864         switch (con) {
865         case OB_FRAME_CONTEXT_MAXIMIZE:
866             return &f->max_press;
867         case OB_FRAME_CONTEXT_CLOSE:
868             return &f->close_press;
869         case OB_FRAME_CONTEXT_ICONIFY:
870             return &f->iconify_press;
871         case OB_FRAME_CONTEXT_ALLDESKTOPS:
872             return &f->desk_press;
873         case OB_FRAME_CONTEXT_SHADE:
874             return &f->shade_press;
875         default:
876             return NULL;
877         }
878     } else {
879         switch (con) {
880         case OB_FRAME_CONTEXT_MAXIMIZE:
881             return &f->max_hover;
882         case OB_FRAME_CONTEXT_CLOSE:
883             return &f->close_hover;
884         case OB_FRAME_CONTEXT_ICONIFY:
885             return &f->iconify_hover;
886         case OB_FRAME_CONTEXT_ALLDESKTOPS:
887             return &f->desk_hover;
888         case OB_FRAME_CONTEXT_SHADE:
889             return &f->shade_hover;
890         default:
891             return NULL;
892         }
893     }
894 }
895
896 static void compress_client_message_event(XEvent *e, XEvent *ce, Window window,
897                                           Atom msgtype)
898 {
899     /* compress changes into a single change */
900     while (XCheckTypedWindowEvent(obt_display, window, e->type, ce)) {
901         /* XXX: it would be nice to compress ALL messages of a
902            type, not just messages in a row without other
903            message types between. */
904         if (ce->xclient.message_type != msgtype) {
905             XPutBackEvent(obt_display, ce);
906             break;
907         }
908         e->xclient = ce->xclient;
909     }
910 }
911
912 static void event_handle_client(ObClient *client, XEvent *e)
913 {
914     XEvent ce;
915     Atom msgtype;
916     ObFrameContext con;
917     gboolean *but;
918     static gint px = -1, py = -1;
919     static guint pb = 0;
920     static ObFrameContext pcon = OB_FRAME_CONTEXT_NONE;
921
922     switch (e->type) {
923     case ButtonPress:
924         /* save where the press occured for the first button pressed */
925         if (!pb) {
926             pb = e->xbutton.button;
927             px = e->xbutton.x;
928             py = e->xbutton.y;
929
930             pcon = frame_context(client, e->xbutton.window, px, py);
931             pcon = mouse_button_frame_context(pcon, e->xbutton.button,
932                                               e->xbutton.state);
933         }
934     case ButtonRelease:
935         /* Wheel buttons don't draw because they are an instant click, so it
936            is a waste of resources to go drawing it.
937            if the user is doing an interactive thing, or has a menu open then
938            the mouse is grabbed (possibly) and if we get these events we don't
939            want to deal with them
940         */
941         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
942             !grab_on_keyboard())
943         {
944             /* use where the press occured */
945             con = frame_context(client, e->xbutton.window, px, py);
946             con = mouse_button_frame_context(con, e->xbutton.button,
947                                              e->xbutton.state);
948
949             /* button presses on CLIENT_CONTEXTs are not accompanied by a
950                release because they are Replayed to the client */
951             if ((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) &&
952                 e->xbutton.button == pb)
953                 pb = 0, px = py = -1, pcon = OB_FRAME_CONTEXT_NONE;
954
955             but = context_to_button(client->frame, con, TRUE);
956             if (but) {
957                 *but = (e->type == ButtonPress);
958                 frame_adjust_state(client->frame);
959             }
960         }
961         break;
962     case MotionNotify:
963         /* when there is a grab on the pointer, we won't get enter/leave
964            notifies, but we still get motion events */
965         if (grab_on_pointer()) break;
966
967         con = frame_context(client, e->xmotion.window,
968                             e->xmotion.x, e->xmotion.y);
969         switch (con) {
970         case OB_FRAME_CONTEXT_TITLEBAR:
971         case OB_FRAME_CONTEXT_TLCORNER:
972         case OB_FRAME_CONTEXT_TRCORNER:
973             /* we've left the button area inside the titlebar */
974             if (client->frame->max_hover || client->frame->desk_hover ||
975                 client->frame->shade_hover || client->frame->iconify_hover ||
976                 client->frame->close_hover)
977             {
978                 client->frame->max_hover =
979                     client->frame->desk_hover =
980                     client->frame->shade_hover =
981                     client->frame->iconify_hover =
982                     client->frame->close_hover = FALSE;
983                 frame_adjust_state(client->frame);
984             }
985             break;
986         default:
987             but = context_to_button(client->frame, con, FALSE);
988             if (but && !*but && !pb) {
989                 *but = TRUE;
990                 frame_adjust_state(client->frame);
991             }
992             break;
993         }
994         break;
995     case LeaveNotify:
996         con = frame_context(client, e->xcrossing.window,
997                             e->xcrossing.x, e->xcrossing.y);
998         switch (con) {
999         case OB_FRAME_CONTEXT_TITLEBAR:
1000         case OB_FRAME_CONTEXT_TLCORNER:
1001         case OB_FRAME_CONTEXT_TRCORNER:
1002             /* we've left the button area inside the titlebar */
1003             client->frame->max_hover =
1004                 client->frame->desk_hover =
1005                 client->frame->shade_hover =
1006                 client->frame->iconify_hover =
1007                 client->frame->close_hover = FALSE;
1008             if (e->xcrossing.mode == NotifyGrab) {
1009                 client->frame->max_press =
1010                     client->frame->desk_press =
1011                     client->frame->shade_press =
1012                     client->frame->iconify_press =
1013                     client->frame->close_press = FALSE;
1014             }
1015             break;
1016         case OB_FRAME_CONTEXT_FRAME:
1017             /* When the mouse leaves an animating window, don't use the
1018                corresponding enter events. Pretend like the animating window
1019                doesn't even exist..! */
1020             if (frame_iconify_animating(client->frame))
1021                 event_end_ignore_all_enters(event_start_ignore_all_enters());
1022
1023             ob_debug_type(OB_DEBUG_FOCUS,
1024                           "%sNotify mode %d detail %d on %lx",
1025                           (e->type == EnterNotify ? "Enter" : "Leave"),
1026                           e->xcrossing.mode,
1027                           e->xcrossing.detail, (client?client->window:0));
1028             if (grab_on_keyboard())
1029                 break;
1030             if (config_focus_follow && config_focus_delay &&
1031                 /* leave inferior events can happen when the mouse goes onto
1032                    the window's border and then into the window before the
1033                    delay is up */
1034                 e->xcrossing.detail != NotifyInferior)
1035             {
1036                 obt_main_loop_timeout_remove_data(ob_main_loop,
1037                                                   focus_delay_func,
1038                                                   client, FALSE);
1039                 if (client == focus_client)
1040                     client_swoon = e->xcrossing.time;
1041             }
1042             break;
1043         default:
1044             but = context_to_button(client->frame, con, FALSE);
1045             if (but) {
1046                 *but = FALSE;
1047                 if (e->xcrossing.mode == NotifyGrab) {
1048                     but = context_to_button(client->frame, con, TRUE);
1049                     *but = FALSE;
1050                 }
1051                 frame_adjust_state(client->frame);
1052             }
1053             break;
1054         }
1055         break;
1056     case EnterNotify:
1057     {
1058         con = frame_context(client, e->xcrossing.window,
1059                             e->xcrossing.x, e->xcrossing.y);
1060         switch (con) {
1061         case OB_FRAME_CONTEXT_FRAME:
1062             if (grab_on_keyboard())
1063                 break;
1064             if (e->xcrossing.mode == NotifyGrab ||
1065                 e->xcrossing.mode == NotifyUngrab ||
1066                 /*ignore enters when we're already in the window */
1067                 e->xcrossing.detail == NotifyInferior)
1068             {
1069                 ob_debug_type(OB_DEBUG_FOCUS,
1070                               "%sNotify mode %d detail %d serial %lu on %lx "
1071                               "IGNORED",
1072                               (e->type == EnterNotify ? "Enter" : "Leave"),
1073                               e->xcrossing.mode,
1074                               e->xcrossing.detail,
1075                               e->xcrossing.serial,
1076                               client?client->window:0);
1077             }
1078             else {
1079                 ob_debug_type(OB_DEBUG_FOCUS,
1080                               "%sNotify mode %d detail %d serial %lu on %lx, "
1081                               "focusing window",
1082                               (e->type == EnterNotify ? "Enter" : "Leave"),
1083                               e->xcrossing.mode,
1084                               e->xcrossing.detail,
1085                               e->xcrossing.serial,
1086                               (client?client->window:0));
1087                 if (config_focus_follow)
1088                     event_enter_client(client);
1089             }
1090             break;
1091         default:
1092             but = context_to_button(client->frame, con, FALSE);
1093             if (but) {
1094                 *but = TRUE;
1095                 if (e->xcrossing.mode == NotifyUngrab) {
1096                     but = context_to_button(client->frame, con, TRUE);
1097                     *but = (con == pcon);
1098                 }
1099                 frame_adjust_state(client->frame);
1100             }
1101             break;
1102         }
1103         break;
1104     }
1105     case ConfigureRequest:
1106     {
1107         if (client->locked)
1108             break;
1109
1110         /* dont compress these unless you're going to watch for property
1111            notifies in between (these can change what the configure would
1112            do to the window).
1113            also you can't compress stacking events
1114         */
1115
1116         gint x, y, w, h;
1117         gboolean move = FALSE;
1118         gboolean resize = FALSE;
1119
1120         /* get the current area */
1121         RECT_TO_DIMS(client->area, x, y, w, h);
1122
1123         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1124                  "visible %d",
1125                  client->title,
1126                  screen_desktop, client->wmstate, client->frame->visible);
1127         ob_debug("                     x %d y %d w %d h %d b %d",
1128                  x, y, w, h, client->border_width);
1129
1130         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1131             if (client->border_width != e->xconfigurerequest.border_width) {
1132                 client->border_width = e->xconfigurerequest.border_width;
1133
1134                 /* if the border width is changing then that is the same
1135                    as requesting a resize, but we don't actually change
1136                    the client's border, so it will change their root
1137                    coordinates (since they include the border width) and
1138                    we need to a notify then */
1139                 move = TRUE;
1140             }
1141
1142         if (e->xconfigurerequest.value_mask & CWStackMode) {
1143             ObClient *sibling = NULL;
1144             gulong ignore_start;
1145             gboolean ok = TRUE;
1146
1147             /* get the sibling */
1148             if (e->xconfigurerequest.value_mask & CWSibling) {
1149                 ObWindow *win;
1150                 win = window_find(e->xconfigurerequest.above);
1151                 if (win && WINDOW_IS_CLIENT(win) &&
1152                     WINDOW_AS_CLIENT(win) != client)
1153                 {
1154                     sibling = WINDOW_AS_CLIENT(win);
1155                 }
1156                 else
1157                     /* an invalid sibling was specified so don't restack at
1158                        all, it won't make sense no matter what we do */
1159                     ok = FALSE;
1160             }
1161
1162             if (ok) {
1163                 if (!config_focus_under_mouse)
1164                     ignore_start = event_start_ignore_all_enters();
1165                 stacking_restack_request(client, sibling,
1166                                          e->xconfigurerequest.detail);
1167                 if (!config_focus_under_mouse)
1168                     event_end_ignore_all_enters(ignore_start);
1169             }
1170
1171             /* a stacking change moves the window without resizing */
1172             move = TRUE;
1173         }
1174
1175         if ((e->xconfigurerequest.value_mask & CWX) ||
1176             (e->xconfigurerequest.value_mask & CWY) ||
1177             (e->xconfigurerequest.value_mask & CWWidth) ||
1178             (e->xconfigurerequest.value_mask & CWHeight))
1179         {
1180             /* don't allow clients to move shaded windows (fvwm does this)
1181             */
1182             if (e->xconfigurerequest.value_mask & CWX) {
1183                 if (!client->shaded)
1184                     x = e->xconfigurerequest.x;
1185                 move = TRUE;
1186             }
1187             if (e->xconfigurerequest.value_mask & CWY) {
1188                 if (!client->shaded)
1189                     y = e->xconfigurerequest.y;
1190                 move = TRUE;
1191             }
1192
1193             if (e->xconfigurerequest.value_mask & CWWidth) {
1194                 w = e->xconfigurerequest.width;
1195                 resize = TRUE;
1196             }
1197             if (e->xconfigurerequest.value_mask & CWHeight) {
1198                 h = e->xconfigurerequest.height;
1199                 resize = TRUE;
1200             }
1201         }
1202
1203         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1204                  "move %d resize %d",
1205                  e->xconfigurerequest.value_mask & CWX, x,
1206                  e->xconfigurerequest.value_mask & CWY, y,
1207                  e->xconfigurerequest.value_mask & CWWidth, w,
1208                  e->xconfigurerequest.value_mask & CWHeight, h,
1209                  move, resize);
1210
1211         /* check for broken apps moving to their root position
1212
1213            XXX remove this some day...that would be nice. right now all
1214            kde apps do this when they try activate themselves on another
1215            desktop. eg. open amarok window on desktop 1, switch to desktop
1216            2, click amarok tray icon. it will move by its decoration size.
1217         */
1218         if (x != client->area.x &&
1219             x == (client->frame->area.x + client->frame->size.left -
1220                   (gint)client->border_width) &&
1221             y != client->area.y &&
1222             y == (client->frame->area.y + client->frame->size.top -
1223                   (gint)client->border_width) &&
1224             w == client->area.width &&
1225             h == client->area.height)
1226         {
1227             ob_debug_type(OB_DEBUG_APP_BUGS,
1228                           "Application %s is trying to move via "
1229                           "ConfigureRequest to it's root window position "
1230                           "but it is not using StaticGravity",
1231                           client->title);
1232             /* don't move it */
1233             x = client->area.x;
1234             y = client->area.y;
1235
1236             /* they still requested a move, so don't change whether a
1237                notify is sent or not */
1238         }
1239
1240         {
1241             gint lw, lh;
1242
1243             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1244
1245             /* if x was not given, then use gravity to figure out the new
1246                x.  the reference point should not be moved */
1247             if ((e->xconfigurerequest.value_mask & CWWidth &&
1248                  !(e->xconfigurerequest.value_mask & CWX)))
1249                 client_gravity_resize_w(client, &x, client->area.width, w);
1250             /* same for y */
1251             if ((e->xconfigurerequest.value_mask & CWHeight &&
1252                  !(e->xconfigurerequest.value_mask & CWY)))
1253                 client_gravity_resize_h(client, &y, client->area.height,h);
1254
1255             client_find_onscreen(client, &x, &y, w, h, FALSE);
1256
1257             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d",
1258                      x, y, w, h);
1259             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1260         }
1261         break;
1262     }
1263     case UnmapNotify:
1264         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1265                  "ignores left %d",
1266                  client->window, e->xunmap.event, e->xunmap.from_configure,
1267                  client->ignore_unmaps);
1268         if (client->ignore_unmaps) {
1269             client->ignore_unmaps--;
1270             break;
1271         }
1272         client_unmanage(client);
1273         break;
1274     case DestroyNotify:
1275         ob_debug("DestroyNotify for window 0x%x", client->window);
1276         client_unmanage(client);
1277         break;
1278     case ReparentNotify:
1279         /* this is when the client is first taken captive in the frame */
1280         if (e->xreparent.parent == client->frame->window) break;
1281
1282         /*
1283           This event is quite rare and is usually handled in unmapHandler.
1284           However, if the window is unmapped when the reparent event occurs,
1285           the window manager never sees it because an unmap event is not sent
1286           to an already unmapped window.
1287         */
1288
1289         /* we don't want the reparent event, put it back on the stack for the
1290            X server to deal with after we unmanage the window */
1291         XPutBackEvent(obt_display, e);
1292
1293         ob_debug("ReparentNotify for window 0x%x", client->window);
1294         client_unmanage(client);
1295         break;
1296     case MapRequest:
1297         ob_debug("MapRequest for 0x%lx", client->window);
1298         if (!client->iconic) break; /* this normally doesn't happen, but if it
1299                                        does, we don't want it!
1300                                        it can happen now when the window is on
1301                                        another desktop, but we still don't
1302                                        want it! */
1303         client_activate(client, FALSE, TRUE, TRUE, TRUE);
1304         break;
1305     case ClientMessage:
1306         /* validate cuz we query stuff off the client here */
1307         if (!client_validate(client)) break;
1308
1309         if (e->xclient.format != 32) return;
1310
1311         msgtype = e->xclient.message_type;
1312         if (msgtype == OBT_PROP_ATOM(WM_CHANGE_STATE)) {
1313             compress_client_message_event(e, &ce, client->window, msgtype);
1314             if (!client->locked)
1315                 client_set_wm_state(client, e->xclient.data.l[0]);
1316         } else if (msgtype == OBT_PROP_ATOM(NET_WM_DESKTOP)) {
1317             compress_client_message_event(e, &ce, client->window, msgtype);
1318             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1319                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1320                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1321                                    FALSE, FALSE);
1322         } else if (msgtype == OBT_PROP_ATOM(NET_WM_STATE)) {
1323             gulong ignore_start;
1324
1325             /* can't compress these */
1326             ob_debug("net_wm_state %s %ld %ld for 0x%lx",
1327                      (e->xclient.data.l[0] == 0 ? "Remove" :
1328                       e->xclient.data.l[0] == 1 ? "Add" :
1329                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1330                      e->xclient.data.l[1], e->xclient.data.l[2],
1331                      client->window);
1332
1333             if (!client->locked) {
1334                 /* ignore enter events caused by these like ob actions do */
1335                 if (!config_focus_under_mouse)
1336                     ignore_start = event_start_ignore_all_enters();
1337                 client_set_state(client, e->xclient.data.l[0],
1338                                  e->xclient.data.l[1], e->xclient.data.l[2]);
1339                 if (!config_focus_under_mouse)
1340                     event_end_ignore_all_enters(ignore_start);
1341             }
1342         } else if (msgtype == OBT_PROP_ATOM(NET_CLOSE_WINDOW)) {
1343             ob_debug("net_close_window for 0x%lx", client->window);
1344             if (!client->locked)
1345                 client_close(client);
1346         } else if (msgtype == OBT_PROP_ATOM(NET_ACTIVE_WINDOW)) {
1347             ob_debug("net_active_window for 0x%lx source=%s",
1348                      client->window,
1349                      (e->xclient.data.l[0] == 0 ? "unknown" :
1350                       (e->xclient.data.l[0] == 1 ? "application" :
1351                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1352             /* XXX make use of data.l[2] !? */
1353             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1354                 /* don't use the user's timestamp for client_focus, cuz if it's
1355                    an old broken timestamp (happens all the time) then focus
1356                    won't move even though we're trying to move it
1357                   event_curtime = e->xclient.data.l[1];*/
1358                 if (e->xclient.data.l[1] == 0)
1359                     ob_debug_type(OB_DEBUG_APP_BUGS,
1360                                   "_NET_ACTIVE_WINDOW message for window %s is"
1361                                   " missing a timestamp", client->title);
1362             } else
1363                 ob_debug_type(OB_DEBUG_APP_BUGS,
1364                               "_NET_ACTIVE_WINDOW message for window %s is "
1365                               "missing source indication");
1366             if (!client->locked)
1367                 client_activate(client, TRUE, TRUE, TRUE,
1368                                 (e->xclient.data.l[0] == 0 ||
1369                                  e->xclient.data.l[0] == 2));
1370         } else if (msgtype == OBT_PROP_ATOM(OB_FOCUS)) {
1371             client_focus(client);
1372         } else if (msgtype == OBT_PROP_ATOM(NET_WM_MOVERESIZE)) {
1373             ob_debug("net_wm_moveresize for 0x%lx direction %d",
1374                      client->window, e->xclient.data.l[2]);
1375             if ((Atom)e->xclient.data.l[2] ==
1376                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
1377                 (Atom)e->xclient.data.l[2] ==
1378                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) ||
1379                 (Atom)e->xclient.data.l[2] ==
1380                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT) ||
1381                 (Atom)e->xclient.data.l[2] ==
1382                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1383                 (Atom)e->xclient.data.l[2] ==
1384                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1385                 (Atom)e->xclient.data.l[2] ==
1386                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) ||
1387                 (Atom)e->xclient.data.l[2] ==
1388                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM) ||
1389                 (Atom)e->xclient.data.l[2] ==
1390                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) ||
1391                 (Atom)e->xclient.data.l[2] ==
1392                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) ||
1393                 (Atom)e->xclient.data.l[2] ==
1394                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) ||
1395                 (Atom)e->xclient.data.l[2] ==
1396                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD) ||
1397                 (Atom)e->xclient.data.l[2] ==
1398                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
1399             {
1400                 if (!client->locked)
1401                     moveresize_start(client, e->xclient.data.l[0],
1402                                      e->xclient.data.l[1], e->xclient.data.l[3],
1403                                      e->xclient.data.l[2]);
1404             }
1405             else if ((Atom)e->xclient.data.l[2] ==
1406                      OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL))
1407                 moveresize_end(TRUE);
1408         } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) {
1409             if (client->locked)
1410                 break;
1411
1412             gint ograv, x, y, w, h;
1413
1414             ograv = client->gravity;
1415
1416             if (e->xclient.data.l[0] & 0xff)
1417                 client->gravity = e->xclient.data.l[0] & 0xff;
1418
1419             if (e->xclient.data.l[0] & 1 << 8)
1420                 x = e->xclient.data.l[1];
1421             else
1422                 x = client->area.x;
1423             if (e->xclient.data.l[0] & 1 << 9)
1424                 y = e->xclient.data.l[2];
1425             else
1426                 y = client->area.y;
1427
1428             if (e->xclient.data.l[0] & 1 << 10) {
1429                 w = e->xclient.data.l[3];
1430
1431                 /* if x was not given, then use gravity to figure out the new
1432                    x.  the reference point should not be moved */
1433                 if (!(e->xclient.data.l[0] & 1 << 8))
1434                     client_gravity_resize_w(client, &x, client->area.width, w);
1435             }
1436             else
1437                 w = client->area.width;
1438
1439             if (e->xclient.data.l[0] & 1 << 11) {
1440                 h = e->xclient.data.l[4];
1441
1442                 /* same for y */
1443                 if (!(e->xclient.data.l[0] & 1 << 9))
1444                     client_gravity_resize_h(client, &y, client->area.height,h);
1445             }
1446             else
1447                 h = client->area.height;
1448
1449             ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)",
1450                      e->xclient.data.l[0] & 1 << 8, x,
1451                      e->xclient.data.l[0] & 1 << 9, y,
1452                      client->gravity);
1453
1454             client_find_onscreen(client, &x, &y, w, h, FALSE);
1455
1456             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1457
1458             client->gravity = ograv;
1459         } else if (msgtype == OBT_PROP_ATOM(NET_RESTACK_WINDOW)) {
1460             if (e->xclient.data.l[0] != 2) {
1461                 ob_debug_type(OB_DEBUG_APP_BUGS,
1462                               "_NET_RESTACK_WINDOW sent for window %s with "
1463                               "invalid source indication %ld",
1464                               client->title, e->xclient.data.l[0]);
1465             } else {
1466                 ObClient *sibling = NULL;
1467                 if (e->xclient.data.l[1]) {
1468                     ObWindow *win = window_find(e->xclient.data.l[1]);
1469                     if (WINDOW_IS_CLIENT(win) &&
1470                         WINDOW_AS_CLIENT(win) != client)
1471                     {
1472                         sibling = WINDOW_AS_CLIENT(win);
1473                     }
1474                     if (sibling == NULL)
1475                         ob_debug_type(OB_DEBUG_APP_BUGS,
1476                                       "_NET_RESTACK_WINDOW sent for window %s "
1477                                       "with invalid sibling 0x%x",
1478                                  client->title, e->xclient.data.l[1]);
1479                 }
1480                 if (e->xclient.data.l[2] == Below ||
1481                     e->xclient.data.l[2] == BottomIf ||
1482                     e->xclient.data.l[2] == Above ||
1483                     e->xclient.data.l[2] == TopIf ||
1484                     e->xclient.data.l[2] == Opposite)
1485                 {
1486                     gulong ignore_start;
1487
1488                     if (!config_focus_under_mouse)
1489                         ignore_start = event_start_ignore_all_enters();
1490                     /* just raise, don't activate */
1491                     stacking_restack_request(client, sibling,
1492                                              e->xclient.data.l[2]);
1493                     if (!config_focus_under_mouse)
1494                         event_end_ignore_all_enters(ignore_start);
1495
1496                     /* send a synthetic ConfigureNotify, cuz this is supposed
1497                        to be like a ConfigureRequest. */
1498                     client_reconfigure(client, TRUE);
1499                 } else
1500                     ob_debug_type(OB_DEBUG_APP_BUGS,
1501                                   "_NET_RESTACK_WINDOW sent for window %s "
1502                                   "with invalid detail %d",
1503                                   client->title, e->xclient.data.l[2]);
1504             }
1505         }
1506         break;
1507     case PropertyNotify:
1508         /* validate cuz we query stuff off the client here */
1509         if (!client_validate(client)) break;
1510
1511         /* compress changes to a single property into a single change */
1512         while (XCheckTypedWindowEvent(obt_display, client->window,
1513                                       e->type, &ce)) {
1514             Atom a, b;
1515
1516             /* XXX: it would be nice to compress ALL changes to a property,
1517                not just changes in a row without other props between. */
1518
1519             a = ce.xproperty.atom;
1520             b = e->xproperty.atom;
1521
1522             if (a == b)
1523                 continue;
1524             if ((a == OBT_PROP_ATOM(NET_WM_NAME) ||
1525                  a == OBT_PROP_ATOM(WM_NAME) ||
1526                  a == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1527                  a == OBT_PROP_ATOM(WM_ICON_NAME))
1528                 &&
1529                 (b == OBT_PROP_ATOM(NET_WM_NAME) ||
1530                  b == OBT_PROP_ATOM(WM_NAME) ||
1531                  b == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1532                  b == OBT_PROP_ATOM(WM_ICON_NAME))) {
1533                 continue;
1534             }
1535             if (a == OBT_PROP_ATOM(NET_WM_ICON) &&
1536                 b == OBT_PROP_ATOM(NET_WM_ICON))
1537                 continue;
1538
1539             XPutBackEvent(obt_display, &ce);
1540             break;
1541         }
1542
1543         msgtype = e->xproperty.atom;
1544         if (msgtype == XA_WM_NORMAL_HINTS) {
1545             ob_debug("Update NORMAL hints");
1546             client_update_normal_hints(client);
1547             /* normal hints can make a window non-resizable */
1548             client_setup_decor_and_functions(client, FALSE);
1549
1550             /* make sure the client's sizes are within its bounds, but only
1551                reconfigure the window if it needs to. emacs will update its
1552                normal hints every time it receives a conigurenotify */
1553             client_reconfigure(client, FALSE);
1554         } else if (msgtype == XA_WM_HINTS) {
1555             client_update_wmhints(client);
1556         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1557             client_update_transient_for(client);
1558             client_get_type_and_transientness(client);
1559             /* type may have changed, so update the layer */
1560             client_calc_layer(client);
1561             client_setup_decor_and_functions(client, TRUE);
1562         } else if (msgtype == OBT_PROP_ATOM(NET_WM_NAME) ||
1563                    msgtype == OBT_PROP_ATOM(WM_NAME) ||
1564                    msgtype == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1565                    msgtype == OBT_PROP_ATOM(WM_ICON_NAME)) {
1566             client_update_title(client);
1567         } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
1568             client_update_protocols(client);
1569             client_setup_decor_and_functions(client, TRUE);
1570         }
1571         else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT) ||
1572                  msgtype == OBT_PROP_ATOM(NET_WM_STRUT_PARTIAL)) {
1573             client_update_strut(client);
1574         }
1575         else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON)) {
1576             client_update_icons(client);
1577         }
1578         else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON_GEOMETRY)) {
1579             client_update_icon_geometry(client);
1580         }
1581         else if (msgtype == OBT_PROP_ATOM(NET_WM_USER_TIME)) {
1582             guint32 t;
1583             if (client == focus_client &&
1584                 OBT_PROP_GET32(client->window, NET_WM_USER_TIME, CARDINAL, &t)
1585                 && t && !event_time_after(t, e->xproperty.time) &&
1586                 (!event_last_user_time ||
1587                  event_time_after(t, event_last_user_time)))
1588             {
1589                 event_last_user_time = t;
1590             }
1591         }
1592 #ifdef SYNC
1593         else if (msgtype == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) {
1594             client_update_sync_request_counter(client);
1595         }
1596 #endif
1597         break;
1598     case ColormapNotify:
1599         client_update_colormap(client, e->xcolormap.colormap);
1600         break;
1601     default:
1602         ;
1603 #ifdef SHAPE
1604         if (obt_display_extension_shape &&
1605             e->type == obt_display_extension_shape_basep)
1606         {
1607             client->shaped = ((XShapeEvent*)e)->shaped;
1608             frame_adjust_shape(client->frame);
1609         }
1610 #endif
1611     }
1612 }
1613
1614 static void event_handle_dock(ObDock *s, XEvent *e)
1615 {
1616     switch (e->type) {
1617     case ButtonPress:
1618         switch (e->xbutton.button) {
1619             case 1:
1620                 stacking_raise(DOCK_AS_WINDOW(s));
1621                 break;
1622             case 2:
1623                 stacking_lower(DOCK_AS_WINDOW(s));
1624                 break;
1625             case 4:
1626                 screen_set_desktop(
1627                         screen_find_desktop(screen_desktop, OB_DIRECTION_WEST,
1628                                             TRUE, TRUE), TRUE);
1629                 break;
1630             case 5:
1631                 screen_set_desktop(
1632                         screen_find_desktop(screen_desktop, OB_DIRECTION_EAST,
1633                                             TRUE, TRUE), TRUE);
1634                 break;
1635             case 8:
1636                 screen_set_desktop(screen_last_desktop, TRUE);
1637                 break;
1638         }
1639         break;
1640     case EnterNotify:
1641         dock_hide(FALSE);
1642         break;
1643     case LeaveNotify:
1644         /* don't hide when moving into a dock app */
1645         if (e->xcrossing.detail != NotifyInferior)
1646             dock_hide(TRUE);
1647         break;
1648     }
1649 }
1650
1651 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1652 {
1653     switch (e->type) {
1654     case MotionNotify:
1655         dock_app_drag(app, &e->xmotion);
1656         break;
1657     case UnmapNotify:
1658         if (app->ignore_unmaps) {
1659             app->ignore_unmaps--;
1660             break;
1661         }
1662         dock_unmanage(app, TRUE);
1663         break;
1664     case DestroyNotify:
1665     case ReparentNotify:
1666         dock_unmanage(app, FALSE);
1667         break;
1668     case ConfigureNotify:
1669         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1670         break;
1671     }
1672 }
1673
1674 static ObMenuFrame* find_active_menu(void)
1675 {
1676     GList *it;
1677     ObMenuFrame *ret = NULL;
1678
1679     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1680         ret = it->data;
1681         if (ret->selected)
1682             break;
1683         ret = NULL;
1684     }
1685     return ret;
1686 }
1687
1688 static ObMenuFrame* find_active_or_last_menu(void)
1689 {
1690     ObMenuFrame *ret = NULL;
1691
1692     ret = find_active_menu();
1693     if (!ret && menu_frame_visible)
1694         ret = menu_frame_visible->data;
1695     return ret;
1696 }
1697
1698 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
1699 {
1700     switch (e->type) {
1701     case ButtonPress:
1702     case ButtonRelease:
1703     case MotionNotify:
1704         return prompt_mouse_event(p, e);
1705         break;
1706     case KeyPress:
1707         return prompt_key_event(p, e);
1708         break;
1709     }
1710     return FALSE;
1711 }
1712
1713 static gboolean event_handle_menu_input(XEvent *ev)
1714 {
1715     gboolean ret = FALSE;
1716
1717     if (ev->type == ButtonRelease || ev->type == ButtonPress) {
1718         ObMenuEntryFrame *e;
1719
1720         if (menu_hide_delay_reached() &&
1721             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1722         {
1723             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1724                                             ev->xbutton.y_root)))
1725             {
1726                 if (ev->type == ButtonPress && e->frame->child)
1727                     menu_frame_select(e->frame->child, NULL, TRUE);
1728                 menu_frame_select(e->frame, e, TRUE);
1729                 if (ev->type == ButtonRelease)
1730                     menu_entry_frame_execute(e, ev->xbutton.state);
1731             }
1732             else if (ev->type == ButtonRelease)
1733                 menu_frame_hide_all();
1734         }
1735         ret = TRUE;
1736     }
1737     else if (ev->type == MotionNotify) {
1738         ObMenuFrame *f;
1739         ObMenuEntryFrame *e;
1740
1741         if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1742                                         ev->xmotion.y_root)))
1743             if (!(f = find_active_menu()) ||
1744                 f == e->frame ||
1745                 f->parent == e->frame ||
1746                 f->child == e->frame)
1747                 menu_frame_select(e->frame, e, FALSE);
1748     }
1749     else if (ev->type == KeyPress || ev->type == KeyRelease) {
1750         guint keycode, state;
1751         gunichar unikey;
1752         ObMenuFrame *frame;
1753
1754         keycode = ev->xkey.keycode;
1755         state = ev->xkey.state;
1756         unikey = obt_keyboard_keycode_to_unichar(keycode);
1757
1758         frame = find_active_or_last_menu();
1759         if (frame == NULL)
1760             g_assert_not_reached(); /* there is no active menu */
1761
1762         /* Allow control while going thru the menu */
1763         else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1764             frame->got_press = TRUE;
1765
1766             if (keycode == ob_keycode(OB_KEY_ESCAPE)) {
1767                 menu_frame_hide_all();
1768                 ret = TRUE;
1769             }
1770
1771             else if (keycode == ob_keycode(OB_KEY_LEFT)) {
1772                 /* Left goes to the parent menu */
1773                 if (frame->parent)
1774                     menu_frame_select(frame, NULL, TRUE);
1775                 ret = TRUE;
1776             }
1777
1778             else if (keycode == ob_keycode(OB_KEY_RIGHT)) {
1779                 /* Right goes to the selected submenu */
1780                 if (frame->child) menu_frame_select_next(frame->child);
1781                 ret = TRUE;
1782             }
1783
1784             else if (keycode == ob_keycode(OB_KEY_UP)) {
1785                 menu_frame_select_previous(frame);
1786                 ret = TRUE;
1787             }
1788
1789             else if (keycode == ob_keycode(OB_KEY_DOWN)) {
1790                 menu_frame_select_next(frame);
1791                 ret = TRUE;
1792             }
1793         }
1794
1795         /* Use KeyRelease events for running things so that the key release
1796            doesn't get sent to the focused application.
1797
1798            Allow ControlMask only, and don't bother if the menu is empty */
1799         else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1800                  frame->entries && frame->got_press)
1801         {
1802             if (keycode == ob_keycode(OB_KEY_RETURN)) {
1803                 /* Enter runs the active item or goes into the submenu.
1804                    Control-Enter runs it without closing the menu. */
1805                 if (frame->child)
1806                     menu_frame_select_next(frame->child);
1807                 else if (frame->selected)
1808                     menu_entry_frame_execute(frame->selected, state);
1809
1810                 ret = TRUE;
1811             }
1812
1813             /* keyboard accelerator shortcuts. (if it was a valid key) */
1814             else if (unikey != 0) {
1815                 GList *start;
1816                 GList *it;
1817                 ObMenuEntryFrame *found = NULL;
1818                 guint num_found = 0;
1819
1820                 /* start after the selected one */
1821                 start = frame->entries;
1822                 if (frame->selected) {
1823                     for (it = start; frame->selected != it->data;
1824                          it = g_list_next(it))
1825                         g_assert(it != NULL); /* nothing was selected? */
1826                     /* next with wraparound */
1827                     start = g_list_next(it);
1828                     if (start == NULL) start = frame->entries;
1829                 }
1830
1831                 it = start;
1832                 do {
1833                     ObMenuEntryFrame *e = it->data;
1834                     gunichar entrykey = 0;
1835
1836                     if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1837                         entrykey = e->entry->data.normal.shortcut;
1838                     else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1839                         entrykey = e->entry->data.submenu.submenu->shortcut;
1840
1841                     if (unikey == entrykey) {
1842                         if (found == NULL) found = e;
1843                         ++num_found;
1844                     }
1845
1846                     /* next with wraparound */
1847                     it = g_list_next(it);
1848                     if (it == NULL) it = frame->entries;
1849                 } while (it != start);
1850
1851                 if (found) {
1852                     if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1853                         num_found == 1)
1854                     {
1855                         menu_frame_select(frame, found, TRUE);
1856                         usleep(50000); /* highlight the item for a short bit so
1857                                           the user can see what happened */
1858                         menu_entry_frame_execute(found, state);
1859                     } else {
1860                         menu_frame_select(frame, found, TRUE);
1861                         if (num_found == 1)
1862                             menu_frame_select_next(frame->child);
1863                     }
1864
1865                     ret = TRUE;
1866                 }
1867             }
1868         }
1869     }
1870
1871     return ret;
1872 }
1873
1874 static void event_handle_menu(ObMenuFrame *frame, XEvent *ev)
1875 {
1876     ObMenuFrame *f;
1877     ObMenuEntryFrame *e;
1878
1879     switch (ev->type) {
1880     case EnterNotify:
1881         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1882             if (e->ignore_enters)
1883                 --e->ignore_enters;
1884             else if (!(f = find_active_menu()) ||
1885                      f == e->frame ||
1886                      f->parent == e->frame ||
1887                      f->child == e->frame)
1888                 menu_frame_select(e->frame, e, FALSE);
1889         }
1890         break;
1891     case LeaveNotify:
1892         /*ignore leaves when we're already in the window */
1893         if (ev->xcrossing.detail == NotifyInferior)
1894             break;
1895
1896         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1897             (f = find_active_menu()) && f->selected == e &&
1898             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1899         {
1900             menu_frame_select(e->frame, NULL, FALSE);
1901         }
1902         break;
1903     }
1904 }
1905
1906 static void event_handle_user_input(ObClient *client, XEvent *e)
1907 {
1908     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1909              e->type == MotionNotify || e->type == KeyPress ||
1910              e->type == KeyRelease);
1911
1912     if (menu_frame_visible) {
1913         if (event_handle_menu_input(e))
1914             /* don't use the event if the menu used it, but if the menu
1915                didn't use it and it's a keypress that is bound, it will
1916                close the menu and be used */
1917             return;
1918     }
1919
1920     /* if the keyboard interactive action uses the event then dont
1921        use it for bindings. likewise is moveresize uses the event. */
1922     if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1923         if (moveresize_in_progress)
1924             /* make further actions work on the client being
1925                moved/resized */
1926             client = moveresize_client;
1927
1928         if (e->type == ButtonPress ||
1929             e->type == ButtonRelease ||
1930             e->type == MotionNotify)
1931         {
1932             /* the frame may not be "visible" but they can still click on it
1933                in the case where it is animating before disappearing */
1934             if (!client || !frame_iconify_animating(client->frame))
1935                 mouse_event(client, e);
1936         } else
1937             keyboard_event(event_target_client(client), e);
1938     }
1939 }
1940
1941 ObClient* event_target_client(ObClient *client)
1942 {
1943     return (focus_cycle_target ? focus_cycle_target :
1944             (client ? client : focus_client));
1945 }
1946
1947 static void focus_delay_dest(gpointer data)
1948 {
1949     g_free(data);
1950 }
1951
1952 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1953 {
1954     const ObFocusDelayData *f1 = d1;
1955     return f1->client == d2;
1956 }
1957
1958 static gboolean focus_delay_func(gpointer data)
1959 {
1960     ObFocusDelayData *d = data;
1961     Time old = event_curtime;
1962
1963     event_curtime = d->time;
1964     event_curserial = d->serial;
1965     if (client_focus(d->client) && config_focus_raise)
1966         stacking_raise(CLIENT_AS_WINDOW(d->client));
1967     event_curtime = old;
1968     return FALSE; /* no repeat */
1969 }
1970
1971 static void focus_delay_client_dest(ObClient *client, gpointer data)
1972 {
1973     obt_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1974                                       client, FALSE);
1975 }
1976
1977 void event_halt_focus_delay(void)
1978 {
1979     /* ignore all enter events up till the event which caused this to occur */
1980     if (event_curserial) event_ignore_enter_range(1, event_curserial);
1981     obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1982 }
1983
1984 gulong event_start_ignore_all_enters(void)
1985 {
1986     return NextRequest(obt_display);
1987 }
1988
1989 static void event_ignore_enter_range(gulong start, gulong end)
1990 {
1991     ObSerialRange *r;
1992
1993     g_assert(start != 0);
1994     g_assert(end != 0);
1995
1996     r = g_new(ObSerialRange, 1);
1997     r->start = start;
1998     r->end = end;
1999     ignore_serials = g_slist_prepend(ignore_serials, r);
2000
2001     ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu",
2002                   r->start, r->end);
2003
2004     /* increment the serial so we don't ignore events we weren't meant to */
2005     OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
2006 }
2007
2008 void event_end_ignore_all_enters(gulong start)
2009 {
2010     /* Use (NextRequest-1) so that we ignore up to the current serial only.
2011        Inside event_ignore_enter_range, we increment the serial by one, but if
2012        we ignore that serial too, then any enter events generated by mouse
2013        movement will be ignored until we create some further network traffic.
2014        Instead ignore up to NextRequest-1, then when we increment the serial,
2015        we will be *past* the range of ignored serials */
2016     event_ignore_enter_range(start, NextRequest(obt_display)-1);
2017 }
2018
2019 static gboolean is_enter_focus_event_ignored(gulong serial)
2020 {
2021     GSList *it, *next;
2022
2023     for (it = ignore_serials; it; it = next) {
2024         ObSerialRange *r = it->data;
2025
2026         next = g_slist_next(it);
2027
2028         if ((glong)(serial - r->end) > 0) {
2029             /* past the end */
2030             ignore_serials = g_slist_delete_link(ignore_serials, it);
2031             g_free(r);
2032         }
2033         else if ((glong)(serial - r->start) >= 0)
2034             return TRUE;
2035     }
2036     return FALSE;
2037 }
2038
2039 void event_cancel_all_key_grabs(void)
2040 {
2041     return;
2042     if (actions_interactive_act_running()) {
2043         actions_interactive_cancel_act();
2044         ob_debug("KILLED interactive action");
2045     }
2046     else if (menu_frame_visible) {
2047         menu_frame_hide_all();
2048         ob_debug("KILLED open menus");
2049     }
2050     else if (moveresize_in_progress) {
2051         moveresize_end(TRUE);
2052         ob_debug("KILLED interactive moveresize");
2053     }
2054     else if (grab_on_keyboard()) {
2055         ungrab_keyboard();
2056         ob_debug("KILLED active grab on keyboard");
2057     }
2058     else
2059         ungrab_passive_key();
2060
2061     XSync(obt_display, FALSE);
2062 }
2063
2064 gboolean event_time_after(guint32 t1, guint32 t2)
2065 {
2066     g_assert(t1 != CurrentTime);
2067     g_assert(t2 != CurrentTime);
2068
2069     /*
2070       Timestamp values wrap around (after about 49.7 days). The server, given
2071       its current time is represented by timestamp T, always interprets
2072       timestamps from clients by treating half of the timestamp space as being
2073       later in time than T.
2074       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2075     */
2076
2077     /* TIME_HALF is not half of the number space of a Time type variable.
2078      * Rather, it is half the number space of a timestamp value, which is
2079      * always 32 bits. */
2080 #define TIME_HALF (guint32)(1 << 31)
2081
2082     if (t2 >= TIME_HALF)
2083         /* t2 is in the second half so t1 might wrap around and be smaller than
2084            t2 */
2085         return t1 >= t2 || t1 < (t2 + TIME_HALF);
2086     else
2087         /* t2 is in the first half so t1 has to come after it */
2088         return t1 >= t2 && t1 < (t2 + TIME_HALF);
2089 }
2090
2091 Time event_get_server_time(void)
2092 {
2093     /* Generate a timestamp */
2094     XEvent event;
2095
2096     XChangeProperty(obt_display, screen_support_win,
2097                     OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING),
2098                     8, PropModeAppend, NULL, 0);
2099     XWindowEvent(obt_display, screen_support_win, PropertyChangeMask, &event);
2100     return event.xproperty.time;
2101 }