9905d973819660fe9455df638b1560282ef72172
[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             client_update_sync_request_counter(client);
1663         }
1664 #endif
1665         break;
1666     case ColormapNotify:
1667         client_update_colormap(client, e->xcolormap.colormap);
1668         break;
1669     default:
1670         ;
1671 #ifdef SHAPE
1672         {
1673             int kind;
1674             if (obt_display_extension_shape &&
1675                 e->type == obt_display_extension_shape_basep)
1676             {
1677                 switch (((XShapeEvent*)e)->kind) {
1678                     case ShapeBounding:
1679                     case ShapeClip:
1680                         client->shaped = ((XShapeEvent*)e)->shaped;
1681                         kind = ShapeBounding;
1682                         break;
1683                     case ShapeInput:
1684                         client->shaped_input = ((XShapeEvent*)e)->shaped;
1685                         kind = ShapeInput;
1686                         break;
1687                     default:
1688                         g_assert_not_reached();
1689                 }
1690                 frame_adjust_shape_kind(client->frame, kind);
1691             }
1692         }
1693 #endif
1694     }
1695 }
1696
1697 static void event_handle_dock(ObDock *s, XEvent *e)
1698 {
1699     switch (e->type) {
1700     case ButtonPress:
1701         if (e->xbutton.button == 1)
1702             stacking_raise(DOCK_AS_WINDOW(s));
1703         else if (e->xbutton.button == 2)
1704             stacking_lower(DOCK_AS_WINDOW(s));
1705         break;
1706     case EnterNotify:
1707         dock_hide(FALSE);
1708         break;
1709     case LeaveNotify:
1710         /* don't hide when moving into a dock app */
1711         if (e->xcrossing.detail != NotifyInferior)
1712             dock_hide(TRUE);
1713         break;
1714     }
1715 }
1716
1717 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1718 {
1719     switch (e->type) {
1720     case MotionNotify:
1721         dock_app_drag(app, &e->xmotion);
1722         break;
1723     case UnmapNotify:
1724         if (app->ignore_unmaps) {
1725             app->ignore_unmaps--;
1726             break;
1727         }
1728         dock_unmanage(app, TRUE);
1729         break;
1730     case DestroyNotify:
1731     case ReparentNotify:
1732         dock_unmanage(app, FALSE);
1733         break;
1734     case ConfigureNotify:
1735         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1736         break;
1737     }
1738 }
1739
1740 static ObMenuFrame* find_active_menu(void)
1741 {
1742     GList *it;
1743     ObMenuFrame *ret = NULL;
1744
1745     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1746         ret = it->data;
1747         if (ret->selected)
1748             break;
1749         ret = NULL;
1750     }
1751     return ret;
1752 }
1753
1754 static ObMenuFrame* find_active_or_last_menu(void)
1755 {
1756     ObMenuFrame *ret = NULL;
1757
1758     ret = find_active_menu();
1759     if (!ret && menu_frame_visible)
1760         ret = menu_frame_visible->data;
1761     return ret;
1762 }
1763
1764 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
1765 {
1766     switch (e->type) {
1767     case ButtonPress:
1768     case ButtonRelease:
1769     case MotionNotify:
1770         return prompt_mouse_event(p, e);
1771         break;
1772     case KeyPress:
1773         return prompt_key_event(p, e);
1774         break;
1775     }
1776     return FALSE;
1777 }
1778
1779 static gboolean event_handle_menu_input(XEvent *ev)
1780 {
1781     gboolean ret = FALSE;
1782
1783     if (ev->type == ButtonRelease || ev->type == ButtonPress) {
1784         ObMenuEntryFrame *e;
1785
1786         if (menu_hide_delay_reached() &&
1787             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1788         {
1789             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1790                                             ev->xbutton.y_root)))
1791             {
1792                 if (ev->type == ButtonPress && e->frame->child)
1793                     menu_frame_select(e->frame->child, NULL, TRUE);
1794                 menu_frame_select(e->frame, e, TRUE);
1795                 if (ev->type == ButtonRelease)
1796                     menu_entry_frame_execute(e, ev->xbutton.state);
1797             }
1798             else if (ev->type == ButtonRelease)
1799                 menu_frame_hide_all();
1800         }
1801         ret = TRUE;
1802     }
1803     else if (ev->type == MotionNotify) {
1804         ObMenuFrame *f;
1805         ObMenuEntryFrame *e;
1806
1807         if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1808                                         ev->xmotion.y_root)))
1809             if (!(f = find_active_menu()) ||
1810                 f == e->frame ||
1811                 f->parent == e->frame ||
1812                 f->child == e->frame)
1813                 menu_frame_select(e->frame, e, FALSE);
1814     }
1815     else if (ev->type == KeyPress || ev->type == KeyRelease) {
1816         guint mods;
1817         ObMenuFrame *frame;
1818
1819         /* get the modifiers */
1820         mods = obt_keyboard_only_modmasks(ev->xkey.state);
1821
1822         frame = find_active_or_last_menu();
1823         if (frame == NULL)
1824             g_assert_not_reached(); /* there is no active menu */
1825
1826         /* Allow control while going thru the menu */
1827         else if (ev->type == KeyPress && (mods & ~ControlMask) == 0) {
1828             gunichar unikey;
1829             KeySym sym;
1830
1831             frame->got_press = TRUE;
1832             frame->press_keycode = ev->xkey.keycode;
1833             frame->press_doexec = FALSE;
1834
1835             sym = obt_keyboard_keypress_to_keysym(ev);
1836
1837             if (sym == XK_Escape) {
1838                 menu_frame_hide_all();
1839                 ret = TRUE;
1840             }
1841
1842             else if (sym == XK_Left) {
1843                 /* Left goes to the parent menu */
1844                 if (frame->parent) {
1845                     /* remove focus from the child */
1846                     menu_frame_select(frame, NULL, TRUE);
1847                     /* and put it in the parent */
1848                     menu_frame_select(frame->parent, frame->parent->selected,
1849                                       TRUE);
1850                 }
1851                 ret = TRUE;
1852             }
1853
1854             else if (sym == XK_Right) {
1855                 /* Right goes to the selected submenu */
1856                 if (frame->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1857                 {
1858                     /* make sure it is visible */
1859                     menu_frame_select(frame, frame->selected, TRUE);
1860                     menu_frame_select_next(frame->child);
1861                 }
1862                 ret = TRUE;
1863             }
1864
1865             else if (sym == XK_Up) {
1866                 menu_frame_select_previous(frame);
1867                 ret = TRUE;
1868             }
1869
1870             else if (sym == XK_Down) {
1871                 menu_frame_select_next(frame);
1872                 ret = TRUE;
1873             }
1874
1875             else if (sym == XK_Home) {
1876                 menu_frame_select_first(frame);
1877                 ret = TRUE;
1878             }
1879
1880             else if (sym == XK_End) {
1881                 menu_frame_select_last(frame);
1882                 ret = TRUE;
1883             }
1884
1885             else if (sym == XK_Return || sym == XK_KP_Enter) {
1886                 frame->press_doexec = TRUE;
1887                 ret = TRUE;
1888             }
1889
1890             /* keyboard accelerator shortcuts. (if it was a valid key) */
1891             else if (frame->entries &&
1892                      (unikey =
1893                       obt_keyboard_keypress_to_unichar(menu_frame_ic(frame),
1894                                                        ev)))
1895             {
1896                 GList *start;
1897                 GList *it;
1898                 ObMenuEntryFrame *found = NULL;
1899                 guint num_found = 0;
1900
1901                 /* start after the selected one */
1902                 start = frame->entries;
1903                 if (frame->selected) {
1904                     for (it = start; frame->selected != it->data;
1905                          it = g_list_next(it))
1906                         g_assert(it != NULL); /* nothing was selected? */
1907                     /* next with wraparound */
1908                     start = g_list_next(it);
1909                     if (start == NULL) start = frame->entries;
1910                 }
1911
1912                 it = start;
1913                 do {
1914                     ObMenuEntryFrame *e = it->data;
1915                     gunichar entrykey = 0;
1916
1917                     if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1918                         entrykey = e->entry->data.normal.shortcut;
1919                     else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1920                         entrykey = e->entry->data.submenu.submenu->shortcut;
1921
1922                     if (unikey == entrykey) {
1923                         if (found == NULL) found = e;
1924                         ++num_found;
1925                     }
1926
1927                     /* next with wraparound */
1928                     it = g_list_next(it);
1929                     if (it == NULL) it = frame->entries;
1930                 } while (it != start);
1931
1932                 if (found) {
1933                     menu_frame_select(frame, found, TRUE);
1934
1935                     if (num_found == 1)
1936                         frame->press_doexec = TRUE;
1937                     ret = TRUE;
1938                 }
1939             }
1940         }
1941
1942         /* Use KeyRelease events for running things so that the key release
1943            doesn't get sent to the focused application.
1944
1945            Allow ControlMask only, and don't bother if the menu is empty */
1946         else if (ev->type == KeyRelease && (mods & ~ControlMask) == 0) {
1947             if (frame->press_keycode == ev->xkey.keycode &&
1948                 frame->got_press &&
1949                 frame->press_doexec)
1950             {
1951                 if (frame->child)
1952                     menu_frame_select_next(frame->child);
1953                 else if (frame->selected)
1954                     menu_entry_frame_execute(frame->selected, ev->xkey.state);
1955             }
1956         }
1957     }
1958
1959     return ret;
1960 }
1961
1962 static gboolean event_look_for_menu_enter(XEvent *ev, gpointer data)
1963 {
1964     const ObMenuFrame *f = (ObMenuFrame*)data;
1965     ObMenuEntryFrame *e;
1966     return ev->type == EnterNotify &&
1967         (e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1968         e->frame == f && !e->ignore_enters;
1969 }
1970
1971 static void event_handle_menu(ObMenuFrame *frame, XEvent *ev)
1972 {
1973     ObMenuFrame *f;
1974     ObMenuEntryFrame *e;
1975
1976     switch (ev->type) {
1977     case EnterNotify:
1978         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1979             if (e->ignore_enters)
1980                 --e->ignore_enters;
1981             else if (!(f = find_active_menu()) ||
1982                      f == e->frame ||
1983                      f->parent == e->frame ||
1984                      f->child == e->frame)
1985                 menu_frame_select(e->frame, e, FALSE);
1986         }
1987         break;
1988     case LeaveNotify:
1989         /*ignore leaves when we're already in the window */
1990         if (ev->xcrossing.detail == NotifyInferior)
1991             break;
1992
1993         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1994             /* check if an EnterNotify event is coming, and if not, then select
1995                nothing in the menu */
1996             if (!xqueue_exists_local(event_look_for_menu_enter, e->frame))
1997                 menu_frame_select(e->frame, NULL, FALSE);
1998         }
1999         break;
2000     }
2001 }
2002
2003 static gboolean event_handle_user_input(ObClient *client, XEvent *e)
2004 {
2005     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
2006              e->type == MotionNotify || e->type == KeyPress ||
2007              e->type == KeyRelease);
2008
2009     if (menu_frame_visible) {
2010         if (event_handle_menu_input(e))
2011             /* don't use the event if the menu used it, but if the menu
2012                didn't use it and it's a keypress that is bound, it will
2013                close the menu and be used */
2014             return TRUE;
2015     }
2016
2017     /* if the keyboard interactive action uses the event then dont
2018        use it for bindings. likewise is moveresize uses the event. */
2019     if (actions_interactive_input_event(e) || moveresize_event(e))
2020         return TRUE;
2021
2022     if (moveresize_in_progress)
2023         /* make further actions work on the client being
2024            moved/resized */
2025         client = moveresize_client;
2026
2027     if (e->type == ButtonPress ||
2028         e->type == ButtonRelease ||
2029         e->type == MotionNotify)
2030     {
2031         /* the frame may not be "visible" but they can still click on it
2032            in the case where it is animating before disappearing */
2033         if (!client || !frame_iconify_animating(client->frame))
2034             return mouse_event(client, e);
2035     } else
2036         return keyboard_event((focus_cycle_target ? focus_cycle_target :
2037                                (client ? client : focus_client)), e);
2038
2039     return FALSE;
2040 }
2041
2042 static void focus_delay_dest(gpointer data)
2043 {
2044     g_slice_free(ObFocusDelayData, data);
2045 }
2046
2047 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
2048 {
2049     const ObFocusDelayData *f1 = d1;
2050     return f1->client == d2;
2051 }
2052
2053 static gboolean focus_delay_func(gpointer data)
2054 {
2055     ObFocusDelayData *d = data;
2056     Time old = event_curtime; /* save the curtime */
2057
2058     event_curtime = d->time;
2059     event_curserial = d->serial;
2060     if (client_focus(d->client) && config_focus_raise)
2061         stacking_raise(CLIENT_AS_WINDOW(d->client));
2062     event_curtime = old;
2063     return FALSE; /* no repeat */
2064 }
2065
2066 static gboolean unfocus_delay_func(gpointer data)
2067 {
2068     ObFocusDelayData *d = data;
2069     Time old = event_curtime; /* save the curtime */
2070
2071     event_curtime = d->time;
2072     event_curserial = d->serial;
2073     focus_nothing();
2074     event_curtime = old;
2075     return FALSE; /* no repeat */
2076 }
2077
2078 static void focus_delay_client_dest(ObClient *client, gpointer data)
2079 {
2080     obt_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
2081                                       client, FALSE);
2082     obt_main_loop_timeout_remove_data(ob_main_loop, unfocus_delay_func,
2083                                       client, FALSE);
2084 }
2085
2086 void event_halt_focus_delay(void)
2087 {
2088     /* ignore all enter events up till the event which caused this to occur */
2089     if (event_curserial) event_ignore_enter_range(1, event_curserial);
2090     obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
2091     obt_main_loop_timeout_remove(ob_main_loop, unfocus_delay_func);
2092 }
2093
2094 gulong event_start_ignore_all_enters(void)
2095 {
2096     return NextRequest(obt_display);
2097 }
2098
2099 static void event_ignore_enter_range(gulong start, gulong end)
2100 {
2101     ObSerialRange *r;
2102
2103     g_assert(start != 0);
2104     g_assert(end != 0);
2105
2106     r = g_slice_new(ObSerialRange);
2107     r->start = start;
2108     r->end = end;
2109     ignore_serials = g_slist_prepend(ignore_serials, r);
2110
2111     ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu",
2112                   r->start, r->end);
2113
2114     /* increment the serial so we don't ignore events we weren't meant to */
2115     OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
2116 }
2117
2118 void event_end_ignore_all_enters(gulong start)
2119 {
2120     /* Use (NextRequest-1) so that we ignore up to the current serial only.
2121        Inside event_ignore_enter_range, we increment the serial by one, but if
2122        we ignore that serial too, then any enter events generated by mouse
2123        movement will be ignored until we create some further network traffic.
2124        Instead ignore up to NextRequest-1, then when we increment the serial,
2125        we will be *past* the range of ignored serials */
2126     event_ignore_enter_range(start, NextRequest(obt_display)-1);
2127 }
2128
2129 static gboolean is_enter_focus_event_ignored(gulong serial)
2130 {
2131     GSList *it, *next;
2132
2133     for (it = ignore_serials; it; it = next) {
2134         ObSerialRange *r = it->data;
2135
2136         next = g_slist_next(it);
2137
2138         if ((glong)(serial - r->end) > 0) {
2139             /* past the end */
2140             ignore_serials = g_slist_delete_link(ignore_serials, it);
2141             g_slice_free(ObSerialRange, r);
2142         }
2143         else if ((glong)(serial - r->start) >= 0)
2144             return TRUE;
2145     }
2146     return FALSE;
2147 }
2148
2149 void event_cancel_all_key_grabs(void)
2150 {
2151     if (actions_interactive_act_running()) {
2152         actions_interactive_cancel_act();
2153         ob_debug("KILLED interactive action");
2154     }
2155     else if (menu_frame_visible) {
2156         menu_frame_hide_all();
2157         ob_debug("KILLED open menus");
2158     }
2159     else if (moveresize_in_progress) {
2160         moveresize_end(TRUE);
2161         ob_debug("KILLED interactive moveresize");
2162     }
2163     else if (grab_on_keyboard()) {
2164         ungrab_keyboard();
2165         ob_debug("KILLED active grab on keyboard");
2166     }
2167     else
2168         ungrab_passive_key();
2169
2170     XSync(obt_display, FALSE);
2171 }
2172
2173 gboolean event_time_after(guint32 t1, guint32 t2)
2174 {
2175     g_assert(t1 != CurrentTime);
2176     g_assert(t2 != CurrentTime);
2177
2178     /*
2179       Timestamp values wrap around (after about 49.7 days). The server, given
2180       its current time is represented by timestamp T, always interprets
2181       timestamps from clients by treating half of the timestamp space as being
2182       later in time than T.
2183       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2184     */
2185
2186     /* TIME_HALF is not half of the number space of a Time type variable.
2187      * Rather, it is half the number space of a timestamp value, which is
2188      * always 32 bits. */
2189 #define TIME_HALF (guint32)(1 << 31)
2190
2191     if (t2 >= TIME_HALF)
2192         /* t2 is in the second half so t1 might wrap around and be smaller than
2193            t2 */
2194         return t1 >= t2 || t1 < (t2 + TIME_HALF);
2195     else
2196         /* t2 is in the first half so t1 has to come after it */
2197         return t1 >= t2 && t1 < (t2 + TIME_HALF);
2198 }
2199
2200 gboolean find_timestamp(XEvent *e, gpointer data)
2201 {
2202     const Time t = event_get_timestamp(e);
2203     if (t != CurrentTime) {
2204         event_curtime = t;
2205         return TRUE;
2206     }
2207     else
2208         return FALSE;
2209 }
2210
2211 Time event_time(void)
2212 {
2213     if (event_curtime) return event_curtime;
2214
2215     /* Some events don't come with timestamps :(
2216        ...but we can get one anyways >:) */
2217
2218     /* Generate a timestamp so there is guaranteed at least one in the queue
2219        eventually */
2220     XChangeProperty(obt_display, screen_support_win,
2221                     OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING),
2222                     8, PropModeAppend, NULL, 0);
2223
2224     /* Grab the first timestamp available */
2225     xqueue_exists(find_timestamp, NULL);
2226
2227     /*g_assert(event_curtime != CurrentTime);*/
2228
2229     /* Save the time so we don't have to do this again for this event */
2230     return event_curtime;
2231 }
2232
2233 Time event_source_time(void)
2234 {
2235     return event_sourcetime;
2236 }