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