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