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