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