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