]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
Rename ObFramePlugin to ObFrameEngine
[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 "engine_interface.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     }
420     switch (detail) {
421     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
422     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
423     case NotifyInferior:    detailstr="NotifyInferior";    break;
424     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
425     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
426     case NotifyPointer:     detailstr="NotifyPointer";     break;
427     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
428     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
429     }
430
431     if (mode == NotifyGrab || mode == NotifyUngrab)
432         return;
433
434     g_assert(modestr);
435     g_assert(detailstr);
436     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s",
437                   (e->xfocus.type == FocusIn ? "In" : "Out"),
438                   win,
439                   modestr, detailstr);
440
441 }
442
443 static gboolean event_ignore(XEvent *e, ObClient *client)
444 {
445     switch(e->type) {
446     case FocusIn:
447         print_focusevent(e);
448         if (!wanted_focusevent(e, FALSE))
449             return TRUE;
450         break;
451     case FocusOut:
452         print_focusevent(e);
453         if (!wanted_focusevent(e, FALSE))
454             return TRUE;
455         break;
456     }
457     return FALSE;
458 }
459
460 static void event_process(const XEvent *ec, gpointer data)
461 {
462     XEvent ee, *e;
463     ObEventData *ed = data;
464
465     Window window;
466     ObClient *client = NULL;
467     ObDock *dock = NULL;
468     ObDockApp *dockapp = NULL;
469     ObWindow *obwin = NULL;
470     ObMenuFrame *menu = NULL;
471     ObPrompt *prompt = NULL;
472
473     /* make a copy we can mangle */
474     ee = *ec;
475     e = &ee;
476
477     window = event_get_window(e);
478     if (window == obt_root(ob_screen))
479         /* don't do any lookups, waste of cpu */;
480     else if ((obwin = window_find(window))) {
481         switch (obwin->type) {
482         case OB_WINDOW_CLASS_DOCK:
483             dock = WINDOW_AS_DOCK(obwin);
484             break;
485         case OB_WINDOW_CLASS_CLIENT:
486             client = WINDOW_AS_CLIENT(obwin);
487             /* events on clients can be events on prompt windows too */
488             prompt = client->prompt;
489             break;
490         case OB_WINDOW_CLASS_MENUFRAME:
491             menu = WINDOW_AS_MENUFRAME(obwin);
492             break;
493         case OB_WINDOW_CLASS_INTERNAL:
494             /* we don't do anything with events directly on these windows */
495             break;
496         case OB_WINDOW_CLASS_PROMPT:
497             prompt = WINDOW_AS_PROMPT(obwin);
498             break;
499         }
500     }
501     else
502         dockapp = dock_find_dockapp(window);
503
504     event_set_curtime(e);
505     event_curserial = e->xany.serial;
506     event_hack_mods(e);
507     if (event_ignore(e, client)) {
508         if (ed)
509             ed->ignored = TRUE;
510         return;
511     } else if (ed)
512             ed->ignored = FALSE;
513
514     /* deal with it in the kernel */
515
516     if (e->type == FocusIn) {
517         if (client &&
518             e->xfocus.detail == NotifyInferior)
519         {
520             ob_debug_type(OB_DEBUG_FOCUS,
521                           "Focus went to the frame window");
522
523             focus_left_screen = FALSE;
524
525             focus_fallback(FALSE, config_focus_under_mouse, TRUE, TRUE);
526
527             /* We don't get a FocusOut for this case, because it's just moving
528                from our Inferior up to us. This happens when iconifying a
529                window with RevertToParent focus */
530             render_plugin->frame_set_is_focus(client->frame, FALSE);
531             render_plugin->frame_update_layout (client->frame, FALSE, FALSE);
532             render_plugin->frame_update_skin(client->frame);
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             render_plugin->frame_set_is_focus(client->frame, TRUE);
596             render_plugin->frame_update_layout (client->frame, FALSE, FALSE);
597             render_plugin->frame_update_skin (client->frame);
598             focus_set_client(client);
599             client_calc_layer(client);
600             client_bring_helper_windows(client);
601         }
602     } else if (e->type == FocusOut) {
603         XEvent ce;
604
605         /* Look for the followup FocusIn */
606         if (!XCheckIfEvent(obt_display, &ce, event_look_for_focusin, NULL)) {
607             /* There is no FocusIn, this means focus went to a window that
608                is not being managed, or a window on another screen. */
609             Window win, root;
610             gint i;
611             guint u;
612             obt_display_ignore_errors(TRUE);
613             if (XGetInputFocus(obt_display, &win, &i) &&
614                 XGetGeometry(obt_display, win, &root, &i,&i,&u,&u,&u,&u) &&
615                 root != obt_root(ob_screen))
616             {
617                 ob_debug_type(OB_DEBUG_FOCUS,
618                               "Focus went to another screen !");
619                 focus_left_screen = TRUE;
620             }
621             else
622                 ob_debug_type(OB_DEBUG_FOCUS,
623                               "Focus went to a black hole !");
624             obt_display_ignore_errors(FALSE);
625             /* nothing is focused */
626             focus_set_client(NULL);
627         } else {
628             /* Focus moved, so process the FocusIn event */
629             ObEventData ed = { .ignored = FALSE };
630             event_process(&ce, &ed);
631             if (ed.ignored) {
632                 /* The FocusIn was ignored, this means it was on a window
633                    that isn't a client. */
634                 ob_debug_type(OB_DEBUG_FOCUS,
635                               "Focus went to an unmanaged window 0x%x !",
636                               ce.xfocus.window);
637                 focus_fallback(TRUE, config_focus_under_mouse, TRUE, TRUE);
638             }
639         }
640
641         if (client && client != focus_client) {
642           render_plugin->frame_set_is_focus(client->frame, FALSE);
643           render_plugin->frame_update_layout (client->frame, FALSE, FALSE);
644           render_plugin->frame_update_skin(client->frame);
645             /* focus_set_client(NULL) has already been called in this
646                section or by focus_fallback */
647         }
648     }
649     else if (client)
650         event_handle_client(client, e);
651     else if (dockapp)
652         event_handle_dockapp(dockapp, e);
653     else if (dock)
654         event_handle_dock(dock, e);
655     else if (menu)
656         event_handle_menu(menu, e);
657     else if (window == obt_root(ob_screen))
658         event_handle_root(e);
659     else if (e->type == MapRequest)
660         window_manage(window);
661     else if (e->type == MappingNotify) {
662         /* keyboard layout changes for modifier mapping changes. reload the
663            modifier map, and rebind all the key bindings as appropriate */
664         ob_debug("Kepboard map changed. Reloading keyboard bindings.");
665         obt_keyboard_reload();
666         keyboard_rebind();
667     }
668     else if (e->type == ClientMessage) {
669         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
670            windows that are not managed yet. */
671         if (e->xclient.message_type ==
672             OBT_PROP_ATOM(NET_REQUEST_FRAME_EXTENTS))
673         {
674             /* Pretend to manage the client, getting information used to
675                determine its decorations */
676             ObClient *c = client_fake_manage(e->xclient.window);
677             gulong vals[4];
678
679             /* set the frame extents on the window */
680             Strut size = render_plugin->frame_get_size(c->frame);
681             vals[0] = size.left;
682             vals[1] = size.right;
683             vals[2] = size.top;
684             vals[3] = size.bottom;
685             OBT_PROP_SETA32(e->xclient.window, NET_FRAME_EXTENTS,
686                             CARDINAL, vals, 4);
687
688             /* Free the pretend client */
689             client_fake_unmanage(c);
690         }
691     }
692     else if (e->type == ConfigureRequest) {
693         /* unhandled configure requests must be used to configure the
694            window directly */
695         XWindowChanges xwc;
696
697         xwc.x = e->xconfigurerequest.x;
698         xwc.y = e->xconfigurerequest.y;
699         xwc.width = e->xconfigurerequest.width;
700         xwc.height = e->xconfigurerequest.height;
701         xwc.border_width = e->xconfigurerequest.border_width;
702         xwc.sibling = e->xconfigurerequest.above;
703         xwc.stack_mode = e->xconfigurerequest.detail;
704
705         /* we are not to be held responsible if someone sends us an
706            invalid request! */
707         obt_display_ignore_errors(TRUE);
708         XConfigureWindow(obt_display, window,
709                          e->xconfigurerequest.value_mask, &xwc);
710         obt_display_ignore_errors(FALSE);
711     }
712 #ifdef SYNC
713     else if (obt_display_extension_sync &&
714              e->type == obt_display_extension_sync_basep + XSyncAlarmNotify)
715     {
716         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
717         if (se->alarm == moveresize_alarm && render_plugin->moveresize_in_progress)
718             moveresize_event(e);
719     }
720 #endif
721
722     if (prompt && event_handle_prompt(prompt, e))
723         ;
724     else if (e->type == ButtonPress || e->type == ButtonRelease) {
725         /* If the button press was on some non-root window, or was physically
726            on the root window, then process it */
727         if (window != obt_root(ob_screen) ||
728             e->xbutton.subwindow == None)
729         {
730             event_handle_user_input(client, e);
731         }
732         /* Otherwise only process it if it was physically on an openbox
733            internal window */
734         else {
735             ObWindow *w;
736
737             if ((w = window_find(e->xbutton.subwindow)) &&
738                 WINDOW_IS_INTERNAL(w))
739             {
740                 event_handle_user_input(client, e);
741             }
742         }
743     }
744     else if (e->type == KeyPress || e->type == KeyRelease ||
745              e->type == MotionNotify)
746         event_handle_user_input(client, e);
747
748     /* if something happens and it's not from an XEvent, then we don't know
749        the time */
750     event_curtime = CurrentTime;
751     event_curserial = 0;
752 }
753
754 static void event_handle_root(XEvent *e)
755 {
756     Atom msgtype;
757
758     switch(e->type) {
759     case SelectionClear:
760         ob_debug("Another WM has requested to replace us. Exiting.");
761         ob_exit_replace();
762         break;
763
764     case ClientMessage:
765         if (e->xclient.format != 32) break;
766
767         msgtype = e->xclient.message_type;
768         if (msgtype == OBT_PROP_ATOM(NET_CURRENT_DESKTOP)) {
769             guint d = e->xclient.data.l[0];
770             if (d < screen_num_desktops) {
771                 event_curtime = e->xclient.data.l[1];
772                 if (event_curtime == 0)
773                     ob_debug_type(OB_DEBUG_APP_BUGS,
774                                   "_NET_CURRENT_DESKTOP message is missing "
775                                   "a timestamp");
776                 screen_set_desktop(d, TRUE);
777             }
778         } else if (msgtype == OBT_PROP_ATOM(NET_NUMBER_OF_DESKTOPS)) {
779             guint d = e->xclient.data.l[0];
780             if (d > 0 && d <= 1000)
781                 screen_set_num_desktops(d);
782         } else if (msgtype == OBT_PROP_ATOM(NET_SHOWING_DESKTOP)) {
783             screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
784         } else if (msgtype == OBT_PROP_ATOM(OB_CONTROL)) {
785             ob_debug("OB_CONTROL: %d", e->xclient.data.l[0]);
786             if (e->xclient.data.l[0] == 1)
787                 ob_reconfigure();
788             else if (e->xclient.data.l[0] == 2)
789                 ob_restart();
790             else if (e->xclient.data.l[0] == 3)
791                 ob_exit(0);
792         } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
793             if ((Atom)e->xclient.data.l[0] == OBT_PROP_ATOM(NET_WM_PING))
794                 ping_got_pong(e->xclient.data.l[1]);
795         }
796         break;
797     case PropertyNotify:
798         if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_NAMES)) {
799             ob_debug("UPDATE DESKTOP NAMES");
800             screen_update_desktop_names();
801         }
802         else if (e->xproperty.atom == OBT_PROP_ATOM(NET_DESKTOP_LAYOUT))
803             screen_update_layout();
804         break;
805     case ConfigureNotify:
806 #ifdef XRANDR
807         XRRUpdateConfiguration(e);
808 #endif
809         screen_resize();
810         break;
811     default:
812         ;
813     }
814 }
815
816 void event_enter_client(ObClient *client)
817 {
818     g_assert(config_focus_follow);
819
820     if (is_enter_focus_event_ignored(event_curserial)) {
821         ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n"
822                       "on client 0x%x", event_curserial, client->w_client);
823         return;
824     }
825
826     if (client_enter_focusable(client) && client_can_focus(client)) {
827         if (config_focus_delay) {
828             ObFocusDelayData *data;
829
830             obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
831
832             data = g_new(ObFocusDelayData, 1);
833             data->client = client;
834             data->time = event_curtime;
835             data->serial = event_curserial;
836
837             obt_main_loop_timeout_add(ob_main_loop,
838                                       config_focus_delay * 1000,
839                                       focus_delay_func,
840                                       data, focus_delay_cmp, focus_delay_dest);
841         } else {
842             ObFocusDelayData data;
843             data.client = client;
844             data.time = event_curtime;
845             data.serial = event_curserial;
846             focus_delay_func(&data);
847         }
848     }
849 }
850
851 static void event_handle_client(ObClient *client, XEvent *e)
852 {
853     XEvent ce;
854     Atom msgtype;
855     ObFrameContext con;
856     static gint px = -1, py = -1;
857     static guint pb = 0;
858
859     switch (e->type) {
860     case ButtonPress:
861         /* save where the press occured for the first button pressed */
862         if (!pb) {
863             pb = e->xbutton.button;
864             px = e->xbutton.x;
865             py = e->xbutton.y;
866         }
867     case ButtonRelease:
868         /* Wheel buttons don't draw because they are an instant click, so it
869            is a waste of resources to go drawing it.
870            if the user is doing an intereactive thing, or has a menu open then
871            the mouse is grabbed (possibly) and if we get these events we don't
872            want to deal with them
873         */
874         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
875             !grab_on_keyboard())
876         {
877             /* use where the press occured */
878             con = plugin_frame_context(client, e->xbutton.window, px, py);
879             con = mouse_button_frame_context(con, e->xbutton.button,
880                                              e->xbutton.state);
881
882             if (e->type == ButtonRelease && e->xbutton.button == pb)
883                 pb = 0, px = py = -1;
884
885             ObFrameButton current_button = OB_BUTTON_NONE;
886
887             switch (con) {
888             case OB_FRAME_CONTEXT_MAXIMIZE:
889                 current_button = OB_BUTTON_MAX;
890                 break;
891             case OB_FRAME_CONTEXT_CLOSE:
892         current_button = OB_BUTTON_CLOSE;
893                 break;
894             case OB_FRAME_CONTEXT_ICONIFY:
895         current_button = OB_BUTTON_ICONIFY;
896                 break;
897             case OB_FRAME_CONTEXT_ALLDESKTOPS:
898         current_button = OB_BUTTON_DESK;
899                 break;
900             case OB_FRAME_CONTEXT_SHADE:
901         current_button = OB_BUTTON_SHADE;
902                 break;
903             default:
904                 /* nothing changes with clicks for any other contexts */
905                 break;
906             }
907
908             if (current_button)
909         render_plugin->frame_set_hover_flag (client->frame, current_button);
910         }
911         break;
912     case MotionNotify:
913         /* when there is a grab on the pointer, we won't get enter/leave
914            notifies, but we still get motion events */
915         if (grab_on_pointer()) break;
916
917         con = plugin_frame_context(client, e->xmotion.window,
918                             e->xmotion.x, e->xmotion.y);
919         switch (con) {
920         case OB_FRAME_CONTEXT_TITLEBAR:
921         case OB_FRAME_CONTEXT_TLCORNER:
922         case OB_FRAME_CONTEXT_TRCORNER:
923             /* we've left the button area inside the titlebar */
924             render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
925             break;
926         case OB_FRAME_CONTEXT_MAXIMIZE:
927         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_MAX);
928             break;
929         case OB_FRAME_CONTEXT_ALLDESKTOPS:
930         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_DESK);
931             break;
932         case OB_FRAME_CONTEXT_SHADE:
933         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_SHADE);
934             break;
935         case OB_FRAME_CONTEXT_ICONIFY:
936         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_ICONIFY);
937             break;
938         case OB_FRAME_CONTEXT_CLOSE:
939         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_CLOSE);
940             break;
941         default:
942             break;
943         }
944         break;
945     case LeaveNotify:
946         con = plugin_frame_context(client, e->xcrossing.window,
947                             e->xcrossing.x, e->xcrossing.y);
948         switch (con) {
949         case OB_FRAME_CONTEXT_TITLEBAR:
950         case OB_FRAME_CONTEXT_TLCORNER:
951         case OB_FRAME_CONTEXT_TRCORNER:
952             /* we've left the button area inside the titlebar */
953         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
954             break;
955         case OB_FRAME_CONTEXT_MAXIMIZE:
956         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
957             break;
958         case OB_FRAME_CONTEXT_ALLDESKTOPS:
959         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
960             break;
961         case OB_FRAME_CONTEXT_SHADE:
962         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
963             break;
964         case OB_FRAME_CONTEXT_ICONIFY:
965         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
966             break;
967         case OB_FRAME_CONTEXT_CLOSE:
968         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_NONE);
969             break;
970         case OB_FRAME_CONTEXT_FRAME:
971             /* When the mouse leaves an animating window, don't use the
972                corresponding enter events. Pretend like the animating window
973                doesn't even exist..! */
974             if (render_plugin->frame_iconify_animating(client->frame))
975                 event_end_ignore_all_enters(event_start_ignore_all_enters());
976
977             ob_debug_type(OB_DEBUG_FOCUS,
978                           "%sNotify mode %d detail %d on %lx",
979                           (e->type == EnterNotify ? "Enter" : "Leave"),
980                           e->xcrossing.mode,
981                           e->xcrossing.detail, (client?client->w_client:0));
982             if (grab_on_keyboard())
983                 break;
984             if (config_focus_follow && config_focus_delay &&
985                 /* leave inferior events can happen when the mouse goes onto
986                    the window's border and then into the window before the
987                    delay is up */
988                 e->xcrossing.detail != NotifyInferior)
989             {
990                 obt_main_loop_timeout_remove_data(ob_main_loop,
991                                                   focus_delay_func,
992                                                   client, FALSE);
993             }
994             break;
995         default:
996             break;
997         }
998         break;
999     case EnterNotify:
1000     {
1001         con = plugin_frame_context(client, e->xcrossing.window,
1002                             e->xcrossing.x, e->xcrossing.y);
1003         switch (con) {
1004         case OB_FRAME_CONTEXT_MAXIMIZE:
1005         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_MAX);
1006             break;
1007         case OB_FRAME_CONTEXT_ALLDESKTOPS:
1008         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_DESK);
1009             break;
1010         case OB_FRAME_CONTEXT_SHADE:
1011         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_SHADE);
1012             break;
1013         case OB_FRAME_CONTEXT_ICONIFY:
1014         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_ICONIFY);
1015             break;
1016         case OB_FRAME_CONTEXT_CLOSE:
1017         render_plugin->frame_set_hover_flag(client->frame, OB_BUTTON_CLOSE);
1018             break;
1019         case OB_FRAME_CONTEXT_FRAME:
1020             if (grab_on_keyboard())
1021                 break;
1022             if (e->xcrossing.mode == NotifyGrab ||
1023                 e->xcrossing.mode == NotifyUngrab ||
1024                 /*ignore enters when we're already in the window */
1025                 e->xcrossing.detail == NotifyInferior)
1026             {
1027                 ob_debug_type(OB_DEBUG_FOCUS,
1028                               "%sNotify mode %d detail %d serial %lu on %lx "
1029                               "IGNORED",
1030                               (e->type == EnterNotify ? "Enter" : "Leave"),
1031                               e->xcrossing.mode,
1032                               e->xcrossing.detail,
1033                               e->xcrossing.serial,
1034                               client?client->w_client:0);
1035             }
1036             else {
1037                 ob_debug_type(OB_DEBUG_FOCUS,
1038                               "%sNotify mode %d detail %d serial %lu on %lx, "
1039                               "focusing window",
1040                               (e->type == EnterNotify ? "Enter" : "Leave"),
1041                               e->xcrossing.mode,
1042                               e->xcrossing.detail,
1043                               e->xcrossing.serial,
1044                               (client?client->w_client:0));
1045                 if (config_focus_follow)
1046                     event_enter_client(client);
1047             }
1048             break;
1049         default:
1050             break;
1051         }
1052         break;
1053     }
1054     case ConfigureRequest:
1055     {
1056         /* dont compress these unless you're going to watch for property
1057            notifies in between (these can change what the configure would
1058            do to the window).
1059            also you can't compress stacking events
1060         */
1061
1062         gint x, y, w, h;
1063         gboolean move = FALSE;
1064         gboolean resize = FALSE;
1065
1066         /* get the current area */
1067         RECT_TO_DIMS(client->area, x, y, w, h);
1068
1069         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1070                  "visibile %d",
1071                  client->title,
1072                  screen_desktop, client->wmstate, render_plugin->frame_is_visible(client->frame),
1073                  x, y, w, h, client->border_width);
1074
1075         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1076             if (client->border_width != e->xconfigurerequest.border_width) {
1077                 client->border_width = e->xconfigurerequest.border_width;
1078
1079                 /* if the border width is changing then that is the same
1080                    as requesting a resize, but we don't actually change
1081                    the client's border, so it will change their root
1082                    coordiantes (since they include the border width) and
1083                    we need to a notify then */
1084                 move = TRUE;
1085             }
1086
1087
1088         if (e->xconfigurerequest.value_mask & CWStackMode) {
1089             ObClient *sibling = NULL;
1090             gulong ignore_start;
1091             gboolean ok = TRUE;
1092
1093             /* get the sibling */
1094             if (e->xconfigurerequest.value_mask & CWSibling) {
1095                 ObWindow *win;
1096                 win = window_find(e->xconfigurerequest.above);
1097                 if (win && WINDOW_IS_CLIENT(win) &&
1098                     WINDOW_AS_CLIENT(win) != client)
1099                 {
1100                     sibling = WINDOW_AS_CLIENT(win);
1101                 }
1102                 else
1103                     /* an invalid sibling was specified so don't restack at
1104                        all, it won't make sense no matter what we do */
1105                     ok = FALSE;
1106             }
1107
1108             if (ok) {
1109                 if (!config_focus_under_mouse)
1110                     ignore_start = event_start_ignore_all_enters();
1111                 stacking_restack_request(client, sibling,
1112                                          e->xconfigurerequest.detail);
1113                 if (!config_focus_under_mouse)
1114                     event_end_ignore_all_enters(ignore_start);
1115             }
1116
1117             /* a stacking change moves the window without resizing */
1118             move = TRUE;
1119         }
1120
1121         if ((e->xconfigurerequest.value_mask & CWX) ||
1122             (e->xconfigurerequest.value_mask & CWY) ||
1123             (e->xconfigurerequest.value_mask & CWWidth) ||
1124             (e->xconfigurerequest.value_mask & CWHeight))
1125         {
1126             if (e->xconfigurerequest.value_mask & CWX) {
1127                 /* don't allow clients to move shaded windows (fvwm does this)
1128                  */
1129                 if (!client->shaded)
1130                     x = e->xconfigurerequest.x;
1131                 move = TRUE;
1132             }
1133             if (e->xconfigurerequest.value_mask & CWY) {
1134                 /* don't allow clients to move shaded windows (fvwm does this)
1135                  */
1136                 if (!client->shaded)
1137                     y = e->xconfigurerequest.y;
1138                 move = TRUE;
1139             }
1140
1141             if (e->xconfigurerequest.value_mask & CWWidth) {
1142                 w = e->xconfigurerequest.width;
1143                 resize = TRUE;
1144             }
1145             if (e->xconfigurerequest.value_mask & CWHeight) {
1146                 h = e->xconfigurerequest.height;
1147                 resize = TRUE;
1148             }
1149         }
1150
1151         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1152                  "move %d resize %d",
1153                  e->xconfigurerequest.value_mask & CWX, x,
1154                  e->xconfigurerequest.value_mask & CWY, y,
1155                  e->xconfigurerequest.value_mask & CWWidth, w,
1156                  e->xconfigurerequest.value_mask & CWHeight, h,
1157                  move, resize);
1158
1159         /* check for broken apps moving to their root position
1160
1161            XXX remove this some day...that would be nice. right now all
1162            kde apps do this when they try activate themselves on another
1163            desktop. eg. open amarok window on desktop 1, switch to desktop
1164            2, click amarok tray icon. it will move by its decoration size.
1165         */
1166         Strut size = render_plugin->frame_get_size(client->frame);
1167         Rect area = render_plugin->frame_get_window_area(client->frame);
1168         if (x != client->area.x &&
1169             x == (area.x + size.left -
1170                   (gint)client->border_width) &&
1171             y != client->area.y &&
1172             y == (area.y + size.top -
1173                   (gint)client->border_width) &&
1174             w == client->area.width &&
1175             h == client->area.height)
1176         {
1177             ob_debug_type(OB_DEBUG_APP_BUGS,
1178                           "Application %s is trying to move via "
1179                           "ConfigureRequest to it's root window position "
1180                           "but it is not using StaticGravity",
1181                           client->title);
1182             /* don't move it */
1183             x = client->area.x;
1184             y = client->area.y;
1185
1186             /* they still requested a move, so don't change whether a
1187                notify is sent or not */
1188         }
1189
1190         {
1191             gint lw,lh;
1192
1193             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1194
1195             /* if x was not given, then use gravity to figure out the new
1196                x.  the reference point should not be moved */
1197             if ((e->xconfigurerequest.value_mask & CWWidth &&
1198                  !(e->xconfigurerequest.value_mask & CWX)))
1199                 client_gravity_resize_w(client, &x, client->area.width, w);
1200             /* if y was not given, then use gravity to figure out the new
1201                y.  the reference point should not be moved */
1202             if ((e->xconfigurerequest.value_mask & CWHeight &&
1203                  !(e->xconfigurerequest.value_mask & CWY)))
1204                 client_gravity_resize_h(client, &y, client->area.height,h);
1205
1206             client_find_onscreen(client, &x, &y, w, h, FALSE);
1207
1208             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d",
1209                      x, y, w, h);
1210             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1211         }
1212         break;
1213     }
1214     case UnmapNotify:
1215         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1216                  "ignores left %d",
1217                  client->w_client, e->xunmap.event, e->xunmap.from_configure,
1218                  client->ignore_unmaps);
1219         if (client->ignore_unmaps) {
1220             client->ignore_unmaps--;
1221             break;
1222         }
1223         client_unmanage(client);
1224         break;
1225     case DestroyNotify:
1226         ob_debug("DestroyNotify for window 0x%x", client->w_client);
1227         client_unmanage(client);
1228         break;
1229     case ReparentNotify:
1230         /* this is when the client is first taken captive in the frame */
1231         if (e->xreparent.parent == render_plugin->frame_get_window(client->frame)) break;
1232
1233         /*
1234           This event is quite rare and is usually handled in unmapHandler.
1235           However, if the window is unmapped when the reparent event occurs,
1236           the window manager never sees it because an unmap event is not sent
1237           to an already unmapped window.
1238         */
1239
1240         /* we don't want the reparent event, put it back on the stack for the
1241            X server to deal with after we unmanage the window */
1242         XPutBackEvent(obt_display, e);
1243
1244         ob_debug("ReparentNotify for window 0x%x", client->w_client);
1245         client_unmanage(client);
1246         break;
1247     case MapRequest:
1248         ob_debug("MapRequest for 0x%lx", client->w_client);
1249         if (!client->iconic) break; /* this normally doesn't happen, but if it
1250                                        does, we don't want it!
1251                                        it can happen now when the window is on
1252                                        another desktop, but we still don't
1253                                        want it! */
1254         client_activate(client, FALSE, TRUE, TRUE, TRUE);
1255         break;
1256     case ClientMessage:
1257         /* validate cuz we query stuff off the client here */
1258         if (!client_validate(client)) break;
1259
1260         if (e->xclient.format != 32) return;
1261
1262         msgtype = e->xclient.message_type;
1263         if (msgtype == OBT_PROP_ATOM(WM_CHANGE_STATE)) {
1264             /* compress changes into a single change */
1265             while (XCheckTypedWindowEvent(obt_display, client->w_client,
1266                                           e->type, &ce)) {
1267                 /* XXX: it would be nice to compress ALL messages of a
1268                    type, not just messages in a row without other
1269                    message types between. */
1270                 if (ce.xclient.message_type != msgtype) {
1271                     XPutBackEvent(obt_display, &ce);
1272                     break;
1273                 }
1274                 e->xclient = ce.xclient;
1275             }
1276             client_set_wm_state(client, e->xclient.data.l[0]);
1277         } else if (msgtype == OBT_PROP_ATOM(NET_WM_DESKTOP)) {
1278             /* compress changes into a single change */
1279             while (XCheckTypedWindowEvent(obt_display, client->w_client,
1280                                           e->type, &ce)) {
1281                 /* XXX: it would be nice to compress ALL messages of a
1282                    type, not just messages in a row without other
1283                    message types between. */
1284                 if (ce.xclient.message_type != msgtype) {
1285                     XPutBackEvent(obt_display, &ce);
1286                     break;
1287                 }
1288                 e->xclient = ce.xclient;
1289             }
1290             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1291                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1292                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1293                                    FALSE, FALSE);
1294         } else if (msgtype == OBT_PROP_ATOM(NET_WM_STATE)) {
1295             gulong ignore_start;
1296
1297             /* can't compress these */
1298             ob_debug("net_wm_state %s %ld %ld for 0x%lx",
1299                      (e->xclient.data.l[0] == 0 ? "Remove" :
1300                       e->xclient.data.l[0] == 1 ? "Add" :
1301                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1302                      e->xclient.data.l[1], e->xclient.data.l[2],
1303                      client->w_client);
1304
1305             /* ignore enter events caused by these like ob actions do */
1306             if (!config_focus_under_mouse)
1307                 ignore_start = event_start_ignore_all_enters();
1308             client_set_state(client, e->xclient.data.l[0],
1309                              e->xclient.data.l[1], e->xclient.data.l[2]);
1310             if (!config_focus_under_mouse)
1311                 event_end_ignore_all_enters(ignore_start);
1312         } else if (msgtype == OBT_PROP_ATOM(NET_CLOSE_WINDOW)) {
1313             ob_debug("net_close_window for 0x%lx", client->w_client);
1314             client_close(client);
1315         } else if (msgtype == OBT_PROP_ATOM(NET_ACTIVE_WINDOW)) {
1316             ob_debug("net_active_window for 0x%lx source=%s",
1317                      client->w_client,
1318                      (e->xclient.data.l[0] == 0 ? "unknown" :
1319                       (e->xclient.data.l[0] == 1 ? "application" :
1320                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1321             /* XXX make use of data.l[2] !? */
1322             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1323                 /* don't use the user's timestamp for client_focus, cuz if it's
1324                    an old broken timestamp (happens all the time) then focus
1325                    won't move even though we're trying to move it
1326                   event_curtime = e->xclient.data.l[1];*/
1327                 if (e->xclient.data.l[1] == 0)
1328                     ob_debug_type(OB_DEBUG_APP_BUGS,
1329                                   "_NET_ACTIVE_WINDOW message for window %s is"
1330                                   " missing a timestamp", client->title);
1331             } else
1332                 ob_debug_type(OB_DEBUG_APP_BUGS,
1333                               "_NET_ACTIVE_WINDOW message for window %s is "
1334                               "missing source indication");
1335             client_activate(client, FALSE, TRUE, TRUE,
1336                             (e->xclient.data.l[0] == 0 ||
1337                              e->xclient.data.l[0] == 2));
1338         } else if (msgtype == OBT_PROP_ATOM(NET_WM_MOVERESIZE)) {
1339             ob_debug("net_wm_moveresize for 0x%lx direction %d",
1340                      client->w_client, e->xclient.data.l[2]);
1341             if ((Atom)e->xclient.data.l[2] ==
1342                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
1343                 (Atom)e->xclient.data.l[2] ==
1344                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) ||
1345                 (Atom)e->xclient.data.l[2] ==
1346                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT) ||
1347                 (Atom)e->xclient.data.l[2] ==
1348                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1349                 (Atom)e->xclient.data.l[2] ==
1350                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
1351                 (Atom)e->xclient.data.l[2] ==
1352                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) ||
1353                 (Atom)e->xclient.data.l[2] ==
1354                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM) ||
1355                 (Atom)e->xclient.data.l[2] ==
1356                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) ||
1357                 (Atom)e->xclient.data.l[2] ==
1358                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) ||
1359                 (Atom)e->xclient.data.l[2] ==
1360                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) ||
1361                 (Atom)e->xclient.data.l[2] ==
1362                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD) ||
1363                 (Atom)e->xclient.data.l[2] ==
1364                 OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
1365             {
1366                 moveresize_start(client, e->xclient.data.l[0],
1367                                  e->xclient.data.l[1], e->xclient.data.l[3],
1368                                  e->xclient.data.l[2]);
1369             }
1370             else if ((Atom)e->xclient.data.l[2] ==
1371                      OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL))
1372                 moveresize_end(TRUE);
1373         } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) {
1374             gint ograv, x, y, w, h;
1375
1376             ograv = client->gravity;
1377
1378             if (e->xclient.data.l[0] & 0xff)
1379                 client->gravity = e->xclient.data.l[0] & 0xff;
1380
1381             if (e->xclient.data.l[0] & 1 << 8)
1382                 x = e->xclient.data.l[1];
1383             else
1384                 x = client->area.x;
1385             if (e->xclient.data.l[0] & 1 << 9)
1386                 y = e->xclient.data.l[2];
1387             else
1388                 y = client->area.y;
1389
1390             if (e->xclient.data.l[0] & 1 << 10) {
1391                 w = e->xclient.data.l[3];
1392
1393                 /* if x was not given, then use gravity to figure out the new
1394                    x.  the reference point should not be moved */
1395                 if (!(e->xclient.data.l[0] & 1 << 8))
1396                     client_gravity_resize_w(client, &x, client->area.width, w);
1397             }
1398             else
1399                 w = client->area.width;
1400
1401             if (e->xclient.data.l[0] & 1 << 11) {
1402                 h = e->xclient.data.l[4];
1403
1404                 /* if y was not given, then use gravity to figure out the new
1405                    y.  the reference point should not be moved */
1406                 if (!(e->xclient.data.l[0] & 1 << 9))
1407                     client_gravity_resize_h(client, &y, client->area.height,h);
1408             }
1409             else
1410                 h = client->area.height;
1411
1412             ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)",
1413                      e->xclient.data.l[0] & 1 << 8, x,
1414                      e->xclient.data.l[0] & 1 << 9, y,
1415                      client->gravity);
1416
1417             client_find_onscreen(client, &x, &y, w, h, FALSE);
1418
1419             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1420
1421             client->gravity = ograv;
1422         } else if (msgtype == OBT_PROP_ATOM(NET_RESTACK_WINDOW)) {
1423             if (e->xclient.data.l[0] != 2) {
1424                 ob_debug_type(OB_DEBUG_APP_BUGS,
1425                               "_NET_RESTACK_WINDOW sent for window %s with "
1426                               "invalid source indication %ld",
1427                               client->title, e->xclient.data.l[0]);
1428             } else {
1429                 ObClient *sibling = NULL;
1430                 if (e->xclient.data.l[1]) {
1431                     ObWindow *win = window_find(e->xclient.data.l[1]);
1432                     if (WINDOW_IS_CLIENT(win) &&
1433                         WINDOW_AS_CLIENT(win) != client)
1434                     {
1435                         sibling = WINDOW_AS_CLIENT(win);
1436                     }
1437                     if (sibling == NULL)
1438                         ob_debug_type(OB_DEBUG_APP_BUGS,
1439                                       "_NET_RESTACK_WINDOW sent for window %s "
1440                                       "with invalid sibling 0x%x",
1441                                  client->title, e->xclient.data.l[1]);
1442                 }
1443                 if (e->xclient.data.l[2] == Below ||
1444                     e->xclient.data.l[2] == BottomIf ||
1445                     e->xclient.data.l[2] == Above ||
1446                     e->xclient.data.l[2] == TopIf ||
1447                     e->xclient.data.l[2] == Opposite)
1448                 {
1449                     gulong ignore_start;
1450
1451                     if (!config_focus_under_mouse)
1452                         ignore_start = event_start_ignore_all_enters();
1453                     /* just raise, don't activate */
1454                     stacking_restack_request(client, sibling,
1455                                              e->xclient.data.l[2]);
1456                     if (!config_focus_under_mouse)
1457                         event_end_ignore_all_enters(ignore_start);
1458
1459                     /* send a synthetic ConfigureNotify, cuz this is supposed
1460                        to be like a ConfigureRequest. */
1461                     client_reconfigure(client, TRUE);
1462                 } else
1463                     ob_debug_type(OB_DEBUG_APP_BUGS,
1464                                   "_NET_RESTACK_WINDOW sent for window %s "
1465                                   "with invalid detail %d",
1466                                   client->title, e->xclient.data.l[2]);
1467             }
1468         }
1469         break;
1470     case PropertyNotify:
1471         /* validate cuz we query stuff off the client here */
1472         if (!client_validate(client)) break;
1473
1474         /* compress changes to a single property into a single change */
1475         while (XCheckTypedWindowEvent(obt_display, client->w_client,
1476                                       e->type, &ce)) {
1477             Atom a, b;
1478
1479             /* XXX: it would be nice to compress ALL changes to a property,
1480                not just changes in a row without other props between. */
1481
1482             a = ce.xproperty.atom;
1483             b = e->xproperty.atom;
1484
1485             if (a == b)
1486                 continue;
1487             if ((a == OBT_PROP_ATOM(NET_WM_NAME) ||
1488                  a == OBT_PROP_ATOM(WM_NAME) ||
1489                  a == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1490                  a == OBT_PROP_ATOM(WM_ICON_NAME))
1491                 &&
1492                 (b == OBT_PROP_ATOM(NET_WM_NAME) ||
1493                  b == OBT_PROP_ATOM(WM_NAME) ||
1494                  b == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1495                  b == OBT_PROP_ATOM(WM_ICON_NAME))) {
1496                 continue;
1497             }
1498             if (a == OBT_PROP_ATOM(NET_WM_ICON) &&
1499                 b == OBT_PROP_ATOM(NET_WM_ICON))
1500                 continue;
1501
1502             XPutBackEvent(obt_display, &ce);
1503             break;
1504         }
1505
1506         msgtype = e->xproperty.atom;
1507         if (msgtype == XA_WM_NORMAL_HINTS) {
1508             ob_debug("Update NORMAL hints");
1509             client_update_normal_hints(client);
1510             /* normal hints can make a window non-resizable */
1511             client_setup_decor_and_functions(client, FALSE);
1512
1513             /* make sure the client's sizes are within its bounds, but only
1514                reconfigure the window if it needs to. emacs will update its
1515                normal hints every time it receives a conigurenotify */
1516             client_reconfigure(client, FALSE);
1517         } else if (msgtype == XA_WM_HINTS) {
1518             client_update_wmhints(client);
1519         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1520             client_update_transient_for(client);
1521             client_get_type_and_transientness(client);
1522             /* type may have changed, so update the layer */
1523             client_calc_layer(client);
1524             client_setup_decor_and_functions(client, TRUE);
1525         } else if (msgtype == OBT_PROP_ATOM(NET_WM_NAME) ||
1526                    msgtype == OBT_PROP_ATOM(WM_NAME) ||
1527                    msgtype == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
1528                    msgtype == OBT_PROP_ATOM(WM_ICON_NAME)) {
1529             client_update_title(client);
1530         } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
1531             client_update_protocols(client);
1532             client_setup_decor_and_functions(client, TRUE);
1533         }
1534         else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT)) {
1535             client_update_strut(client);
1536         }
1537         else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT_PARTIAL)) {
1538             client_update_strut(client);
1539         }
1540         else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON)) {
1541             client_update_icons(client);
1542         }
1543         else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON_GEOMETRY)) {
1544             client_update_icon_geometry(client);
1545         }
1546         else if (msgtype == OBT_PROP_ATOM(NET_WM_USER_TIME)) {
1547             guint32 t;
1548             if (client == focus_client &&
1549                 OBT_PROP_GET32(client->w_client, NET_WM_USER_TIME, CARDINAL, &t)
1550                 && t && !event_time_after(t, e->xproperty.time) &&
1551                 (!event_last_user_time ||
1552                  event_time_after(t, event_last_user_time)))
1553             {
1554                 event_last_user_time = t;
1555             }
1556         }
1557 #ifdef SYNC
1558         else if (msgtype == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) {
1559             client_update_sync_request_counter(client);
1560         }
1561 #endif
1562         break;
1563     case ColormapNotify:
1564         client_update_colormap(client, e->xcolormap.colormap);
1565         break;
1566     default:
1567         ;
1568 #ifdef SHAPE
1569         if (obt_display_extension_shape &&
1570             e->type == obt_display_extension_shape_basep)
1571         {
1572             client->shaped = ((XShapeEvent*)e)->shaped;
1573             render_plugin->frame_adjust_shape(client->frame);
1574         }
1575 #endif
1576     }
1577 }
1578
1579 static void event_handle_dock(ObDock *s, XEvent *e)
1580 {
1581     switch (e->type) {
1582     case ButtonPress:
1583         if (e->xbutton.button == 1)
1584             stacking_raise(DOCK_AS_WINDOW(s));
1585         else if (e->xbutton.button == 2)
1586             stacking_lower(DOCK_AS_WINDOW(s));
1587         break;
1588     case EnterNotify:
1589         dock_hide(FALSE);
1590         break;
1591     case LeaveNotify:
1592         /* don't hide when moving into a dock app */
1593         if (e->xcrossing.detail != NotifyInferior)
1594             dock_hide(TRUE);
1595         break;
1596     }
1597 }
1598
1599 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1600 {
1601     switch (e->type) {
1602     case MotionNotify:
1603         dock_app_drag(app, &e->xmotion);
1604         break;
1605     case UnmapNotify:
1606         if (app->ignore_unmaps) {
1607             app->ignore_unmaps--;
1608             break;
1609         }
1610         dock_unmanage(app, TRUE);
1611         break;
1612     case DestroyNotify:
1613         dock_unmanage(app, FALSE);
1614         break;
1615     case ReparentNotify:
1616         dock_unmanage(app, FALSE);
1617         break;
1618     case ConfigureNotify:
1619         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1620         break;
1621     }
1622 }
1623
1624 static ObMenuFrame* find_active_menu(void)
1625 {
1626     GList *it;
1627     ObMenuFrame *ret = NULL;
1628
1629     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1630         ret = it->data;
1631         if (ret->selected)
1632             break;
1633         ret = NULL;
1634     }
1635     return ret;
1636 }
1637
1638 static ObMenuFrame* find_active_or_last_menu(void)
1639 {
1640     ObMenuFrame *ret = NULL;
1641
1642     ret = find_active_menu();
1643     if (!ret && menu_frame_visible)
1644         ret = menu_frame_visible->data;
1645     return ret;
1646 }
1647
1648 static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
1649 {
1650     switch (e->type) {
1651     case ButtonPress:
1652     case ButtonRelease:
1653     case MotionNotify:
1654         return prompt_mouse_event(p, e);
1655         break;
1656     case KeyPress:
1657         return prompt_key_event(p, e);
1658         break;
1659     }
1660     return FALSE;
1661 }
1662
1663 static gboolean event_handle_menu_input(XEvent *ev)
1664 {
1665     gboolean ret = FALSE;
1666
1667     if (ev->type == ButtonRelease || ev->type == ButtonPress) {
1668         ObMenuEntryFrame *e;
1669
1670         if (menu_hide_delay_reached() &&
1671             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1672         {
1673             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1674                                             ev->xbutton.y_root)))
1675             {
1676                 if (ev->type == ButtonPress && e->frame->child)
1677                     menu_frame_select(e->frame->child, NULL, TRUE);
1678                 menu_frame_select(e->frame, e, TRUE);
1679                 if (ev->type == ButtonRelease)
1680                     menu_entry_frame_execute(e, ev->xbutton.state);
1681             }
1682             else if (ev->type == ButtonRelease)
1683                 menu_frame_hide_all();
1684         }
1685         ret = TRUE;
1686     }
1687     else if (ev->type == MotionNotify) {
1688         ObMenuFrame *f;
1689         ObMenuEntryFrame *e;
1690
1691         if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1692                                         ev->xmotion.y_root)))
1693             if (!(f = find_active_menu()) ||
1694                 f == e->frame ||
1695                 f->parent == e->frame ||
1696                 f->child == e->frame)
1697                 menu_frame_select(e->frame, e, FALSE);
1698     }
1699     else if (ev->type == KeyPress || ev->type == KeyRelease) {
1700         guint keycode, state;
1701         gunichar unikey;
1702         ObMenuFrame *frame;
1703
1704         keycode = ev->xkey.keycode;
1705         state = ev->xkey.state;
1706         unikey = obt_keyboard_keycode_to_unichar(keycode);
1707
1708         frame = find_active_or_last_menu();
1709         if (frame == NULL)
1710             g_assert_not_reached(); /* there is no active menu */
1711
1712         /* Allow control while going thru the menu */
1713         else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1714             frame->got_press = TRUE;
1715
1716             if (keycode == ob_keycode(OB_KEY_ESCAPE)) {
1717                 menu_frame_hide_all();
1718                 ret = TRUE;
1719             }
1720
1721             else if (keycode == ob_keycode(OB_KEY_LEFT)) {
1722                 /* Left goes to the parent menu */
1723                 menu_frame_select(frame, NULL, TRUE);
1724                 ret = TRUE;
1725             }
1726
1727             else if (keycode == ob_keycode(OB_KEY_RIGHT)) {
1728                 /* Right goes to the selected submenu */
1729                 if (frame->child) menu_frame_select_next(frame->child);
1730                 ret = TRUE;
1731             }
1732
1733             else if (keycode == ob_keycode(OB_KEY_UP)) {
1734                 menu_frame_select_previous(frame);
1735                 ret = TRUE;
1736             }
1737
1738             else if (keycode == ob_keycode(OB_KEY_DOWN)) {
1739                 menu_frame_select_next(frame);
1740                 ret = TRUE;
1741             }
1742         }
1743
1744         /* Use KeyRelease events for running things so that the key release
1745            doesn't get sent to the focused application.
1746
1747            Allow ControlMask only, and don't bother if the menu is empty */
1748         else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1749                  frame->entries && frame->got_press)
1750         {
1751             if (keycode == ob_keycode(OB_KEY_RETURN)) {
1752                 /* Enter runs the active item or goes into the submenu.
1753                    Control-Enter runs it without closing the menu. */
1754                 if (frame->child)
1755                     menu_frame_select_next(frame->child);
1756                 else if (frame->selected)
1757                     menu_entry_frame_execute(frame->selected, state);
1758
1759                 ret = TRUE;
1760             }
1761
1762             /* keyboard accelerator shortcuts. (if it was a valid key) */
1763             else if (unikey != 0) {
1764                 GList *start;
1765                 GList *it;
1766                 ObMenuEntryFrame *found = NULL;
1767                 guint num_found = 0;
1768
1769                 /* start after the selected one */
1770                 start = frame->entries;
1771                 if (frame->selected) {
1772                     for (it = start; frame->selected != it->data;
1773                          it = g_list_next(it))
1774                         g_assert(it != NULL); /* nothing was selected? */
1775                     /* next with wraparound */
1776                     start = g_list_next(it);
1777                     if (start == NULL) start = frame->entries;
1778                 }
1779
1780                 it = start;
1781                 do {
1782                     ObMenuEntryFrame *e = it->data;
1783                     gunichar entrykey = 0;
1784
1785                     if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1786                         entrykey = e->entry->data.normal.shortcut;
1787                     else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1788                         entrykey = e->entry->data.submenu.submenu->shortcut;
1789
1790                     if (unikey == entrykey) {
1791                         if (found == NULL) found = e;
1792                         ++num_found;
1793                     }
1794
1795                     /* next with wraparound */
1796                     it = g_list_next(it);
1797                     if (it == NULL) it = frame->entries;
1798                 } while (it != start);
1799
1800                 if (found) {
1801                     if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1802                         num_found == 1)
1803                     {
1804                         menu_frame_select(frame, found, TRUE);
1805                         usleep(50000); /* highlight the item for a short bit so
1806                                           the user can see what happened */
1807                         menu_entry_frame_execute(found, state);
1808                     } else {
1809                         menu_frame_select(frame, found, TRUE);
1810                         if (num_found == 1)
1811                             menu_frame_select_next(frame->child);
1812                     }
1813
1814                     ret = TRUE;
1815                 }
1816             }
1817         }
1818     }
1819
1820     return ret;
1821 }
1822
1823 static void event_handle_menu(ObMenuFrame *frame, XEvent *ev)
1824 {
1825     ObMenuFrame *f;
1826     ObMenuEntryFrame *e;
1827
1828     switch (ev->type) {
1829     case EnterNotify:
1830         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1831             if (e->ignore_enters)
1832                 --e->ignore_enters;
1833             else if (!(f = find_active_menu()) ||
1834                      f == e->frame ||
1835                      f->parent == e->frame ||
1836                      f->child == e->frame)
1837                 menu_frame_select(e->frame, e, FALSE);
1838         }
1839         break;
1840     case LeaveNotify:
1841         /*ignore leaves when we're already in the window */
1842         if (ev->xcrossing.detail == NotifyInferior)
1843             break;
1844
1845         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1846             (f = find_active_menu()) && f->selected == e &&
1847             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1848         {
1849             menu_frame_select(e->frame, NULL, FALSE);
1850         }
1851         break;
1852     }
1853 }
1854
1855 static void event_handle_user_input(ObClient *client, XEvent *e)
1856 {
1857     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1858              e->type == MotionNotify || e->type == KeyPress ||
1859              e->type == KeyRelease);
1860
1861     if (menu_frame_visible) {
1862         if (event_handle_menu_input(e))
1863             /* don't use the event if the menu used it, but if the menu
1864                didn't use it and it's a keypress that is bound, it will
1865                close the menu and be used */
1866             return;
1867     }
1868
1869     /* if the keyboard interactive action uses the event then dont
1870        use it for bindings. likewise is moveresize uses the event. */
1871     if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1872         if (render_plugin->moveresize_in_progress)
1873             /* make further actions work on the client being
1874                moved/resized */
1875             client = moveresize_client;
1876
1877         if (e->type == ButtonPress ||
1878             e->type == ButtonRelease ||
1879             e->type == MotionNotify)
1880         {
1881             /* the frame may not be "visible" but they can still click on it
1882                in the case where it is animating before disappearing */
1883             if (!client || !render_plugin->frame_iconify_animating(client->frame))
1884                 mouse_event(client, e);
1885         } else
1886             keyboard_event((render_plugin->focus_cycle_target ? render_plugin->focus_cycle_target :
1887                             (client ? client : focus_client)), e);
1888     }
1889 }
1890
1891 static void focus_delay_dest(gpointer data)
1892 {
1893     g_free(data);
1894 }
1895
1896 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1897 {
1898     const ObFocusDelayData *f1 = d1;
1899     return f1->client == d2;
1900 }
1901
1902 static gboolean focus_delay_func(gpointer data)
1903 {
1904     ObFocusDelayData *d = data;
1905     Time old = event_curtime;
1906
1907     /* don't move focus and kill the menu or the move/resize */
1908     if (menu_frame_visible || render_plugin->moveresize_in_progress) return FALSE;
1909
1910     event_curtime = d->time;
1911     event_curserial = d->serial;
1912     if (client_focus(d->client) && config_focus_raise)
1913         stacking_raise(CLIENT_AS_WINDOW(d->client));
1914     event_curtime = old;
1915     return FALSE; /* no repeat */
1916 }
1917
1918 static void focus_delay_client_dest(ObClient *client, gpointer data)
1919 {
1920     obt_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1921                                       client, FALSE);
1922 }
1923
1924 void event_halt_focus_delay(void)
1925 {
1926     /* ignore all enter events up till the event which caused this to occur */
1927     if (event_curserial) event_ignore_enter_range(1, event_curserial);
1928     obt_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1929 }
1930
1931 gulong event_start_ignore_all_enters(void)
1932 {
1933     return NextRequest(obt_display);
1934 }
1935
1936 static void event_ignore_enter_range(gulong start, gulong end)
1937 {
1938     ObSerialRange *r;
1939
1940     g_assert(start != 0);
1941     g_assert(end != 0);
1942
1943     r = g_new(ObSerialRange, 1);
1944     r->start = start;
1945     r->end = end;
1946     ignore_serials = g_slist_prepend(ignore_serials, r);
1947
1948     ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu",
1949                   r->start, r->end);
1950
1951     /* increment the serial so we don't ignore events we weren't meant to */
1952     OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
1953 }
1954
1955 void event_end_ignore_all_enters(gulong start)
1956 {
1957     /* Use (NextRequest-1) so that we ignore up to the current serial only.
1958        Inside event_ignore_enter_range, we increment the serial by one, but if
1959        we ignore that serial too, then any enter events generated by mouse
1960        movement will be ignored until we create some further network traffic.
1961        Instead ignore up to NextRequest-1, then when we increment the serial,
1962        we will be *past* the range of ignored serials */
1963     event_ignore_enter_range(start, NextRequest(obt_display)-1);
1964 }
1965
1966 static gboolean is_enter_focus_event_ignored(gulong serial)
1967 {
1968     GSList *it, *next;
1969
1970     for (it = ignore_serials; it; it = next) {
1971         ObSerialRange *r = it->data;
1972
1973         next = g_slist_next(it);
1974
1975         if ((glong)(serial - r->end) > 0) {
1976             /* past the end */
1977             ignore_serials = g_slist_delete_link(ignore_serials, it);
1978             g_free(r);
1979         }
1980         else if ((glong)(serial - r->start) >= 0)
1981             return TRUE;
1982     }
1983     return FALSE;
1984 }
1985
1986 void event_cancel_all_key_grabs(void)
1987 {
1988     if (actions_interactive_act_running()) {
1989         actions_interactive_cancel_act();
1990         ob_debug("KILLED interactive action");
1991     }
1992     else if (menu_frame_visible) {
1993         menu_frame_hide_all();
1994         ob_debug("KILLED open menus");
1995     }
1996     else if (render_plugin->moveresize_in_progress) {
1997         moveresize_end(TRUE);
1998         ob_debug("KILLED interactive moveresize");
1999     }
2000     else if (grab_on_keyboard()) {
2001         ungrab_keyboard();
2002         ob_debug("KILLED active grab on keyboard");
2003     }
2004     else
2005         ungrab_passive_key();
2006
2007     XSync(obt_display, FALSE);
2008 }
2009
2010 gboolean event_time_after(Time t1, Time t2)
2011 {
2012     g_assert(t1 != CurrentTime);
2013     g_assert(t2 != CurrentTime);
2014
2015     /*
2016       Timestamp values wrap around (after about 49.7 days). The server, given
2017       its current time is represented by timestamp T, always interprets
2018       timestamps from clients by treating half of the timestamp space as being
2019       later in time than T.
2020       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
2021     */
2022
2023     /* TIME_HALF is half of the number space of a Time type variable */
2024 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
2025
2026     if (t2 >= TIME_HALF)
2027         /* t2 is in the second half so t1 might wrap around and be smaller than
2028            t2 */
2029         return t1 >= t2 || t1 < (t2 + TIME_HALF);
2030     else
2031         /* t2 is in the first half so t1 has to come after it */
2032         return t1 >= t2 && t1 < (t2 + TIME_HALF);
2033 }
2034
2035 Time event_get_server_time(void)
2036 {
2037     /* Generate a timestamp */
2038     XEvent event;
2039
2040     XChangeProperty(obt_display, screen_support_win,
2041                     OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING),
2042                     8, PropModeAppend, NULL, 0);
2043     XWindowEvent(obt_display, screen_support_win, PropertyChangeMask, &event);
2044     return event.xproperty.time;
2045 }