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