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