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