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