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