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