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