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