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