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