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