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