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