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