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