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