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