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