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