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