fix race condition in focus delay
[dana/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) 2003        Ben Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "event.h"
20 #include "debug.h"
21 #include "window.h"
22 #include "openbox.h"
23 #include "dock.h"
24 #include "client.h"
25 #include "xerror.h"
26 #include "prop.h"
27 #include "config.h"
28 #include "screen.h"
29 #include "frame.h"
30 #include "menu.h"
31 #include "menuframe.h"
32 #include "keyboard.h"
33 #include "mouse.h"
34 #include "mainloop.h"
35 #include "framerender.h"
36 #include "focus.h"
37 #include "moveresize.h"
38 #include "group.h"
39 #include "stacking.h"
40 #include "extensions.h"
41
42 #include <X11/Xlib.h>
43 #include <X11/keysym.h>
44 #include <X11/Xatom.h>
45 #include <glib.h>
46
47 #ifdef HAVE_SYS_SELECT_H
48 #  include <sys/select.h>
49 #endif
50 #ifdef HAVE_SIGNAL_H
51 #  include <signal.h>
52 #endif
53
54 #ifdef USE_SM
55 #include <X11/ICE/ICElib.h>
56 #endif
57
58 typedef struct
59 {
60     gboolean ignored;
61 } ObEventData;
62
63 static void event_process(const XEvent *e, gpointer data);
64 static void event_handle_root(XEvent *e);
65 static void event_handle_menu(XEvent *e);
66 static void event_handle_dock(ObDock *s, XEvent *e);
67 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
68 static void event_handle_client(ObClient *c, XEvent *e);
69 static void event_handle_group(ObGroup *g, XEvent *e);
70
71 static gboolean focus_delay_func(gpointer data);
72 static void focus_delay_client_dest(gpointer data);
73
74 static gboolean menu_hide_delay_func(gpointer data);
75
76 #define INVALID_FOCUSIN(e) ((e)->xfocus.detail == NotifyInferior || \
77                             (e)->xfocus.detail == NotifyAncestor || \
78                             (e)->xfocus.detail > NotifyNonlinearVirtual)
79 #define INVALID_FOCUSOUT(e) ((e)->xfocus.mode == NotifyGrab || \
80                              (e)->xfocus.detail == NotifyInferior || \
81                              (e)->xfocus.detail == NotifyAncestor || \
82                              (e)->xfocus.detail > NotifyNonlinearVirtual)
83
84 Time event_lasttime = 0;
85
86 /*! The value of the mask for the NumLock modifier */
87 unsigned int NumLockMask;
88 /*! The value of the mask for the ScrollLock modifier */
89 unsigned int ScrollLockMask;
90 /*! The key codes for the modifier keys */
91 static XModifierKeymap *modmap;
92 /*! Table of the constant modifier masks */
93 static const int mask_table[] = {
94     ShiftMask, LockMask, ControlMask, Mod1Mask,
95     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
96 };
97 static int mask_table_size;
98
99 static guint ignore_enter_focus = 0;
100 static ObClient *focus_delay_client;
101
102 static gboolean menu_can_hide;
103
104 #ifdef USE_SM
105 static void ice_handler(int fd, gpointer conn)
106 {
107     Bool b;
108     IceProcessMessages(conn, NULL, &b);
109 }
110
111 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
112                       IcePointer *watch_data)
113 {
114     static gint fd = -1;
115
116     if (opening) {
117         fd = IceConnectionNumber(conn);
118         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
119     } else {
120         ob_main_loop_fd_remove(ob_main_loop, fd);
121         fd = -1;
122     }
123 }
124 #endif
125
126 void event_startup(gboolean reconfig)
127 {
128     if (reconfig) return;
129
130     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
131      
132     /* get lock masks that are defined by the display (not constant) */
133     modmap = XGetModifierMapping(ob_display);
134     g_assert(modmap);
135     if (modmap && modmap->max_keypermod > 0) {
136         size_t cnt;
137         const size_t size = mask_table_size * modmap->max_keypermod;
138         /* get the values of the keyboard lock modifiers
139            Note: Caps lock is not retrieved the same way as Scroll and Num
140            lock since it doesn't need to be. */
141         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
142         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
143                                                      XK_Scroll_Lock);
144           
145         for (cnt = 0; cnt < size; ++cnt) {
146             if (! modmap->modifiermap[cnt]) continue;
147                
148             if (num_lock == modmap->modifiermap[cnt])
149                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
150             if (scroll_lock == modmap->modifiermap[cnt])
151                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
152         }
153     }
154
155     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
156
157 #ifdef USE_SM
158     IceAddConnectionWatch(ice_watch, NULL);
159 #endif
160
161     client_add_destructor(focus_delay_client_dest);
162 }
163
164 void event_shutdown(gboolean reconfig)
165 {
166     if (reconfig) return;
167
168 #ifdef USE_SM
169     IceRemoveConnectionWatch(ice_watch, NULL);
170 #endif
171
172     client_remove_destructor(focus_delay_client_dest);
173     XFreeModifiermap(modmap);
174 }
175
176 static Window event_get_window(XEvent *e)
177 {
178     Window window;
179
180     /* pick a window */
181     switch (e->type) {
182     case SelectionClear:
183         window = RootWindow(ob_display, ob_screen);
184         break;
185     case MapRequest:
186         window = e->xmap.window;
187         break;
188     case UnmapNotify:
189         window = e->xunmap.window;
190         break;
191     case DestroyNotify:
192         window = e->xdestroywindow.window;
193         break;
194     case ConfigureRequest:
195         window = e->xconfigurerequest.window;
196         break;
197     case ConfigureNotify:
198         window = e->xconfigure.window;
199         break;
200     default:
201 #ifdef XKB
202         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
203             switch (((XkbAnyEvent*)e)->xkb_type) {
204             case XkbBellNotify:
205                 window = ((XkbBellNotifyEvent*)e)->window;
206             default:
207                 window = None;
208             }
209         } else
210 #endif
211             window = e->xany.window;
212     }
213     return window;
214 }
215
216 static void event_set_lasttime(XEvent *e)
217 {
218     Time t = 0;
219
220     /* grab the lasttime and hack up the state */
221     switch (e->type) {
222     case ButtonPress:
223     case ButtonRelease:
224         t = e->xbutton.time;
225         break;
226     case KeyPress:
227         t = e->xkey.time;
228         break;
229     case KeyRelease:
230         t = e->xkey.time;
231         break;
232     case MotionNotify:
233         t = e->xmotion.time;
234         break;
235     case PropertyNotify:
236         t = e->xproperty.time;
237         break;
238     case EnterNotify:
239     case LeaveNotify:
240         t = e->xcrossing.time;
241         break;
242     default:
243         /* if more event types are anticipated, get their timestamp
244            explicitly */
245         break;
246     }
247
248     if (t > event_lasttime)
249         event_lasttime = t;
250 }
251
252 #define STRIP_MODS(s) \
253         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
254         /* kill off the Button1Mask etc, only want the modifiers */ \
255         s &= (ControlMask | ShiftMask | Mod1Mask | \
256               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
257
258 static void event_hack_mods(XEvent *e)
259 {
260     KeyCode *kp;
261     int i, k;
262
263     switch (e->type) {
264     case ButtonPress:
265     case ButtonRelease:
266         STRIP_MODS(e->xbutton.state);
267         break;
268     case KeyPress:
269         STRIP_MODS(e->xkey.state);
270         break;
271     case KeyRelease:
272         STRIP_MODS(e->xkey.state);
273         /* remove from the state the mask of the modifier being released, if
274            it is a modifier key being released (this is a little ugly..) */
275         kp = modmap->modifiermap;
276         for (i = 0; i < mask_table_size; ++i) {
277             for (k = 0; k < modmap->max_keypermod; ++k) {
278                 if (*kp == e->xkey.keycode) { /* found the keycode */
279                     /* remove the mask for it */
280                     e->xkey.state &= ~mask_table[i];
281                     /* cause the first loop to break; */
282                     i = mask_table_size;
283                     break; /* get outta here! */
284                 }
285                 ++kp;
286             }
287         }
288         break;
289     case MotionNotify:
290         STRIP_MODS(e->xmotion.state);
291         /* compress events */
292         {
293             XEvent ce;
294             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
295                                           e->type, &ce)) {
296                 e->xmotion.x_root = ce.xmotion.x_root;
297                 e->xmotion.y_root = ce.xmotion.y_root;
298             }
299         }
300         break;
301     }
302 }
303
304 static gboolean event_ignore(XEvent *e, ObClient *client)
305 {
306     switch(e->type) {
307     case FocusIn:
308         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
309            because of RevertToPointerRoot. If the focus ends up reverting to
310            pointer root on a workspace change, then the FocusIn event that we
311            want will be of type NotifyAncestor. This situation does not occur
312            for FocusOut, so it is safely ignored there.
313         */
314         if (INVALID_FOCUSIN(e) ||
315             client == NULL) {
316 #ifdef DEBUG_FOCUS
317             ob_debug("FocusIn on %lx mode %d detail %d IGNORED\n",
318                      e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
319 #endif
320             /* says a client was not found for the event (or a valid FocusIn
321                event was not found.
322             */
323             e->xfocus.window = None;
324             return TRUE;
325         }
326
327 #ifdef DEBUG_FOCUS
328         ob_debug("FocusIn on %lx mode %d detail %d\n", e->xfocus.window,
329                  e->xfocus.mode, e->xfocus.detail);
330 #endif
331         break;
332     case FocusOut:
333         if (INVALID_FOCUSOUT(e)) {
334 #ifdef DEBUG_FOCUS
335         ob_debug("FocusOut on %lx mode %d detail %d IGNORED\n",
336                  e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
337 #endif
338             return TRUE;
339         }
340
341 #ifdef DEBUG_FOCUS
342         ob_debug("FocusOut on %lx mode %d detail %d\n",
343                  e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
344 #endif
345
346         {
347             XEvent fe;
348             gboolean fallback = TRUE;
349
350             while (TRUE) {
351                 if (!XCheckTypedWindowEvent(ob_display, e->xfocus.window,
352                                             FocusOut, &fe))
353                     if (!XCheckTypedEvent(ob_display, FocusIn, &fe))
354                         break;
355                 if (fe.type == FocusOut) {
356 #ifdef DEBUG_FOCUS
357                     ob_debug("found pending FocusOut\n");
358 #endif
359                     if (!INVALID_FOCUSOUT(&fe)) {
360                         /* if there is a VALID FocusOut still coming, don't
361                            fallback focus yet, we'll deal with it then */
362                         XPutBackEvent(ob_display, &fe);
363                         fallback = FALSE;
364                         break;
365                     }
366                 } else {
367 #ifdef DEBUG_FOCUS
368                     ob_debug("found pending FocusIn\n");
369 #endif
370                     /* is the focused window getting a FocusOut/In back to
371                        itself?
372                     */
373                     if (fe.xfocus.window == e->xfocus.window &&
374                         !event_ignore(&fe, client)) {
375                         /*
376                           if focus_client is not set, then we can't do
377                           this. we need the FocusIn. This happens in the
378                           case when the set_focus_client(NULL) in the
379                           focus_fallback function fires and then
380                           focus_fallback picks the currently focused
381                           window (such as on a SendToDesktop-esque action.
382                         */
383                         if (focus_client) {
384 #ifdef DEBUG_FOCUS
385                             ob_debug("focused window got an Out/In back to "
386                                      "itself IGNORED both\n");
387 #endif
388                             return TRUE;
389                         } else {
390                             event_process(&fe, NULL);
391 #ifdef DEBUG_FOCUS
392                             ob_debug("focused window got an Out/In back to "
393                                      "itself but focus_client was null "
394                                      "IGNORED just the Out\n");
395 #endif
396                             return TRUE;
397                         }
398                     }
399
400                     {
401                         ObEventData d;
402
403                         /* once all the FocusOut's have been dealt with, if
404                            there is a FocusIn still left and it is valid, then
405                            use it */
406                         event_process(&fe, &d);
407                         if (!d.ignored) {
408 #ifdef DEBUG_FOCUS
409                             ob_debug("FocusIn was OK, so don't fallback\n");
410 #endif
411                             fallback = FALSE;
412                             break;
413                         }
414                     }
415                 }
416             }
417             if (fallback) {
418 #ifdef DEBUG_FOCUS
419                 ob_debug("no valid FocusIn and no FocusOut events found, "
420                          "falling back\n");
421 #endif
422                 focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS);
423             }
424         }
425         break;
426     }
427     return FALSE;
428 }
429
430 static void event_process(const XEvent *ec, gpointer data)
431 {
432     Window window;
433     ObGroup *group = NULL;
434     ObClient *client = NULL;
435     ObDock *dock = NULL;
436     ObDockApp *dockapp = NULL;
437     ObWindow *obwin = NULL;
438     XEvent ee, *e;
439     ObEventData *ed = data;
440
441     /* make a copy we can mangle */
442     ee = *ec;
443     e = &ee;
444
445     window = event_get_window(e);
446     if (!(e->type == PropertyNotify &&
447           (group = g_hash_table_lookup(group_map, &window))))
448         if ((obwin = g_hash_table_lookup(window_map, &window))) {
449             switch (obwin->type) {
450             case Window_Dock:
451                 dock = WINDOW_AS_DOCK(obwin);
452                 break;
453             case Window_DockApp:
454                 dockapp = WINDOW_AS_DOCKAPP(obwin);
455                 break;
456             case Window_Client:
457                 client = WINDOW_AS_CLIENT(obwin);
458                 break;
459             case Window_Menu:
460             case Window_Internal:
461                 /* not to be used for events */
462                 g_assert_not_reached();
463                 break;
464             }
465         }
466
467     event_set_lasttime(e);
468     event_hack_mods(e);
469     if (event_ignore(e, client)) {
470         if (ed)
471             ed->ignored = TRUE;
472         return;
473     } else if (ed)
474             ed->ignored = FALSE;
475
476     /* deal with it in the kernel */
477     if (group)
478         event_handle_group(group, e);
479     else if (client)
480         event_handle_client(client, e);
481     else if (dockapp)
482         event_handle_dockapp(dockapp, e);
483     else if (dock)
484         event_handle_dock(dock, e);
485     else if (window == RootWindow(ob_display, ob_screen))
486         event_handle_root(e);
487     else if (e->type == MapRequest)
488         client_manage(window);
489     else if (e->type == ConfigureRequest) {
490         /* unhandled configure requests must be used to configure the
491            window directly */
492         XWindowChanges xwc;
493                
494         xwc.x = e->xconfigurerequest.x;
495         xwc.y = e->xconfigurerequest.y;
496         xwc.width = e->xconfigurerequest.width;
497         xwc.height = e->xconfigurerequest.height;
498         xwc.border_width = e->xconfigurerequest.border_width;
499         xwc.sibling = e->xconfigurerequest.above;
500         xwc.stack_mode = e->xconfigurerequest.detail;
501        
502         /* we are not to be held responsible if someone sends us an
503            invalid request! */
504         xerror_set_ignore(TRUE);
505         XConfigureWindow(ob_display, window,
506                          e->xconfigurerequest.value_mask, &xwc);
507         xerror_set_ignore(FALSE);
508     }
509
510     /* user input (action-bound) events */
511     if (e->type == ButtonPress || e->type == ButtonRelease ||
512         e->type == MotionNotify || e->type == KeyPress ||
513         e->type == KeyRelease)
514     {
515         if (menu_frame_visible)
516             event_handle_menu(e);
517         else {
518             if (!keyboard_process_interactive_grab(e, &client)) {
519                 if (moveresize_in_progress) {
520                     moveresize_event(e);
521
522                     /* make further actions work on the client being
523                        moved/resized */
524                     client = moveresize_client;
525                 }
526
527                 menu_can_hide = FALSE;
528                 ob_main_loop_timeout_add(ob_main_loop,
529                                          G_USEC_PER_SEC / 4,
530                                          menu_hide_delay_func,
531                                          NULL, NULL);
532
533                 if (e->type == ButtonPress || e->type == ButtonRelease ||
534                     e->type == MotionNotify)
535                     mouse_event(client, e);
536                 else if (e->type == KeyPress)
537                     /* when in the middle of a focus cycling action, this
538                        causes the window which appears to be focused to be
539                        the one on which the actions will be executed */
540                     keyboard_event((focus_cycle_target ?
541                                     focus_cycle_target : client), e);
542             }
543         }
544     }
545 }
546
547 static void event_handle_root(XEvent *e)
548 {
549     Atom msgtype;
550      
551     switch(e->type) {
552     case SelectionClear:
553         ob_debug("Another WM has requested to replace us. Exiting.\n");
554         ob_exit(0);
555         break;
556
557     case ClientMessage:
558         if (e->xclient.format != 32) break;
559
560         msgtype = e->xclient.message_type;
561         if (msgtype == prop_atoms.net_current_desktop) {
562             unsigned int d = e->xclient.data.l[0];
563             if (d < screen_num_desktops)
564                 screen_set_desktop(d);
565         } else if (msgtype == prop_atoms.net_number_of_desktops) {
566             unsigned int d = e->xclient.data.l[0];
567             if (d > 0)
568                 screen_set_num_desktops(d);
569         } else if (msgtype == prop_atoms.net_showing_desktop) {
570             screen_show_desktop(e->xclient.data.l[0] != 0);
571         }
572         break;
573     case PropertyNotify:
574         if (e->xproperty.atom == prop_atoms.net_desktop_names)
575             screen_update_desktop_names();
576         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
577             screen_update_layout();
578         break;
579     case ConfigureNotify:
580 #ifdef XRANDR
581         XRRUpdateConfiguration(e);
582 #endif
583         screen_resize();
584         break;
585     default:
586         ;
587 #ifdef VIDMODE
588         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
589             ob_debug("VIDMODE EVENT\n");
590         }
591 #endif
592     }
593 }
594
595 static void event_handle_group(ObGroup *group, XEvent *e)
596 {
597     GSList *it;
598
599     g_assert(e->type == PropertyNotify);
600
601     for (it = group->members; it; it = g_slist_next(it))
602         event_handle_client(it->data, e);
603 }
604
605 static void event_handle_client(ObClient *client, XEvent *e)
606 {
607     XEvent ce;
608     Atom msgtype;
609     int i=0;
610     ObFrameContext con;
611      
612     switch (e->type) {
613     case VisibilityNotify:
614         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
615         break;
616     case ButtonPress:
617     case ButtonRelease:
618         /* Wheel buttons don't draw because they are an instant click, so it
619            is a waste of resources to go drawing it. */
620         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
621             con = frame_context(client, e->xbutton.window);
622             con = mouse_button_frame_context(con, e->xbutton.button);
623             switch (con) {
624             case OB_FRAME_CONTEXT_MAXIMIZE:
625                 client->frame->max_press = (e->type == ButtonPress);
626                 framerender_frame(client->frame);
627                 break;
628             case OB_FRAME_CONTEXT_CLOSE:
629                 client->frame->close_press = (e->type == ButtonPress);
630                 framerender_frame(client->frame);
631                 break;
632             case OB_FRAME_CONTEXT_ICONIFY:
633                 client->frame->iconify_press = (e->type == ButtonPress);
634                 framerender_frame(client->frame);
635                 break;
636             case OB_FRAME_CONTEXT_ALLDESKTOPS:
637                 client->frame->desk_press = (e->type == ButtonPress);
638                 framerender_frame(client->frame);
639                 break; 
640             case OB_FRAME_CONTEXT_SHADE:
641                 client->frame->shade_press = (e->type == ButtonPress);
642                 framerender_frame(client->frame);
643                 break;
644             default:
645                 /* nothing changes with clicks for any other contexts */
646                 break;
647             }
648         }
649         break;
650     case FocusIn:
651 #ifdef DEBUG_FOCUS
652         ob_debug("FocusIn on client for %lx\n", client->window);
653 #endif
654         if (client != focus_client) {
655             focus_set_client(client);
656             frame_adjust_focus(client->frame, TRUE);
657         }
658         break;
659     case FocusOut:
660 #ifdef DEBUG_FOCUS
661         ob_debug("FocusOut on client for %lx\n", client->window);
662 #endif
663         /* are we a fullscreen window or a transient of one? (checks layer)
664            if we are then we need to be iconified since we are losing focus
665          */
666         if (client->layer == OB_STACKING_LAYER_FULLSCREEN && !client->iconic &&
667             !client_search_focus_tree_full(client))
668             /* iconify fullscreen windows when they and their transients
669                aren't focused */
670             client_iconify(client, TRUE, TRUE);
671         frame_adjust_focus(client->frame, FALSE);
672         break;
673     case LeaveNotify:
674         con = frame_context(client, e->xcrossing.window);
675         switch (con) {
676         case OB_FRAME_CONTEXT_MAXIMIZE:
677             client->frame->max_hover = FALSE;
678             frame_adjust_state(client->frame);
679             break;
680         case OB_FRAME_CONTEXT_ALLDESKTOPS:
681             client->frame->desk_hover = FALSE;
682             frame_adjust_state(client->frame);
683             break;
684         case OB_FRAME_CONTEXT_SHADE:
685             client->frame->shade_hover = FALSE;
686             frame_adjust_state(client->frame);
687             break;
688         case OB_FRAME_CONTEXT_ICONIFY:
689             client->frame->iconify_hover = FALSE;
690             frame_adjust_state(client->frame);
691             break;
692         case OB_FRAME_CONTEXT_CLOSE:
693             client->frame->close_hover = FALSE;
694             frame_adjust_state(client->frame);
695             break;
696         case OB_FRAME_CONTEXT_FRAME:
697             if (client == focus_delay_client) {
698                 ob_main_loop_timeout_remove_data(ob_main_loop,
699                                                  focus_delay_func,
700                                                  focus_delay_client);
701                 focus_delay_client = NULL;
702             }
703         default:
704             break;
705         }
706         break;
707     case EnterNotify:
708     {
709         gboolean nofocus = FALSE;
710
711         if (ignore_enter_focus) {
712             ignore_enter_focus--;
713             nofocus = TRUE;
714         }
715
716         con = frame_context(client, e->xcrossing.window);
717         switch (con) {
718         case OB_FRAME_CONTEXT_MAXIMIZE:
719             client->frame->max_hover = TRUE;
720             frame_adjust_state(client->frame);
721             break;
722         case OB_FRAME_CONTEXT_ALLDESKTOPS:
723             client->frame->desk_hover = TRUE;
724             frame_adjust_state(client->frame);
725             break;
726         case OB_FRAME_CONTEXT_SHADE:
727             client->frame->shade_hover = TRUE;
728             frame_adjust_state(client->frame);
729             break;
730         case OB_FRAME_CONTEXT_ICONIFY:
731             client->frame->iconify_hover = TRUE;
732             frame_adjust_state(client->frame);
733             break;
734         case OB_FRAME_CONTEXT_CLOSE:
735             client->frame->close_hover = TRUE;
736             frame_adjust_state(client->frame);
737             break;
738         case OB_FRAME_CONTEXT_FRAME:
739             if (!nofocus && config_focus_follow &&
740                 client_normal(client) && client_can_focus(client)) {
741                 if (e->xcrossing.mode == NotifyGrab ||
742                     e->xcrossing.detail == NotifyInferior ||
743                     e->xcrossing.mode == NotifyUngrab)
744                 {
745 #ifdef DEBUG_FOCUS
746                     ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
747                              (e->type == EnterNotify ? "Enter" : "Leave"),
748                              e->xcrossing.mode,
749                              e->xcrossing.detail, client?client->window:0);
750 #endif
751                 } else {
752 #ifdef DEBUG_FOCUS
753                     ob_debug("%sNotify mode %d detail %d on %lx, "
754                              "focusing window\n",
755                              (e->type == EnterNotify ? "Enter" : "Leave"),
756                              e->xcrossing.mode,
757                              e->xcrossing.detail, client?client->window:0);
758 #endif
759                     if (config_focus_delay) {
760                         ob_main_loop_timeout_add(ob_main_loop,
761                                                  config_focus_delay,
762                                                  focus_delay_func,
763                                                  client, NULL);
764                         focus_delay_client = client;
765                     } else {
766                         client_focus(client);
767                         if (config_focus_raise)
768                             stacking_raise
769                                 (CLIENT_AS_WINDOW(focus_delay_client));
770                     }
771                 }
772             }
773             break;
774         default:
775             break;
776         }
777         break;
778     }
779     case ConfigureRequest:
780         /* compress these */
781         while (XCheckTypedWindowEvent(ob_display, client->window,
782                                       ConfigureRequest, &ce)) {
783             ++i;
784             /* XXX if this causes bad things.. we can compress config req's
785                with the same mask. */
786             e->xconfigurerequest.value_mask |=
787                 ce.xconfigurerequest.value_mask;
788             if (ce.xconfigurerequest.value_mask & CWX)
789                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
790             if (ce.xconfigurerequest.value_mask & CWY)
791                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
792             if (ce.xconfigurerequest.value_mask & CWWidth)
793                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
794             if (ce.xconfigurerequest.value_mask & CWHeight)
795                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
796             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
797                 e->xconfigurerequest.border_width =
798                     ce.xconfigurerequest.border_width;
799             if (ce.xconfigurerequest.value_mask & CWStackMode)
800                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
801         }
802
803         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
804         if (client->iconic || client->shaded) return;
805
806         /* resize, then move, as specified in the EWMH section 7.7 */
807         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
808                                                CWX | CWY |
809                                                CWBorderWidth)) {
810             int x, y, w, h;
811             ObCorner corner;
812
813             if (e->xconfigurerequest.value_mask & CWBorderWidth)
814                 client->border_width = e->xconfigurerequest.border_width;
815
816             x = (e->xconfigurerequest.value_mask & CWX) ?
817                 e->xconfigurerequest.x : client->area.x;
818             y = (e->xconfigurerequest.value_mask & CWY) ?
819                 e->xconfigurerequest.y : client->area.y;
820             w = (e->xconfigurerequest.value_mask & CWWidth) ?
821                 e->xconfigurerequest.width : client->area.width;
822             h = (e->xconfigurerequest.value_mask & CWHeight) ?
823                 e->xconfigurerequest.height : client->area.height;
824
825             {
826                 int newx = x;
827                 int newy = y;
828                 int fw = w +
829                     client->frame->size.left + client->frame->size.right;
830                 int fh = h +
831                     client->frame->size.top + client->frame->size.bottom;
832                 client_find_onscreen(client, &newx, &newy, fw, fh,
833                                      client_normal(client));
834                 if (e->xconfigurerequest.value_mask & CWX)
835                     x = newx;
836                 if (e->xconfigurerequest.value_mask & CWY)
837                     y = newy;
838             }
839                
840             switch (client->gravity) {
841             case NorthEastGravity:
842             case EastGravity:
843                 corner = OB_CORNER_TOPRIGHT;
844                 break;
845             case SouthWestGravity:
846             case SouthGravity:
847                 corner = OB_CORNER_BOTTOMLEFT;
848                 break;
849             case SouthEastGravity:
850                 corner = OB_CORNER_BOTTOMRIGHT;
851                 break;
852             default:     /* NorthWest, Static, etc */
853                 corner = OB_CORNER_TOPLEFT;
854             }
855
856             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
857                                   TRUE);
858         }
859
860         if (e->xconfigurerequest.value_mask & CWStackMode) {
861             switch (e->xconfigurerequest.detail) {
862             case Below:
863             case BottomIf:
864                 stacking_lower(CLIENT_AS_WINDOW(client));
865                 break;
866
867             case Above:
868             case TopIf:
869             default:
870                 stacking_raise(CLIENT_AS_WINDOW(client));
871                 break;
872             }
873         }
874         break;
875     case UnmapNotify:
876         if (client->ignore_unmaps) {
877             client->ignore_unmaps--;
878             break;
879         }
880         client_unmanage(client);
881         break;
882     case DestroyNotify:
883         client_unmanage(client);
884         break;
885     case ReparentNotify:
886         /* this is when the client is first taken captive in the frame */
887         if (e->xreparent.parent == client->frame->plate) break;
888
889         /*
890           This event is quite rare and is usually handled in unmapHandler.
891           However, if the window is unmapped when the reparent event occurs,
892           the window manager never sees it because an unmap event is not sent
893           to an already unmapped window.
894         */
895
896         /* we don't want the reparent event, put it back on the stack for the
897            X server to deal with after we unmanage the window */
898         XPutBackEvent(ob_display, e);
899      
900         client_unmanage(client);
901         break;
902     case MapRequest:
903         ob_debug("MapRequest for 0x%lx\n", client->window);
904         if (!client->iconic) break; /* this normally doesn't happen, but if it
905                                        does, we don't want it! */
906         if (screen_showing_desktop)
907             screen_show_desktop(FALSE);
908         client_iconify(client, FALSE, TRUE);
909         if (!client->frame->visible)
910             /* if its not visible still, then don't mess with it */
911             break;
912         if (client->shaded)
913             client_shade(client, FALSE);
914         client_focus(client);
915         stacking_raise(CLIENT_AS_WINDOW(client));
916         break;
917     case ClientMessage:
918         /* validate cuz we query stuff off the client here */
919         if (!client_validate(client)) break;
920   
921         if (e->xclient.format != 32) return;
922
923         msgtype = e->xclient.message_type;
924         if (msgtype == prop_atoms.wm_change_state) {
925             /* compress changes into a single change */
926             while (XCheckTypedWindowEvent(ob_display, client->window,
927                                           e->type, &ce)) {
928                 /* XXX: it would be nice to compress ALL messages of a
929                    type, not just messages in a row without other
930                    message types between. */
931                 if (ce.xclient.message_type != msgtype) {
932                     XPutBackEvent(ob_display, &ce);
933                     break;
934                 }
935                 e->xclient = ce.xclient;
936             }
937             client_set_wm_state(client, e->xclient.data.l[0]);
938         } else if (msgtype == prop_atoms.net_wm_desktop) {
939             /* compress changes into a single change */
940             while (XCheckTypedWindowEvent(ob_display, client->window,
941                                           e->type, &ce)) {
942                 /* XXX: it would be nice to compress ALL messages of a
943                    type, not just messages in a row without other
944                    message types between. */
945                 if (ce.xclient.message_type != msgtype) {
946                     XPutBackEvent(ob_display, &ce);
947                     break;
948                 }
949                 e->xclient = ce.xclient;
950             }
951             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
952                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
953                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
954                                    FALSE);
955         } else if (msgtype == prop_atoms.net_wm_state) {
956             /* can't compress these */
957             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
958                      (e->xclient.data.l[0] == 0 ? "Remove" :
959                       e->xclient.data.l[0] == 1 ? "Add" :
960                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
961                      e->xclient.data.l[1], e->xclient.data.l[2],
962                      client->window);
963             client_set_state(client, e->xclient.data.l[0],
964                              e->xclient.data.l[1], e->xclient.data.l[2]);
965         } else if (msgtype == prop_atoms.net_close_window) {
966             ob_debug("net_close_window for 0x%lx\n", client->window);
967             client_close(client);
968         } else if (msgtype == prop_atoms.net_active_window) {
969             ob_debug("net_active_window for 0x%lx\n", client->window);
970             client_activate(client, FALSE);
971         } else if (msgtype == prop_atoms.net_wm_moveresize) {
972             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
973             if ((Atom)e->xclient.data.l[2] ==
974                 prop_atoms.net_wm_moveresize_size_topleft ||
975                 (Atom)e->xclient.data.l[2] ==
976                 prop_atoms.net_wm_moveresize_size_top ||
977                 (Atom)e->xclient.data.l[2] ==
978                 prop_atoms.net_wm_moveresize_size_topright ||
979                 (Atom)e->xclient.data.l[2] ==
980                 prop_atoms.net_wm_moveresize_size_right ||
981                 (Atom)e->xclient.data.l[2] ==
982                 prop_atoms.net_wm_moveresize_size_right ||
983                 (Atom)e->xclient.data.l[2] ==
984                 prop_atoms.net_wm_moveresize_size_bottomright ||
985                 (Atom)e->xclient.data.l[2] ==
986                 prop_atoms.net_wm_moveresize_size_bottom ||
987                 (Atom)e->xclient.data.l[2] ==
988                 prop_atoms.net_wm_moveresize_size_bottomleft ||
989                 (Atom)e->xclient.data.l[2] ==
990                 prop_atoms.net_wm_moveresize_size_left ||
991                 (Atom)e->xclient.data.l[2] ==
992                 prop_atoms.net_wm_moveresize_move ||
993                 (Atom)e->xclient.data.l[2] ==
994                 prop_atoms.net_wm_moveresize_size_keyboard ||
995                 (Atom)e->xclient.data.l[2] ==
996                 prop_atoms.net_wm_moveresize_move_keyboard) {
997
998                 moveresize_start(client, e->xclient.data.l[0],
999                                  e->xclient.data.l[1], e->xclient.data.l[3],
1000                                  e->xclient.data.l[2]);
1001             }
1002         } else if (msgtype == prop_atoms.net_moveresize_window) {
1003             int oldg = client->gravity;
1004             int tmpg, x, y, w, h;
1005
1006             if (e->xclient.data.l[0] & 0xff)
1007                 tmpg = e->xclient.data.l[0] & 0xff;
1008             else
1009                 tmpg = oldg;
1010
1011             if (e->xclient.data.l[0] & 1 << 8)
1012                 x = e->xclient.data.l[1];
1013             else
1014                 x = client->area.x;
1015             if (e->xclient.data.l[0] & 1 << 9)
1016                 y = e->xclient.data.l[2];
1017             else
1018                 y = client->area.y;
1019             if (e->xclient.data.l[0] & 1 << 10)
1020                 w = e->xclient.data.l[3];
1021             else
1022                 w = client->area.width;
1023             if (e->xclient.data.l[0] & 1 << 11)
1024                 h = e->xclient.data.l[4];
1025             else
1026                 h = client->area.height;
1027             client->gravity = tmpg;
1028
1029             {
1030                 int newx = x;
1031                 int newy = y;
1032                 int fw = w +
1033                     client->frame->size.left + client->frame->size.right;
1034                 int fh = h +
1035                     client->frame->size.top + client->frame->size.bottom;
1036                 client_find_onscreen(client, &newx, &newy, fw, fh,
1037                                      client_normal(client));
1038                 if (e->xclient.data.l[0] & 1 << 8)
1039                     x = newx;
1040                 if (e->xclient.data.l[0] & 1 << 9)
1041                     y = newy;
1042             }
1043                
1044             client_configure(client, OB_CORNER_TOPLEFT,
1045                              x, y, w, h, FALSE, TRUE);
1046
1047             client->gravity = oldg;
1048         }
1049         break;
1050     case PropertyNotify:
1051         /* validate cuz we query stuff off the client here */
1052         if (!client_validate(client)) break;
1053   
1054         /* compress changes to a single property into a single change */
1055         while (XCheckTypedWindowEvent(ob_display, client->window,
1056                                       e->type, &ce)) {
1057             Atom a, b;
1058
1059             /* XXX: it would be nice to compress ALL changes to a property,
1060                not just changes in a row without other props between. */
1061
1062             a = ce.xproperty.atom;
1063             b = e->xproperty.atom;
1064
1065             if (a == b)
1066                 continue;
1067             if ((a == prop_atoms.net_wm_name ||
1068                  a == prop_atoms.wm_name ||
1069                  a == prop_atoms.net_wm_icon_name ||
1070                  a == prop_atoms.wm_icon_name)
1071                 &&
1072                 (b == prop_atoms.net_wm_name ||
1073                  b == prop_atoms.wm_name ||
1074                  b == prop_atoms.net_wm_icon_name ||
1075                  b == prop_atoms.wm_icon_name)) {
1076                 continue;
1077             }
1078             if ((a == prop_atoms.net_wm_icon ||
1079                  a == prop_atoms.kwm_win_icon)
1080                 &&
1081                 (b == prop_atoms.net_wm_icon ||
1082                  b == prop_atoms.kwm_win_icon))
1083                 continue;
1084
1085             XPutBackEvent(ob_display, &ce);
1086             break;
1087         }
1088
1089         msgtype = e->xproperty.atom;
1090         if (msgtype == XA_WM_NORMAL_HINTS) {
1091             client_update_normal_hints(client);
1092             /* normal hints can make a window non-resizable */
1093             client_setup_decor_and_functions(client);
1094         } else if (msgtype == XA_WM_HINTS) {
1095             client_update_wmhints(client);
1096         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1097             client_update_transient_for(client);
1098             client_get_type(client);
1099             /* type may have changed, so update the layer */
1100             client_calc_layer(client);
1101             client_setup_decor_and_functions(client);
1102         } else if (msgtype == prop_atoms.net_wm_name ||
1103                    msgtype == prop_atoms.wm_name ||
1104                    msgtype == prop_atoms.net_wm_icon_name ||
1105                    msgtype == prop_atoms.wm_icon_name) {
1106             client_update_title(client);
1107         } else if (msgtype == prop_atoms.wm_class) {
1108             client_update_class(client);
1109         } else if (msgtype == prop_atoms.wm_protocols) {
1110             client_update_protocols(client);
1111             client_setup_decor_and_functions(client);
1112         }
1113         else if (msgtype == prop_atoms.net_wm_strut) {
1114             client_update_strut(client);
1115         }
1116         else if (msgtype == prop_atoms.net_wm_icon ||
1117                  msgtype == prop_atoms.kwm_win_icon) {
1118             client_update_icons(client);
1119         }
1120         else if (msgtype == prop_atoms.sm_client_id) {
1121             client_update_sm_client_id(client);
1122         }
1123     default:
1124         ;
1125 #ifdef SHAPE
1126         if (extensions_shape && e->type == extensions_shape_event_basep) {
1127             client->shaped = ((XShapeEvent*)e)->shaped;
1128             frame_adjust_shape(client->frame);
1129         }
1130 #endif
1131     }
1132 }
1133
1134 static void event_handle_dock(ObDock *s, XEvent *e)
1135 {
1136     switch (e->type) {
1137     case ButtonPress:
1138         stacking_raise(DOCK_AS_WINDOW(s));
1139         break;
1140     case EnterNotify:
1141         dock_hide(FALSE);
1142         break;
1143     case LeaveNotify:
1144         dock_hide(TRUE);
1145         break;
1146     }
1147 }
1148
1149 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1150 {
1151     switch (e->type) {
1152     case MotionNotify:
1153         dock_app_drag(app, &e->xmotion);
1154         break;
1155     case UnmapNotify:
1156         if (app->ignore_unmaps) {
1157             app->ignore_unmaps--;
1158             break;
1159         }
1160         dock_remove(app, TRUE);
1161         break;
1162     case DestroyNotify:
1163         dock_remove(app, FALSE);
1164         break;
1165     case ReparentNotify:
1166         dock_remove(app, FALSE);
1167         break;
1168     case ConfigureNotify:
1169         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1170         break;
1171     }
1172 }
1173
1174 ObMenuFrame* find_active_menu()
1175 {
1176     GList *it;
1177     ObMenuFrame *f;
1178
1179     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1180         f = it->data;
1181         if (f->selected)
1182             break;
1183     }
1184     return it ? it->data : NULL;
1185 }
1186
1187 static void event_handle_menu(XEvent *ev)
1188 {
1189     ObMenuFrame *f;
1190     ObMenuEntryFrame *e;
1191
1192     switch (ev->type) {
1193     case ButtonRelease:
1194         if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1195                                         ev->xbutton.y_root)))
1196             menu_entry_frame_execute(e, ev->xbutton.state);
1197         else if (menu_can_hide)
1198             menu_frame_hide_all();
1199         break;
1200     case MotionNotify:
1201         if ((f = menu_frame_under(ev->xmotion.x_root,
1202                                   ev->xmotion.y_root))) {
1203             menu_frame_move_on_screen(f);
1204             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1205                                             ev->xmotion.y_root)))
1206                 menu_frame_select(f, e);
1207         }
1208         {
1209             ObMenuFrame *a;
1210
1211             a = find_active_menu();
1212             if (a && a != f &&
1213                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1214             {
1215                 menu_frame_select(a, NULL);
1216             }
1217         }
1218         break;
1219     case KeyPress:
1220         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1221             menu_frame_hide_all();
1222         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1223             ObMenuFrame *f;
1224             if ((f = find_active_menu()))
1225                 menu_entry_frame_execute(f->selected, ev->xkey.state);
1226         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1227             ObMenuFrame *f;
1228             if ((f = find_active_menu()) && f->parent)
1229                 menu_frame_select(f, NULL);
1230         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1231             ObMenuFrame *f;
1232             if ((f = find_active_menu()) && f->child)
1233                 menu_frame_select_next(f->child);
1234         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1235             ObMenuFrame *f;
1236             if ((f = find_active_menu()))
1237                 menu_frame_select_previous(f);
1238         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1239             ObMenuFrame *f;
1240             if ((f = find_active_menu()))
1241                 menu_frame_select_next(f);
1242         }
1243         break;
1244     }
1245 }
1246
1247 static gboolean menu_hide_delay_func(gpointer data)
1248 {
1249     menu_can_hide = TRUE;
1250     return FALSE; /* no repeat */
1251 }
1252
1253 static gboolean focus_delay_func(gpointer data)
1254 {
1255     if (data == focus_delay_client) {
1256         client_focus(focus_delay_client);
1257         if (config_focus_raise)
1258             stacking_raise(CLIENT_AS_WINDOW(focus_delay_client));
1259     }
1260     return FALSE; /* no repeat */
1261 }
1262
1263 static void focus_delay_client_dest(gpointer data)
1264 {
1265     ObClient *c = data;
1266     if (c == focus_delay_client) {
1267         ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1268                                          focus_delay_client);
1269         focus_delay_client = NULL;
1270     }
1271 }
1272
1273 void event_ignore_enter_focus(guint num)
1274 {
1275     ignore_enter_focus += num;
1276 }