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