try catch weird racey corner case where we try fallback to a window but it unmaps...
[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 static ObClient *focus_tried = NULL;
102
103 #ifdef USE_SM
104 static void ice_handler(gint fd, gpointer conn)
105 {
106     Bool b;
107     IceProcessMessages(conn, NULL, &b);
108 }
109
110 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
111                       IcePointer *watch_data)
112 {
113     static gint fd = -1;
114
115     if (opening) {
116         fd = IceConnectionNumber(conn);
117         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
118     } else {
119         ob_main_loop_fd_remove(ob_main_loop, fd);
120         fd = -1;
121     }
122 }
123 #endif
124
125 void event_startup(gboolean reconfig)
126 {
127     if (reconfig) return;
128
129     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
130
131 #ifdef USE_SM
132     IceAddConnectionWatch(ice_watch, NULL);
133 #endif
134
135     client_add_destructor(focus_delay_client_dest, NULL);
136 }
137
138 void event_shutdown(gboolean reconfig)
139 {
140     if (reconfig) return;
141
142 #ifdef USE_SM
143     IceRemoveConnectionWatch(ice_watch, NULL);
144 #endif
145
146     client_remove_destructor(focus_delay_client_dest);
147 }
148
149 static Window event_get_window(XEvent *e)
150 {
151     Window window;
152
153     /* pick a window */
154     switch (e->type) {
155     case SelectionClear:
156         window = RootWindow(ob_display, ob_screen);
157         break;
158     case MapRequest:
159         window = e->xmap.window;
160         break;
161     case UnmapNotify:
162         window = e->xunmap.window;
163         break;
164     case DestroyNotify:
165         window = e->xdestroywindow.window;
166         break;
167     case ConfigureRequest:
168         window = e->xconfigurerequest.window;
169         break;
170     case ConfigureNotify:
171         window = e->xconfigure.window;
172         break;
173     default:
174 #ifdef XKB
175         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
176             switch (((XkbAnyEvent*)e)->xkb_type) {
177             case XkbBellNotify:
178                 window = ((XkbBellNotifyEvent*)e)->window;
179             default:
180                 window = None;
181             }
182         } else
183 #endif
184 #ifdef SYNC
185         if (extensions_sync &&
186             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
187         {
188             window = None;
189         } else
190 #endif
191             window = e->xany.window;
192     }
193     return window;
194 }
195
196 static void event_set_curtime(XEvent *e)
197 {
198     Time t = CurrentTime;
199
200     /* grab the lasttime and hack up the state */
201     switch (e->type) {
202     case ButtonPress:
203     case ButtonRelease:
204         t = e->xbutton.time;
205         break;
206     case KeyPress:
207         t = e->xkey.time;
208         break;
209     case KeyRelease:
210         t = e->xkey.time;
211         break;
212     case MotionNotify:
213         t = e->xmotion.time;
214         break;
215     case PropertyNotify:
216         t = e->xproperty.time;
217         break;
218     case EnterNotify:
219     case LeaveNotify:
220         t = e->xcrossing.time;
221         break;
222     default:
223 #ifdef SYNC
224         if (extensions_sync &&
225             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
226         {
227             t = ((XSyncAlarmNotifyEvent*)e)->time;
228         }
229 #endif
230         /* if more event types are anticipated, get their timestamp
231            explicitly */
232         break;
233     }
234
235     event_curtime = t;
236 }
237
238 static void event_hack_mods(XEvent *e)
239 {
240 #ifdef XKB
241     XkbStateRec xkb_state;
242 #endif
243
244     switch (e->type) {
245     case ButtonPress:
246     case ButtonRelease:
247         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
248         break;
249     case KeyPress:
250         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
251         break;
252     case KeyRelease:
253         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
254 #ifdef XKB
255         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
256             e->xkey.state = xkb_state.compat_state;
257             break;
258         }
259 #endif
260         /* remove from the state the mask of the modifier key being released,
261            if it is a modifier key being released that is */
262         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
263         break;
264     case MotionNotify:
265         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
266         /* compress events */
267         {
268             XEvent ce;
269             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
270                                           e->type, &ce)) {
271                 e->xmotion.x_root = ce.xmotion.x_root;
272                 e->xmotion.y_root = ce.xmotion.y_root;
273             }
274         }
275         break;
276     }
277 }
278
279 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
280 {
281     gint mode = e->xfocus.mode;
282     gint detail = e->xfocus.detail;
283     Window win = e->xany.window;
284
285     if (e->type == FocusIn) {
286         /* These are ones we never want.. */
287
288         /* This means focus was given by a keyboard/mouse grab. */
289         if (mode == NotifyGrab)
290             return FALSE;
291         /* This means focus was given back from a keyboard/mouse grab. */
292         if (mode == NotifyUngrab)
293             return FALSE;
294
295         /* These are the ones we want.. */
296
297         if (win == RootWindow(ob_display, ob_screen) && !in_client_only) {
298             /* This means focus reverted off of a client */
299             if (detail == NotifyPointerRoot || detail == NotifyDetailNone ||
300                 detail == NotifyInferior)
301                 return TRUE;
302             else
303                 return FALSE;
304         }
305
306         /* This means focus moved from the root window to a client */
307         if (detail == NotifyVirtual)
308             return TRUE;
309         /* This means focus moved from one client to another */
310         if (detail == NotifyNonlinearVirtual)
311             return TRUE;
312         /* This means focus moved to the frame window */
313         if (detail == NotifyInferior && !in_client_only)
314             return TRUE;
315
316         /* Otherwise.. */
317         return FALSE;
318     } else {
319         g_assert(e->type == FocusOut);
320
321         /* These are ones we never want.. */
322
323         /* This means focus was taken by a keyboard/mouse grab. */
324         if (mode == NotifyGrab)
325             return FALSE;
326
327         /* Focus left the root window revertedto state */
328         if (win == RootWindow(ob_display, ob_screen))
329             return FALSE;
330
331         /* These are the ones we want.. */
332
333         /* This means focus moved from a client to the root window */
334         if (detail == NotifyVirtual)
335             return TRUE;
336         /* This means focus moved from one client to another */
337         if (detail == NotifyNonlinearVirtual)
338             return TRUE;
339         /* This means focus had moved to our frame window and now moved off */
340         if (detail == NotifyNonlinear)
341             return TRUE;
342
343         /* Otherwise.. */
344         return FALSE;
345     }
346 }
347
348 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
349 {
350     return e->type == FocusIn && wanted_focusevent(e, FALSE);
351 }
352
353 static Bool look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
354 {
355     return e->type == FocusIn && wanted_focusevent(e, TRUE);
356 }
357
358 static void print_focusevent(XEvent *e)
359 {
360     gint mode = e->xfocus.mode;
361     gint detail = e->xfocus.detail;
362     Window win = e->xany.window;
363     const gchar *modestr, *detailstr;
364
365     switch (mode) {
366     case NotifyNormal:       modestr="NotifyNormal";       break;
367     case NotifyGrab:         modestr="NotifyGrab";         break;
368     case NotifyUngrab:       modestr="NotifyUngrab";       break;
369     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
370     }
371     switch (detail) {
372     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
373     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
374     case NotifyInferior:    detailstr="NotifyInferior";    break;
375     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
376     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
377     case NotifyPointer:     detailstr="NotifyPointer";     break;
378     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
379     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
380     }
381
382     g_assert(modestr);
383     g_assert(detailstr);
384     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
385                   (e->xfocus.type == FocusIn ? "In" : "Out"),
386                   win,
387                   modestr, detailstr);
388
389 }
390
391 static gboolean event_ignore(XEvent *e, ObClient *client)
392 {
393     switch(e->type) {
394     case FocusIn:
395         print_focusevent(e);
396         if (!wanted_focusevent(e, FALSE))
397             return TRUE;
398         break;
399     case FocusOut:
400         print_focusevent(e);
401         if (!wanted_focusevent(e, FALSE))
402             return TRUE;
403         break;
404     }
405     return FALSE;
406 }
407
408 static void event_process(const XEvent *ec, gpointer data)
409 {
410     Window window;
411     ObClient *client = NULL;
412     ObDock *dock = NULL;
413     ObDockApp *dockapp = NULL;
414     ObWindow *obwin = NULL;
415     GSList *timewinclients = NULL;
416     XEvent ee, *e;
417     ObEventData *ed = data;
418
419     /* make a copy we can mangle */
420     ee = *ec;
421     e = &ee;
422
423     window = event_get_window(e);
424     if (e->type != PropertyNotify ||
425         !(timewinclients = propwin_get_clients(window,
426                                                OB_PROPWIN_USER_TIME)))
427         if ((obwin = g_hash_table_lookup(window_map, &window))) {
428             switch (obwin->type) {
429             case Window_Dock:
430                 dock = WINDOW_AS_DOCK(obwin);
431                 break;
432             case Window_DockApp:
433                 dockapp = WINDOW_AS_DOCKAPP(obwin);
434                 break;
435             case Window_Client:
436                 client = WINDOW_AS_CLIENT(obwin);
437                 break;
438             case Window_Menu:
439             case Window_Internal:
440                 /* not to be used for events */
441                 g_assert_not_reached();
442                 break;
443             }
444         }
445
446     event_set_curtime(e);
447     event_hack_mods(e);
448     if (event_ignore(e, client)) {
449         if (ed)
450             ed->ignored = TRUE;
451         return;
452     } else if (ed)
453             ed->ignored = FALSE;
454
455     /* deal with it in the kernel */
456
457     if (menu_frame_visible &&
458         (e->type == EnterNotify || e->type == LeaveNotify))
459     {
460         /* crossing events for menu */
461         event_handle_menu(e);
462     } else if (e->type == FocusIn) {
463         if (e->xfocus.detail == NotifyPointerRoot ||
464             e->xfocus.detail == NotifyDetailNone ||
465             e->xfocus.detail == NotifyInferior)
466         {
467             XEvent ce;
468             ob_debug_type(OB_DEBUG_FOCUS,
469                           "Focus went to pointer root/none or to our frame "
470                           "window\n");
471
472             /* If another FocusIn is in the queue then don't fallback yet. This
473                fixes the fun case of:
474                window map -> send focusin
475                window unmap -> get focusout
476                window map -> send focusin
477                get first focus out -> fall back to something (new window
478                  hasn't received focus yet, so something else) -> send focusin
479                which means the "something else" is the last thing to get a
480                focusin sent to it, so the new window doesn't end up with focus.
481
482                But if the other focus in is something like PointerRoot then we
483                still want to fall back.
484             */
485             if (XCheckIfEvent(ob_display, &ce, look_for_focusin_client, NULL)){
486                 XPutBackEvent(ob_display, &ce);
487                 ob_debug_type(OB_DEBUG_FOCUS,
488                               "  but another FocusIn is coming\n");
489             } else {
490                 /* Focus has been reverted to the root window, nothing, or to
491                    our frame window.
492
493                    FocusOut events come after UnmapNotify, so we don't need to
494                    worry about focusing an invalid window
495                 */
496
497                 /* In this case we know focus is in our screen */
498                 if (e->xfocus.detail == NotifyInferior)
499                     focus_left_screen = FALSE;
500
501                 if (!focus_left_screen)
502                     focus_tried = focus_fallback(TRUE);
503             }
504         } else if (client && client != focus_client) {
505             focus_left_screen = FALSE;
506             frame_adjust_focus(client->frame, TRUE);
507             focus_set_client(client);
508             client_calc_layer(client);
509             client_bring_helper_windows(client);
510
511             focus_tried = NULL; /* focus isn't "trying" to go anywhere now */
512         }
513     } else if (e->type == FocusOut) {
514         gboolean nomove = FALSE;
515         XEvent ce;
516
517         /* Look for the followup FocusIn */
518         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
519             /* There is no FocusIn, this means focus went to a window that
520                is not being managed, or a window on another screen. */
521             Window win, root;
522             gint i;
523             guint u;
524             xerror_set_ignore(TRUE);
525             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
526                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
527                 root != RootWindow(ob_display, ob_screen))
528             {
529                 ob_debug_type(OB_DEBUG_FOCUS,
530                               "Focus went to another screen !\n");
531                 focus_left_screen = TRUE;
532             }
533             else
534                 ob_debug_type(OB_DEBUG_FOCUS,
535                               "Focus went to a black hole !\n");
536             xerror_set_ignore(FALSE);
537             /* nothing is focused */
538             focus_set_client(NULL);
539         } else if (ce.xany.window == e->xany.window) {
540             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
541             /* If focus didn't actually move anywhere, there is nothing to do*/
542             nomove = TRUE;
543         } else {
544             /* Focus did move, so process the FocusIn event */
545             ObEventData ed = { .ignored = FALSE };
546             event_process(&ce, &ed);
547             if (ed.ignored) {
548                 /* The FocusIn was ignored, this means it was on a window
549                    that isn't a client. */
550                 ob_debug_type(OB_DEBUG_FOCUS,
551                               "Focus went to an unmanaged window 0x%x !\n",
552                               ce.xfocus.window);
553                 focus_tried = focus_fallback(TRUE);
554             }
555         }
556
557         if (client && !nomove) {
558             frame_adjust_focus(client->frame, FALSE);
559             /* focus_set_client has already been called for sure */
560             client_calc_layer(client);
561         }
562     } else if (timewinclients)
563         event_handle_user_time_window_clients(timewinclients, e);
564     else if (client)
565         event_handle_client(client, e);
566     else if (dockapp)
567         event_handle_dockapp(dockapp, e);
568     else if (dock)
569         event_handle_dock(dock, e);
570     else if (window == RootWindow(ob_display, ob_screen))
571         event_handle_root(e);
572     else if (e->type == MapRequest)
573         client_manage(window);
574     else if (e->type == ClientMessage) {
575         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
576            windows that are not managed yet. */
577         if (e->xclient.message_type == prop_atoms.net_request_frame_extents) {
578             /* Pretend to manage the client, getting information used to
579                determine its decorations */
580             ObClient *c = client_fake_manage(e->xclient.window);
581             gulong vals[4];
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 configure 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, NULL);
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     {
957         /* dont compress these unless you're going to watch for property
958            notifies in between (these can change what the configure would
959            do to the window).
960            also you can't compress stacking events
961         */
962
963         gint x, y, w, h;
964
965         /* if nothing is changed, then a configurenotify is needed */
966         gboolean config = TRUE;
967
968         x = client->area.x;
969         y = client->area.y;
970         w = client->area.width;
971         h = client->area.height;
972
973         ob_debug("ConfigureRequest desktop %d wmstate %d visibile %d\n",
974                  screen_desktop, client->wmstate, client->frame->visible);
975
976         if (e->xconfigurerequest.value_mask & CWBorderWidth)
977             if (client->border_width != e->xconfigurerequest.border_width) {
978                 client->border_width = e->xconfigurerequest.border_width;
979                 /* if only the border width is changing, then it's not needed*/
980                 config = FALSE;
981             }
982
983
984         if (e->xconfigurerequest.value_mask & CWStackMode) {
985             ObClient *sibling = NULL;
986
987             /* get the sibling */
988             if (e->xconfigurerequest.value_mask & CWSibling) {
989                 ObWindow *win;
990                 win = g_hash_table_lookup(window_map,
991                                           &e->xconfigurerequest.above);
992                 if (WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client)
993                     sibling = WINDOW_AS_CLIENT(win);
994             }
995
996             /* activate it rather than just focus it */
997             stacking_restack_request(client, sibling,
998                                      e->xconfigurerequest.detail, TRUE);
999
1000             /* if a stacking change is requested then it is needed */
1001             config = TRUE;
1002         }
1003
1004         /* don't allow clients to move shaded windows (fvwm does this) */
1005         if (client->shaded && (e->xconfigurerequest.value_mask & CWX ||
1006                                e->xconfigurerequest.value_mask & CWY))
1007         {
1008             e->xconfigurerequest.value_mask &= ~CWX;
1009             e->xconfigurerequest.value_mask &= ~CWY;
1010
1011             /* if the client tried to move and we aren't letting it then a
1012                synthetic event is needed */
1013             config = TRUE;
1014         }
1015
1016         if (e->xconfigurerequest.value_mask & CWX ||
1017             e->xconfigurerequest.value_mask & CWY ||
1018             e->xconfigurerequest.value_mask & CWWidth ||
1019             e->xconfigurerequest.value_mask & CWHeight)
1020         {
1021             if (e->xconfigurerequest.value_mask & CWX)
1022                 x = e->xconfigurerequest.x;
1023             if (e->xconfigurerequest.value_mask & CWY)
1024                 y = e->xconfigurerequest.y;
1025             if (e->xconfigurerequest.value_mask & CWWidth)
1026                 w = e->xconfigurerequest.width;
1027             if (e->xconfigurerequest.value_mask & CWHeight)
1028                 h = e->xconfigurerequest.height;
1029
1030             /* if a new position or size is requested, then a configure is
1031                needed */
1032             config = TRUE;
1033         }
1034
1035         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d\n",
1036                  e->xconfigurerequest.value_mask & CWX, x,
1037                  e->xconfigurerequest.value_mask & CWY, y,
1038                  e->xconfigurerequest.value_mask & CWWidth, w,
1039                  e->xconfigurerequest.value_mask & CWHeight, h);
1040
1041         /* check for broken apps moving to their root position
1042
1043            XXX remove this some day...that would be nice. right now all
1044            kde apps do this when they try activate themselves on another
1045            desktop. eg. open amarok window on desktop 1, switch to desktop
1046            2, click amarok tray icon. it will move by its decoration size.
1047         */
1048         if (x != client->area.x &&
1049             x == (client->frame->area.x + client->frame->size.left -
1050                   (gint)client->border_width) &&
1051             y != client->area.y &&
1052             y == (client->frame->area.y + client->frame->size.top -
1053                   (gint)client->border_width))
1054         {
1055             ob_debug_type(OB_DEBUG_APP_BUGS,
1056                           "Application %s is trying to move via "
1057                           "ConfigureRequest to it's root window position "
1058                           "but it is not using StaticGravity\n",
1059                           client->title);
1060             /* don't move it */
1061             x = client->area.x;
1062             y = client->area.y;
1063         }
1064
1065         if (config) {
1066             client_find_onscreen(client, &x, &y, w, h, FALSE);
1067             client_configure_full(client, x, y, w, h, FALSE, TRUE);
1068         }
1069         break;
1070     }
1071     case UnmapNotify:
1072         if (client->ignore_unmaps) {
1073             client->ignore_unmaps--;
1074             break;
1075         }
1076         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1077                  "ignores left %d\n",
1078                  client->window, e->xunmap.event, e->xunmap.from_configure,
1079                  client->ignore_unmaps);
1080         client_unmanage(client);
1081
1082         /* we were trying to focus this window but it's gone */
1083         if (client == focus_tried) {
1084             ob_debug_type(OB_DEBUG_FOCUS, "Tried to focus window 0x%x but it "
1085                           "is being unmanaged. Falling back focus again.\n");
1086             focus_tried = focus_fallback(TRUE);
1087         }
1088         break;
1089     case DestroyNotify:
1090         ob_debug("DestroyNotify for window 0x%x\n", client->window);
1091         client_unmanage(client);
1092
1093         /* we were trying to focus this window but it's gone */
1094         if (client == focus_tried) {
1095             ob_debug_type(OB_DEBUG_FOCUS, "Tried to focus window 0x%x but it "
1096                           "is being unmanaged. Falling back focus again.\n");
1097             focus_tried = focus_fallback(TRUE);
1098         }
1099         break;
1100     case ReparentNotify:
1101         /* this is when the client is first taken captive in the frame */
1102         if (e->xreparent.parent == client->frame->plate) break;
1103
1104         /*
1105           This event is quite rare and is usually handled in unmapHandler.
1106           However, if the window is unmapped when the reparent event occurs,
1107           the window manager never sees it because an unmap event is not sent
1108           to an already unmapped window.
1109         */
1110
1111         /* we don't want the reparent event, put it back on the stack for the
1112            X server to deal with after we unmanage the window */
1113         XPutBackEvent(ob_display, e);
1114      
1115         ob_debug("ReparentNotify for window 0x%x\n", client->window);
1116         client_unmanage(client);
1117
1118         /* we were trying to focus this window but it's gone */
1119         if (client == focus_tried) {
1120             ob_debug_type(OB_DEBUG_FOCUS, "Tried to focus window 0x%x but it "
1121                           "is being unmanaged. Falling back focus again.\n");
1122             focus_tried = focus_fallback(TRUE);
1123         }
1124         break;
1125     case MapRequest:
1126         ob_debug("MapRequest for 0x%lx\n", client->window);
1127         if (!client->iconic) break; /* this normally doesn't happen, but if it
1128                                        does, we don't want it!
1129                                        it can happen now when the window is on
1130                                        another desktop, but we still don't
1131                                        want it! */
1132         client_activate(client, FALSE, TRUE);
1133         break;
1134     case ClientMessage:
1135         /* validate cuz we query stuff off the client here */
1136         if (!client_validate(client)) break;
1137
1138         if (e->xclient.format != 32) return;
1139
1140         msgtype = e->xclient.message_type;
1141         if (msgtype == prop_atoms.wm_change_state) {
1142             /* compress changes into a single change */
1143             while (XCheckTypedWindowEvent(ob_display, client->window,
1144                                           e->type, &ce)) {
1145                 /* XXX: it would be nice to compress ALL messages of a
1146                    type, not just messages in a row without other
1147                    message types between. */
1148                 if (ce.xclient.message_type != msgtype) {
1149                     XPutBackEvent(ob_display, &ce);
1150                     break;
1151                 }
1152                 e->xclient = ce.xclient;
1153             }
1154             client_set_wm_state(client, e->xclient.data.l[0]);
1155         } else if (msgtype == prop_atoms.net_wm_desktop) {
1156             /* compress changes into a single change */
1157             while (XCheckTypedWindowEvent(ob_display, client->window,
1158                                           e->type, &ce)) {
1159                 /* XXX: it would be nice to compress ALL messages of a
1160                    type, not just messages in a row without other
1161                    message types between. */
1162                 if (ce.xclient.message_type != msgtype) {
1163                     XPutBackEvent(ob_display, &ce);
1164                     break;
1165                 }
1166                 e->xclient = ce.xclient;
1167             }
1168             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1169                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1170                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1171                                    FALSE);
1172         } else if (msgtype == prop_atoms.net_wm_state) {
1173             /* can't compress these */
1174             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1175                      (e->xclient.data.l[0] == 0 ? "Remove" :
1176                       e->xclient.data.l[0] == 1 ? "Add" :
1177                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1178                      e->xclient.data.l[1], e->xclient.data.l[2],
1179                      client->window);
1180             client_set_state(client, e->xclient.data.l[0],
1181                              e->xclient.data.l[1], e->xclient.data.l[2]);
1182         } else if (msgtype == prop_atoms.net_close_window) {
1183             ob_debug("net_close_window for 0x%lx\n", client->window);
1184             client_close(client);
1185         } else if (msgtype == prop_atoms.net_active_window) {
1186             ob_debug("net_active_window for 0x%lx source=%s\n",
1187                      client->window,
1188                      (e->xclient.data.l[0] == 0 ? "unknown" :
1189                       (e->xclient.data.l[0] == 1 ? "application" :
1190                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1191             /* XXX make use of data.l[2] !? */
1192             event_curtime = e->xclient.data.l[1];
1193             if (event_curtime == 0)
1194                 ob_debug_type(OB_DEBUG_APP_BUGS,
1195                               "_NET_ACTIVE_WINDOW message for window %s is "
1196                               "missing a timestamp\n", client->title);
1197             client_activate(client, FALSE,
1198                             (e->xclient.data.l[0] == 0 ||
1199                              e->xclient.data.l[0] == 2));
1200         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1201             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1202                      client->window, e->xclient.data.l[2]);
1203             if ((Atom)e->xclient.data.l[2] ==
1204                 prop_atoms.net_wm_moveresize_size_topleft ||
1205                 (Atom)e->xclient.data.l[2] ==
1206                 prop_atoms.net_wm_moveresize_size_top ||
1207                 (Atom)e->xclient.data.l[2] ==
1208                 prop_atoms.net_wm_moveresize_size_topright ||
1209                 (Atom)e->xclient.data.l[2] ==
1210                 prop_atoms.net_wm_moveresize_size_right ||
1211                 (Atom)e->xclient.data.l[2] ==
1212                 prop_atoms.net_wm_moveresize_size_right ||
1213                 (Atom)e->xclient.data.l[2] ==
1214                 prop_atoms.net_wm_moveresize_size_bottomright ||
1215                 (Atom)e->xclient.data.l[2] ==
1216                 prop_atoms.net_wm_moveresize_size_bottom ||
1217                 (Atom)e->xclient.data.l[2] ==
1218                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1219                 (Atom)e->xclient.data.l[2] ==
1220                 prop_atoms.net_wm_moveresize_size_left ||
1221                 (Atom)e->xclient.data.l[2] ==
1222                 prop_atoms.net_wm_moveresize_move ||
1223                 (Atom)e->xclient.data.l[2] ==
1224                 prop_atoms.net_wm_moveresize_size_keyboard ||
1225                 (Atom)e->xclient.data.l[2] ==
1226                 prop_atoms.net_wm_moveresize_move_keyboard) {
1227
1228                 moveresize_start(client, e->xclient.data.l[0],
1229                                  e->xclient.data.l[1], e->xclient.data.l[3],
1230                                  e->xclient.data.l[2]);
1231             }
1232             else if ((Atom)e->xclient.data.l[2] ==
1233                      prop_atoms.net_wm_moveresize_cancel)
1234                 moveresize_end(TRUE);
1235         } else if (msgtype == prop_atoms.net_moveresize_window) {
1236             gint grav, x, y, w, h;
1237
1238             if (e->xclient.data.l[0] & 0xff)
1239                 grav = e->xclient.data.l[0] & 0xff;
1240             else 
1241                 grav = client->gravity;
1242
1243             if (e->xclient.data.l[0] & 1 << 8)
1244                 x = e->xclient.data.l[1];
1245             else
1246                 x = client->area.x;
1247             if (e->xclient.data.l[0] & 1 << 9)
1248                 y = e->xclient.data.l[2];
1249             else
1250                 y = client->area.y;
1251             if (e->xclient.data.l[0] & 1 << 10)
1252                 w = e->xclient.data.l[3];
1253             else
1254                 w = client->area.width;
1255             if (e->xclient.data.l[0] & 1 << 11)
1256                 h = e->xclient.data.l[4];
1257             else
1258                 h = client->area.height;
1259
1260             ob_debug("MOVERESIZE x %d %d y %d %d\n",
1261                      e->xclient.data.l[0] & 1 << 8, x,
1262                      e->xclient.data.l[0] & 1 << 9, y);
1263             client_convert_gravity(client, grav, &x, &y, w, h);
1264             client_find_onscreen(client, &x, &y, w, h, FALSE);
1265             client_configure(client, x, y, w, h, FALSE, TRUE);
1266         } else if (msgtype == prop_atoms.net_restack_window) {
1267             if (e->xclient.data.l[0] != 2) {
1268                 ob_debug_type(OB_DEBUG_APP_BUGS,
1269                               "_NET_RESTACK_WINDOW sent for window %s with "
1270                               "invalid source indication %ld\n",
1271                               client->title, e->xclient.data.l[0]);
1272             } else {
1273                 ObClient *sibling = NULL;
1274                 if (e->xclient.data.l[1]) {
1275                     ObWindow *win = g_hash_table_lookup(window_map,
1276                                                         &e->xclient.data.l[1]);
1277                     if (WINDOW_IS_CLIENT(win) &&
1278                         WINDOW_AS_CLIENT(win) != client)
1279                     {
1280                         sibling = WINDOW_AS_CLIENT(win);
1281                     }
1282                     if (sibling == NULL)
1283                         ob_debug_type(OB_DEBUG_APP_BUGS,
1284                                       "_NET_RESTACK_WINDOW sent for window %s "
1285                                       "with invalid sibling 0x%x\n",
1286                                  client->title, e->xclient.data.l[1]);
1287                 }
1288                 if (e->xclient.data.l[2] == Below ||
1289                     e->xclient.data.l[2] == BottomIf ||
1290                     e->xclient.data.l[2] == Above ||
1291                     e->xclient.data.l[2] == TopIf ||
1292                     e->xclient.data.l[2] == Opposite)
1293                 {
1294                     /* just raise, don't activate */
1295                     stacking_restack_request(client, sibling,
1296                                              e->xclient.data.l[2], FALSE);
1297                     /* send a synthetic ConfigureNotify, cuz this is supposed
1298                        to be like a ConfigureRequest. */
1299                     client_configure_full(client, client->area.x,
1300                                           client->area.y,
1301                                           client->area.width,
1302                                           client->area.height,
1303                                           FALSE, TRUE);
1304                 } else
1305                     ob_debug_type(OB_DEBUG_APP_BUGS,
1306                                   "_NET_RESTACK_WINDOW sent for window %s "
1307                                   "with invalid detail %d\n",
1308                                   client->title, e->xclient.data.l[2]);
1309             }
1310         }
1311         break;
1312     case PropertyNotify:
1313         /* validate cuz we query stuff off the client here */
1314         if (!client_validate(client)) break;
1315   
1316         /* compress changes to a single property into a single change */
1317         while (XCheckTypedWindowEvent(ob_display, client->window,
1318                                       e->type, &ce)) {
1319             Atom a, b;
1320
1321             /* XXX: it would be nice to compress ALL changes to a property,
1322                not just changes in a row without other props between. */
1323
1324             a = ce.xproperty.atom;
1325             b = e->xproperty.atom;
1326
1327             if (a == b)
1328                 continue;
1329             if ((a == prop_atoms.net_wm_name ||
1330                  a == prop_atoms.wm_name ||
1331                  a == prop_atoms.net_wm_icon_name ||
1332                  a == prop_atoms.wm_icon_name)
1333                 &&
1334                 (b == prop_atoms.net_wm_name ||
1335                  b == prop_atoms.wm_name ||
1336                  b == prop_atoms.net_wm_icon_name ||
1337                  b == prop_atoms.wm_icon_name)) {
1338                 continue;
1339             }
1340             if (a == prop_atoms.net_wm_icon &&
1341                 b == prop_atoms.net_wm_icon)
1342                 continue;
1343
1344             XPutBackEvent(ob_display, &ce);
1345             break;
1346         }
1347
1348         msgtype = e->xproperty.atom;
1349         if (msgtype == XA_WM_NORMAL_HINTS) {
1350             client_update_normal_hints(client);
1351             /* normal hints can make a window non-resizable */
1352             client_setup_decor_and_functions(client);
1353         } else if (msgtype == XA_WM_HINTS) {
1354             client_update_wmhints(client);
1355         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1356             client_update_transient_for(client);
1357             client_get_type_and_transientness(client);
1358             /* type may have changed, so update the layer */
1359             client_calc_layer(client);
1360             client_setup_decor_and_functions(client);
1361         } else if (msgtype == prop_atoms.net_wm_name ||
1362                    msgtype == prop_atoms.wm_name ||
1363                    msgtype == prop_atoms.net_wm_icon_name ||
1364                    msgtype == prop_atoms.wm_icon_name) {
1365             client_update_title(client);
1366         } else if (msgtype == prop_atoms.wm_protocols) {
1367             client_update_protocols(client);
1368             client_setup_decor_and_functions(client);
1369         }
1370         else if (msgtype == prop_atoms.net_wm_strut) {
1371             client_update_strut(client);
1372         }
1373         else if (msgtype == prop_atoms.net_wm_icon) {
1374             client_update_icons(client);
1375         }
1376         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1377             client_update_icon_geometry(client);
1378         }
1379         else if (msgtype == prop_atoms.net_wm_user_time) {
1380             client_update_user_time(client);
1381         }
1382         else if (msgtype == prop_atoms.net_wm_user_time_window) {
1383             client_update_user_time_window(client);
1384         }
1385 #ifdef SYNC
1386         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1387             client_update_sync_request_counter(client);
1388         }
1389 #endif
1390     case ColormapNotify:
1391         client_update_colormap(client, e->xcolormap.colormap);
1392         break;
1393     default:
1394         ;
1395 #ifdef SHAPE
1396         if (extensions_shape && e->type == extensions_shape_event_basep) {
1397             client->shaped = ((XShapeEvent*)e)->shaped;
1398             frame_adjust_shape(client->frame);
1399         }
1400 #endif
1401     }
1402 }
1403
1404 static void event_handle_dock(ObDock *s, XEvent *e)
1405 {
1406     switch (e->type) {
1407     case ButtonPress:
1408         if (e->xbutton.button == 1)
1409             stacking_raise(DOCK_AS_WINDOW(s));
1410         else if (e->xbutton.button == 2)
1411             stacking_lower(DOCK_AS_WINDOW(s));
1412         break;
1413     case EnterNotify:
1414         dock_hide(FALSE);
1415         break;
1416     case LeaveNotify:
1417         dock_hide(TRUE);
1418         break;
1419     }
1420 }
1421
1422 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1423 {
1424     switch (e->type) {
1425     case MotionNotify:
1426         dock_app_drag(app, &e->xmotion);
1427         break;
1428     case UnmapNotify:
1429         if (app->ignore_unmaps) {
1430             app->ignore_unmaps--;
1431             break;
1432         }
1433         dock_remove(app, TRUE);
1434         break;
1435     case DestroyNotify:
1436         dock_remove(app, FALSE);
1437         break;
1438     case ReparentNotify:
1439         dock_remove(app, FALSE);
1440         break;
1441     case ConfigureNotify:
1442         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1443         break;
1444     }
1445 }
1446
1447 static ObMenuFrame* find_active_menu()
1448 {
1449     GList *it;
1450     ObMenuFrame *ret = NULL;
1451
1452     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1453         ret = it->data;
1454         if (ret->selected)
1455             break;
1456         ret = NULL;
1457     }
1458     return ret;
1459 }
1460
1461 static ObMenuFrame* find_active_or_last_menu()
1462 {
1463     ObMenuFrame *ret = NULL;
1464
1465     ret = find_active_menu();
1466     if (!ret && menu_frame_visible)
1467         ret = menu_frame_visible->data;
1468     return ret;
1469 }
1470
1471 static gboolean event_handle_menu_keyboard(XEvent *ev)
1472 {
1473     guint keycode, state;
1474     gunichar unikey;
1475     ObMenuFrame *frame;
1476     gboolean ret = TRUE;
1477
1478     keycode = ev->xkey.keycode;
1479     state = ev->xkey.state;
1480     unikey = translate_unichar(keycode);
1481
1482     frame = find_active_or_last_menu();
1483     if (frame == NULL)
1484         ret = FALSE;
1485
1486     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1487         /* Escape closes the active menu */
1488         menu_frame_hide(frame);
1489     }
1490
1491     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1492                                                       state == ControlMask))
1493     {
1494         /* Enter runs the active item or goes into the submenu.
1495            Control-Enter runs it without closing the menu. */
1496         if (frame->child)
1497             menu_frame_select_next(frame->child);
1498         else
1499             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1500     }
1501
1502     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1503         /* Left goes to the parent menu */
1504         menu_frame_select(frame, NULL, TRUE);
1505     }
1506
1507     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1508         /* Right goes to the selected submenu */
1509         if (frame->child) menu_frame_select_next(frame->child);
1510     }
1511
1512     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1513         menu_frame_select_previous(frame);
1514     }
1515
1516     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1517         menu_frame_select_next(frame);
1518     }
1519
1520     /* keyboard accelerator shortcuts. */
1521     else if (ev->xkey.state == 0 &&
1522              /* was it a valid key? */
1523              unikey != 0 &&
1524              /* don't bother if the menu is empty. */
1525              frame->entries)
1526     {
1527         GList *start;
1528         GList *it;
1529         ObMenuEntryFrame *found = NULL;
1530         guint num_found = 0;
1531
1532         /* start after the selected one */
1533         start = frame->entries;
1534         if (frame->selected) {
1535             for (it = start; frame->selected != it->data; it = g_list_next(it))
1536                 g_assert(it != NULL); /* nothing was selected? */
1537             /* next with wraparound */
1538             start = g_list_next(it);
1539             if (start == NULL) start = frame->entries;
1540         }
1541
1542         it = start;
1543         do {
1544             ObMenuEntryFrame *e = it->data;
1545             gunichar entrykey = 0;
1546
1547             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1548                 entrykey = e->entry->data.normal.shortcut;
1549             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1550                 entrykey = e->entry->data.submenu.submenu->shortcut;
1551
1552             if (unikey == entrykey) {
1553                 if (found == NULL) found = e;
1554                 ++num_found;
1555             }
1556
1557             /* next with wraparound */
1558             it = g_list_next(it);
1559             if (it == NULL) it = frame->entries;
1560         } while (it != start);
1561
1562         if (found) {
1563             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1564                 num_found == 1)
1565             {
1566                 menu_frame_select(frame, found, TRUE);
1567                 usleep(50000); /* highlight the item for a short bit so the
1568                                   user can see what happened */
1569                 menu_entry_frame_execute(found, state, ev->xkey.time);
1570             } else {
1571                 menu_frame_select(frame, found, TRUE);
1572                 if (num_found == 1)
1573                     menu_frame_select_next(frame->child);
1574             }
1575         } else
1576             ret = FALSE;
1577     }
1578     else
1579         ret = FALSE;
1580
1581     return ret;
1582 }
1583
1584 static gboolean event_handle_menu(XEvent *ev)
1585 {
1586     ObMenuFrame *f;
1587     ObMenuEntryFrame *e;
1588     gboolean ret = TRUE;
1589
1590     switch (ev->type) {
1591     case ButtonRelease:
1592         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1593             && menu_can_hide)
1594         {
1595             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1596                                             ev->xbutton.y_root)))
1597                 menu_entry_frame_execute(e, ev->xbutton.state,
1598                                          ev->xbutton.time);
1599             else
1600                 menu_frame_hide_all();
1601         }
1602         break;
1603     case EnterNotify:
1604         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1605             if (e->ignore_enters)
1606                 --e->ignore_enters;
1607             else
1608                 menu_frame_select(e->frame, e, FALSE);
1609         }
1610         break;
1611     case LeaveNotify:
1612         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1613             (f = find_active_menu()) && f->selected == e &&
1614             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1615         {
1616             menu_frame_select(e->frame, NULL, FALSE);
1617         }
1618     case MotionNotify:   
1619         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1620                                         ev->xmotion.y_root)))
1621             menu_frame_select(e->frame, e, FALSE);
1622         break;
1623     case KeyPress:
1624         ret = event_handle_menu_keyboard(ev);
1625         break;
1626     }
1627     return ret;
1628 }
1629
1630 static void event_handle_user_input(ObClient *client, XEvent *e)
1631 {
1632     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1633              e->type == MotionNotify || e->type == KeyPress ||
1634              e->type == KeyRelease);
1635
1636     if (menu_frame_visible) {
1637         if (event_handle_menu(e))
1638             /* don't use the event if the menu used it, but if the menu
1639                didn't use it and it's a keypress that is bound, it will
1640                close the menu and be used */
1641             return;
1642     }
1643
1644     /* if the keyboard interactive action uses the event then dont
1645        use it for bindings. likewise is moveresize uses the event. */
1646     if (!keyboard_process_interactive_grab(e, &client) &&
1647         !(moveresize_in_progress && moveresize_event(e)))
1648     {
1649         if (moveresize_in_progress)
1650             /* make further actions work on the client being
1651                moved/resized */
1652             client = moveresize_client;
1653
1654         menu_can_hide = FALSE;
1655         ob_main_loop_timeout_add(ob_main_loop,
1656                                  config_menu_hide_delay * 1000,
1657                                  menu_hide_delay_func,
1658                                  NULL, g_direct_equal, NULL);
1659
1660         if (e->type == ButtonPress ||
1661             e->type == ButtonRelease ||
1662             e->type == MotionNotify)
1663         {
1664             /* the frame may not be "visible" but they can still click on it
1665                in the case where it is animating before disappearing */
1666             if (!client || !frame_iconify_animating(client->frame))
1667                 mouse_event(client, e);
1668         } else if (e->type == KeyPress) {
1669             keyboard_event((focus_cycle_target ? focus_cycle_target :
1670                             (client ? client : focus_client)), e);
1671         }
1672     }
1673 }
1674
1675 static gboolean menu_hide_delay_func(gpointer data)
1676 {
1677     menu_can_hide = TRUE;
1678     return FALSE; /* no repeat */
1679 }
1680
1681 static void focus_delay_dest(gpointer data)
1682 {
1683     g_free(data);
1684 }
1685
1686 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1687 {
1688     const ObFocusDelayData *f1 = d1;
1689     return f1->client == d2;
1690 }
1691
1692 static gboolean focus_delay_func(gpointer data)
1693 {
1694     ObFocusDelayData *d = data;
1695     Time old = event_curtime;
1696
1697     event_curtime = d->time;
1698     if (focus_client != d->client) {
1699         if (client_focus(d->client) && config_focus_raise)
1700             stacking_raise(CLIENT_AS_WINDOW(d->client));
1701     }
1702     event_curtime = old;
1703     return FALSE; /* no repeat */
1704 }
1705
1706 static void focus_delay_client_dest(ObClient *client, gpointer data)
1707 {
1708     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1709                                      client, FALSE);
1710 }
1711
1712 void event_halt_focus_delay()
1713 {
1714     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1715 }
1716
1717 void event_ignore_queued_enters()
1718 {
1719     GSList *saved = NULL, *it;
1720     XEvent *e;
1721                 
1722     XSync(ob_display, FALSE);
1723
1724     /* count the events */
1725     while (TRUE) {
1726         e = g_new(XEvent, 1);
1727         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1728             ObWindow *win;
1729             
1730             win = g_hash_table_lookup(window_map, &e->xany.window);
1731             if (win && WINDOW_IS_CLIENT(win))
1732                 ++ignore_enter_focus;
1733             
1734             saved = g_slist_append(saved, e);
1735         } else {
1736             g_free(e);
1737             break;
1738         }
1739     }
1740     /* put the events back */
1741     for (it = saved; it; it = g_slist_next(it)) {
1742         XPutBackEvent(ob_display, it->data);
1743         g_free(it->data);
1744     }
1745     g_slist_free(saved);
1746 }
1747
1748 gboolean event_time_after(Time t1, Time t2)
1749 {
1750     g_assert(t1 != CurrentTime);
1751     g_assert(t2 != CurrentTime);
1752
1753     /*
1754       Timestamp values wrap around (after about 49.7 days). The server, given
1755       its current time is represented by timestamp T, always interprets
1756       timestamps from clients by treating half of the timestamp space as being
1757       later in time than T.
1758       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1759     */
1760
1761     /* TIME_HALF is half of the number space of a Time type variable */
1762 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1763
1764     if (t2 >= TIME_HALF)
1765         /* t2 is in the second half so t1 might wrap around and be smaller than
1766            t2 */
1767         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1768     else
1769         /* t2 is in the first half so t1 has to come after it */
1770         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1771 }