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