]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
allow a number of enter events to be ignored. Don't just skip any enter/leave events...
[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 "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                 {
744 #ifdef DEBUG_FOCUS
745                     ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
746                              (e->type == EnterNotify ? "Enter" : "Leave"),
747                              e->xcrossing.mode,
748                              e->xcrossing.detail, client?client->window:0);
749 #endif
750                 } else {
751 #ifdef DEBUG_FOCUS
752                     ob_debug("%sNotify mode %d detail %d on %lx, "
753                              "focusing window\n",
754                              (e->type == EnterNotify ? "Enter" : "Leave"),
755                              e->xcrossing.mode,
756                              e->xcrossing.detail, client?client->window:0);
757 #endif
758                     if (config_focus_delay) {
759                         ob_main_loop_timeout_add(ob_main_loop,
760                                                  config_focus_delay,
761                                                  focus_delay_func,
762                                                  client, NULL);
763                         focus_delay_client = client;
764                     } else
765                         client_focus(client);
766                 }
767             }
768             break;
769         default:
770             break;
771         }
772         break;
773     }
774     case ConfigureRequest:
775         /* compress these */
776         while (XCheckTypedWindowEvent(ob_display, client->window,
777                                       ConfigureRequest, &ce)) {
778             ++i;
779             /* XXX if this causes bad things.. we can compress config req's
780                with the same mask. */
781             e->xconfigurerequest.value_mask |=
782                 ce.xconfigurerequest.value_mask;
783             if (ce.xconfigurerequest.value_mask & CWX)
784                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
785             if (ce.xconfigurerequest.value_mask & CWY)
786                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
787             if (ce.xconfigurerequest.value_mask & CWWidth)
788                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
789             if (ce.xconfigurerequest.value_mask & CWHeight)
790                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
791             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
792                 e->xconfigurerequest.border_width =
793                     ce.xconfigurerequest.border_width;
794             if (ce.xconfigurerequest.value_mask & CWStackMode)
795                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
796         }
797
798         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
799         if (client->iconic || client->shaded) return;
800
801         /* resize, then move, as specified in the EWMH section 7.7 */
802         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
803                                                CWX | CWY |
804                                                CWBorderWidth)) {
805             int x, y, w, h;
806             ObCorner corner;
807
808             if (e->xconfigurerequest.value_mask & CWBorderWidth)
809                 client->border_width = e->xconfigurerequest.border_width;
810
811             x = (e->xconfigurerequest.value_mask & CWX) ?
812                 e->xconfigurerequest.x : client->area.x;
813             y = (e->xconfigurerequest.value_mask & CWY) ?
814                 e->xconfigurerequest.y : client->area.y;
815             w = (e->xconfigurerequest.value_mask & CWWidth) ?
816                 e->xconfigurerequest.width : client->area.width;
817             h = (e->xconfigurerequest.value_mask & CWHeight) ?
818                 e->xconfigurerequest.height : client->area.height;
819
820             {
821                 int newx = x;
822                 int newy = y;
823                 int fw = w +
824                     client->frame->size.left + client->frame->size.right;
825                 int fh = h +
826                     client->frame->size.top + client->frame->size.bottom;
827                 client_find_onscreen(client, &newx, &newy, fw, fh,
828                                      client_normal(client));
829                 if (e->xconfigurerequest.value_mask & CWX)
830                     x = newx;
831                 if (e->xconfigurerequest.value_mask & CWY)
832                     y = newy;
833             }
834                
835             switch (client->gravity) {
836             case NorthEastGravity:
837             case EastGravity:
838                 corner = OB_CORNER_TOPRIGHT;
839                 break;
840             case SouthWestGravity:
841             case SouthGravity:
842                 corner = OB_CORNER_BOTTOMLEFT;
843                 break;
844             case SouthEastGravity:
845                 corner = OB_CORNER_BOTTOMRIGHT;
846                 break;
847             default:     /* NorthWest, Static, etc */
848                 corner = OB_CORNER_TOPLEFT;
849             }
850
851             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
852                                   TRUE);
853         }
854
855         if (e->xconfigurerequest.value_mask & CWStackMode) {
856             switch (e->xconfigurerequest.detail) {
857             case Below:
858             case BottomIf:
859                 stacking_lower(CLIENT_AS_WINDOW(client));
860                 break;
861
862             case Above:
863             case TopIf:
864             default:
865                 stacking_raise(CLIENT_AS_WINDOW(client));
866                 break;
867             }
868         }
869         break;
870     case UnmapNotify:
871         if (client->ignore_unmaps) {
872             client->ignore_unmaps--;
873             break;
874         }
875         client_unmanage(client);
876         break;
877     case DestroyNotify:
878         client_unmanage(client);
879         break;
880     case ReparentNotify:
881         /* this is when the client is first taken captive in the frame */
882         if (e->xreparent.parent == client->frame->plate) break;
883
884         /*
885           This event is quite rare and is usually handled in unmapHandler.
886           However, if the window is unmapped when the reparent event occurs,
887           the window manager never sees it because an unmap event is not sent
888           to an already unmapped window.
889         */
890
891         /* we don't want the reparent event, put it back on the stack for the
892            X server to deal with after we unmanage the window */
893         XPutBackEvent(ob_display, e);
894      
895         client_unmanage(client);
896         break;
897     case MapRequest:
898         ob_debug("MapRequest for 0x%lx\n", client->window);
899         if (!client->iconic) break; /* this normally doesn't happen, but if it
900                                        does, we don't want it! */
901         if (screen_showing_desktop)
902             screen_show_desktop(FALSE);
903         client_iconify(client, FALSE, TRUE);
904         if (!client->frame->visible)
905             /* if its not visible still, then don't mess with it */
906             break;
907         if (client->shaded)
908             client_shade(client, FALSE);
909         client_focus(client);
910         stacking_raise(CLIENT_AS_WINDOW(client));
911         break;
912     case ClientMessage:
913         /* validate cuz we query stuff off the client here */
914         if (!client_validate(client)) break;
915   
916         if (e->xclient.format != 32) return;
917
918         msgtype = e->xclient.message_type;
919         if (msgtype == prop_atoms.wm_change_state) {
920             /* compress changes into a single change */
921             while (XCheckTypedWindowEvent(ob_display, client->window,
922                                           e->type, &ce)) {
923                 /* XXX: it would be nice to compress ALL messages of a
924                    type, not just messages in a row without other
925                    message types between. */
926                 if (ce.xclient.message_type != msgtype) {
927                     XPutBackEvent(ob_display, &ce);
928                     break;
929                 }
930                 e->xclient = ce.xclient;
931             }
932             client_set_wm_state(client, e->xclient.data.l[0]);
933         } else if (msgtype == prop_atoms.net_wm_desktop) {
934             /* compress changes into a single change */
935             while (XCheckTypedWindowEvent(ob_display, client->window,
936                                           e->type, &ce)) {
937                 /* XXX: it would be nice to compress ALL messages of a
938                    type, not just messages in a row without other
939                    message types between. */
940                 if (ce.xclient.message_type != msgtype) {
941                     XPutBackEvent(ob_display, &ce);
942                     break;
943                 }
944                 e->xclient = ce.xclient;
945             }
946             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
947                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
948                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
949                                    FALSE);
950         } else if (msgtype == prop_atoms.net_wm_state) {
951             /* can't compress these */
952             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
953                      (e->xclient.data.l[0] == 0 ? "Remove" :
954                       e->xclient.data.l[0] == 1 ? "Add" :
955                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
956                      e->xclient.data.l[1], e->xclient.data.l[2],
957                      client->window);
958             client_set_state(client, e->xclient.data.l[0],
959                              e->xclient.data.l[1], e->xclient.data.l[2]);
960         } else if (msgtype == prop_atoms.net_close_window) {
961             ob_debug("net_close_window for 0x%lx\n", client->window);
962             client_close(client);
963         } else if (msgtype == prop_atoms.net_active_window) {
964             ob_debug("net_active_window for 0x%lx\n", client->window);
965             client_activate(client, FALSE);
966         } else if (msgtype == prop_atoms.net_wm_moveresize) {
967             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
968             if ((Atom)e->xclient.data.l[2] ==
969                 prop_atoms.net_wm_moveresize_size_topleft ||
970                 (Atom)e->xclient.data.l[2] ==
971                 prop_atoms.net_wm_moveresize_size_top ||
972                 (Atom)e->xclient.data.l[2] ==
973                 prop_atoms.net_wm_moveresize_size_topright ||
974                 (Atom)e->xclient.data.l[2] ==
975                 prop_atoms.net_wm_moveresize_size_right ||
976                 (Atom)e->xclient.data.l[2] ==
977                 prop_atoms.net_wm_moveresize_size_right ||
978                 (Atom)e->xclient.data.l[2] ==
979                 prop_atoms.net_wm_moveresize_size_bottomright ||
980                 (Atom)e->xclient.data.l[2] ==
981                 prop_atoms.net_wm_moveresize_size_bottom ||
982                 (Atom)e->xclient.data.l[2] ==
983                 prop_atoms.net_wm_moveresize_size_bottomleft ||
984                 (Atom)e->xclient.data.l[2] ==
985                 prop_atoms.net_wm_moveresize_size_left ||
986                 (Atom)e->xclient.data.l[2] ==
987                 prop_atoms.net_wm_moveresize_move ||
988                 (Atom)e->xclient.data.l[2] ==
989                 prop_atoms.net_wm_moveresize_size_keyboard ||
990                 (Atom)e->xclient.data.l[2] ==
991                 prop_atoms.net_wm_moveresize_move_keyboard) {
992
993                 moveresize_start(client, e->xclient.data.l[0],
994                                  e->xclient.data.l[1], e->xclient.data.l[3],
995                                  e->xclient.data.l[2]);
996             }
997         } else if (msgtype == prop_atoms.net_moveresize_window) {
998             int oldg = client->gravity;
999             int tmpg, x, y, w, h;
1000
1001             if (e->xclient.data.l[0] & 0xff)
1002                 tmpg = e->xclient.data.l[0] & 0xff;
1003             else
1004                 tmpg = oldg;
1005
1006             if (e->xclient.data.l[0] & 1 << 8)
1007                 x = e->xclient.data.l[1];
1008             else
1009                 x = client->area.x;
1010             if (e->xclient.data.l[0] & 1 << 9)
1011                 y = e->xclient.data.l[2];
1012             else
1013                 y = client->area.y;
1014             if (e->xclient.data.l[0] & 1 << 10)
1015                 w = e->xclient.data.l[3];
1016             else
1017                 w = client->area.width;
1018             if (e->xclient.data.l[0] & 1 << 11)
1019                 h = e->xclient.data.l[4];
1020             else
1021                 h = client->area.height;
1022             client->gravity = tmpg;
1023
1024             {
1025                 int newx = x;
1026                 int newy = y;
1027                 int fw = w +
1028                     client->frame->size.left + client->frame->size.right;
1029                 int fh = h +
1030                     client->frame->size.top + client->frame->size.bottom;
1031                 client_find_onscreen(client, &newx, &newy, fw, fh,
1032                                      client_normal(client));
1033                 if (e->xclient.data.l[0] & 1 << 8)
1034                     x = newx;
1035                 if (e->xclient.data.l[0] & 1 << 9)
1036                     y = newy;
1037             }
1038                
1039             client_configure(client, OB_CORNER_TOPLEFT,
1040                              x, y, w, h, FALSE, TRUE);
1041
1042             client->gravity = oldg;
1043         }
1044         break;
1045     case PropertyNotify:
1046         /* validate cuz we query stuff off the client here */
1047         if (!client_validate(client)) break;
1048   
1049         /* compress changes to a single property into a single change */
1050         while (XCheckTypedWindowEvent(ob_display, client->window,
1051                                       e->type, &ce)) {
1052             Atom a, b;
1053
1054             /* XXX: it would be nice to compress ALL changes to a property,
1055                not just changes in a row without other props between. */
1056
1057             a = ce.xproperty.atom;
1058             b = e->xproperty.atom;
1059
1060             if (a == b)
1061                 continue;
1062             if ((a == prop_atoms.net_wm_name ||
1063                  a == prop_atoms.wm_name ||
1064                  a == prop_atoms.net_wm_icon_name ||
1065                  a == prop_atoms.wm_icon_name)
1066                 &&
1067                 (b == prop_atoms.net_wm_name ||
1068                  b == prop_atoms.wm_name ||
1069                  b == prop_atoms.net_wm_icon_name ||
1070                  b == prop_atoms.wm_icon_name)) {
1071                 continue;
1072             }
1073             if ((a == prop_atoms.net_wm_icon ||
1074                  a == prop_atoms.kwm_win_icon)
1075                 &&
1076                 (b == prop_atoms.net_wm_icon ||
1077                  b == prop_atoms.kwm_win_icon))
1078                 continue;
1079
1080             XPutBackEvent(ob_display, &ce);
1081             break;
1082         }
1083
1084         msgtype = e->xproperty.atom;
1085         if (msgtype == XA_WM_NORMAL_HINTS) {
1086             client_update_normal_hints(client);
1087             /* normal hints can make a window non-resizable */
1088             client_setup_decor_and_functions(client);
1089         } else if (msgtype == XA_WM_HINTS) {
1090             client_update_wmhints(client);
1091         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1092             client_update_transient_for(client);
1093             client_get_type(client);
1094             /* type may have changed, so update the layer */
1095             client_calc_layer(client);
1096             client_setup_decor_and_functions(client);
1097         } else if (msgtype == prop_atoms.net_wm_name ||
1098                    msgtype == prop_atoms.wm_name ||
1099                    msgtype == prop_atoms.net_wm_icon_name ||
1100                    msgtype == prop_atoms.wm_icon_name) {
1101             client_update_title(client);
1102         } else if (msgtype == prop_atoms.wm_class) {
1103             client_update_class(client);
1104         } else if (msgtype == prop_atoms.wm_protocols) {
1105             client_update_protocols(client);
1106             client_setup_decor_and_functions(client);
1107         }
1108         else if (msgtype == prop_atoms.net_wm_strut) {
1109             client_update_strut(client);
1110         }
1111         else if (msgtype == prop_atoms.net_wm_icon ||
1112                  msgtype == prop_atoms.kwm_win_icon) {
1113             client_update_icons(client);
1114         }
1115         else if (msgtype == prop_atoms.sm_client_id) {
1116             client_update_sm_client_id(client);
1117         }
1118     default:
1119         ;
1120 #ifdef SHAPE
1121         if (extensions_shape && e->type == extensions_shape_event_basep) {
1122             client->shaped = ((XShapeEvent*)e)->shaped;
1123             frame_adjust_shape(client->frame);
1124         }
1125 #endif
1126     }
1127 }
1128
1129 static void event_handle_dock(ObDock *s, XEvent *e)
1130 {
1131     switch (e->type) {
1132     case ButtonPress:
1133         stacking_raise(DOCK_AS_WINDOW(s));
1134         break;
1135     case EnterNotify:
1136         dock_hide(FALSE);
1137         break;
1138     case LeaveNotify:
1139         dock_hide(TRUE);
1140         break;
1141     }
1142 }
1143
1144 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1145 {
1146     switch (e->type) {
1147     case MotionNotify:
1148         dock_app_drag(app, &e->xmotion);
1149         break;
1150     case UnmapNotify:
1151         if (app->ignore_unmaps) {
1152             app->ignore_unmaps--;
1153             break;
1154         }
1155         dock_remove(app, TRUE);
1156         break;
1157     case DestroyNotify:
1158         dock_remove(app, FALSE);
1159         break;
1160     case ReparentNotify:
1161         dock_remove(app, FALSE);
1162         break;
1163     case ConfigureNotify:
1164         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1165         break;
1166     }
1167 }
1168
1169 ObMenuFrame* find_active_menu()
1170 {
1171     GList *it;
1172     ObMenuFrame *f;
1173
1174     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1175         f = it->data;
1176         if (f->selected)
1177             break;
1178     }
1179     return it ? it->data : NULL;
1180 }
1181
1182 static void event_handle_menu(XEvent *ev)
1183 {
1184     ObMenuFrame *f;
1185     ObMenuEntryFrame *e;
1186
1187     switch (ev->type) {
1188     case ButtonRelease:
1189         if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1190                                         ev->xbutton.y_root)))
1191             menu_entry_frame_execute(e, ev->xbutton.state);
1192         else if (menu_can_hide)
1193             menu_frame_hide_all();
1194         break;
1195     case MotionNotify:
1196         if ((f = menu_frame_under(ev->xmotion.x_root,
1197                                   ev->xmotion.y_root))) {
1198             menu_frame_move_on_screen(f);
1199             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1200                                             ev->xmotion.y_root)))
1201                 menu_frame_select(f, e);
1202         }
1203         {
1204             ObMenuFrame *a;
1205
1206             a = find_active_menu();
1207             if (a && a != f &&
1208                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1209             {
1210                 menu_frame_select(a, NULL);
1211             }
1212         }
1213         break;
1214     case KeyPress:
1215         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1216             menu_frame_hide_all();
1217         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1218             ObMenuFrame *f;
1219             if ((f = find_active_menu()))
1220                 menu_entry_frame_execute(f->selected, ev->xkey.state);
1221         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1222             ObMenuFrame *f;
1223             if ((f = find_active_menu()) && f->parent)
1224                 menu_frame_select(f, NULL);
1225         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1226             ObMenuFrame *f;
1227             if ((f = find_active_menu()) && f->child)
1228                 menu_frame_select_next(f->child);
1229         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1230             ObMenuFrame *f;
1231             if ((f = find_active_menu()))
1232                 menu_frame_select_previous(f);
1233         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1234             ObMenuFrame *f;
1235             if ((f = find_active_menu()))
1236                 menu_frame_select_next(f);
1237         }
1238         break;
1239     }
1240 }
1241
1242 static gboolean menu_hide_delay_func(gpointer data)
1243 {
1244     menu_can_hide = TRUE;
1245     return FALSE; /* no repeat */
1246 }
1247
1248 static gboolean focus_delay_func(gpointer data)
1249 {
1250     client_focus(focus_delay_client);
1251     return FALSE; /* no repeat */
1252 }
1253
1254 static void focus_delay_client_dest(gpointer data)
1255 {
1256     ObClient *c = data;
1257     if (c == focus_delay_client) {
1258         ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1259                                          focus_delay_client);
1260         focus_delay_client = NULL;
1261     }
1262 }
1263
1264 void event_ignore_enter_focus(guint num)
1265 {
1266     ignore_enter_focus += num;
1267 }