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