]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
i rewrote handling of focus events. this is pretty much based on blackbox's current...
[dana/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 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 #endif
456
457     event_set_lasttime(e);
458     event_hack_mods(e);
459     if (event_ignore(e, client)) {
460         if (ed)
461             ed->ignored = TRUE;
462         return;
463     } else if (ed)
464             ed->ignored = FALSE;
465
466     /* deal with it in the kernel */
467     if (group)
468         event_handle_group(group, e);
469     else if (client)
470         event_handle_client(client, e);
471     else if (dockapp)
472         event_handle_dockapp(dockapp, e);
473     else if (dock)
474         event_handle_dock(dock, e);
475     else if (window == RootWindow(ob_display, ob_screen))
476         event_handle_root(e);
477     else if (e->type == MapRequest)
478         client_manage(window);
479     else if (e->type == ConfigureRequest) {
480         /* unhandled configure requests must be used to configure the
481            window directly */
482         XWindowChanges xwc;
483
484         xwc.x = e->xconfigurerequest.x;
485         xwc.y = e->xconfigurerequest.y;
486         xwc.width = e->xconfigurerequest.width;
487         xwc.height = e->xconfigurerequest.height;
488         xwc.border_width = e->xconfigurerequest.border_width;
489         xwc.sibling = e->xconfigurerequest.above;
490         xwc.stack_mode = e->xconfigurerequest.detail;
491        
492         /* we are not to be held responsible if someone sends us an
493            invalid request! */
494         xerror_set_ignore(TRUE);
495         XConfigureWindow(ob_display, window,
496                          e->xconfigurerequest.value_mask, &xwc);
497         xerror_set_ignore(FALSE);
498     }
499
500     /* user input (action-bound) events */
501     if (e->type == ButtonPress || e->type == ButtonRelease ||
502         e->type == MotionNotify || e->type == KeyPress ||
503         e->type == KeyRelease)
504     {
505         if (menu_frame_visible)
506             event_handle_menu(e);
507         else {
508             if (!keyboard_process_interactive_grab(e, &client)) {
509                 if (moveresize_in_progress) {
510                     moveresize_event(e);
511
512                     /* make further actions work on the client being
513                        moved/resized */
514                     client = moveresize_client;
515                 }
516
517                 menu_can_hide = FALSE;
518                 ob_main_loop_timeout_add(ob_main_loop,
519                                          config_menu_hide_delay * 1000,
520                                          menu_hide_delay_func,
521                                          NULL, NULL);
522
523                 if (e->type == ButtonPress || e->type == ButtonRelease ||
524                     e->type == MotionNotify)
525                     mouse_event(client, e);
526                 else if (e->type == KeyPress)
527                     keyboard_event((focus_cycle_target ? focus_cycle_target :
528                                     (focus_hilite ? focus_hilite : client)),
529                                    e);
530             }
531         }
532     }
533 }
534
535 static void event_handle_root(XEvent *e)
536 {
537     Atom msgtype;
538      
539     switch(e->type) {
540     case SelectionClear:
541         ob_debug("Another WM has requested to replace us. Exiting.\n");
542         ob_exit_replace();
543         break;
544
545     case ClientMessage:
546         if (e->xclient.format != 32) break;
547
548         msgtype = e->xclient.message_type;
549         if (msgtype == prop_atoms.net_current_desktop) {
550             guint d = e->xclient.data.l[0];
551             if (d < screen_num_desktops)
552                 screen_set_desktop(d);
553         } else if (msgtype == prop_atoms.net_number_of_desktops) {
554             guint d = e->xclient.data.l[0];
555             if (d > 0)
556                 screen_set_num_desktops(d);
557         } else if (msgtype == prop_atoms.net_showing_desktop) {
558             screen_show_desktop(e->xclient.data.l[0] != 0);
559         } else if (msgtype == prop_atoms.ob_control) {
560             if (e->xclient.data.l[0] == 1)
561                 ob_reconfigure();
562             else if (e->xclient.data.l[0] == 2)
563                 ob_restart();
564         }
565         break;
566     case PropertyNotify:
567         if (e->xproperty.atom == prop_atoms.net_desktop_names)
568             screen_update_desktop_names();
569         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
570             screen_update_layout();
571         break;
572     case ConfigureNotify:
573 #ifdef XRANDR
574         XRRUpdateConfiguration(e);
575 #endif
576         screen_resize();
577         break;
578     default:
579         ;
580     }
581 }
582
583 static void event_handle_group(ObGroup *group, XEvent *e)
584 {
585     GSList *it;
586
587     g_assert(e->type == PropertyNotify);
588
589     for (it = group->members; it; it = g_slist_next(it))
590         event_handle_client(it->data, e);
591 }
592
593 void event_enter_client(ObClient *client)
594 {
595     g_assert(config_focus_follow);
596
597     if (client_normal(client) && client_can_focus(client)) {
598         if (config_focus_delay) {
599             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
600             ob_main_loop_timeout_add(ob_main_loop,
601                                      config_focus_delay,
602                                      focus_delay_func,
603                                      client, NULL);
604         } else
605             focus_delay_func(client);
606     }
607 }
608
609 static void event_handle_client(ObClient *client, XEvent *e)
610 {
611     XEvent ce;
612     Atom msgtype;
613     gint i=0;
614     ObFrameContext con;
615      
616     switch (e->type) {
617     case VisibilityNotify:
618         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
619         break;
620     case ButtonPress:
621     case ButtonRelease:
622         /* Wheel buttons don't draw because they are an instant click, so it
623            is a waste of resources to go drawing it. */
624         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
625             con = frame_context(client, e->xbutton.window);
626             con = mouse_button_frame_context(con, e->xbutton.button);
627             switch (con) {
628             case OB_FRAME_CONTEXT_MAXIMIZE:
629                 client->frame->max_press = (e->type == ButtonPress);
630                 framerender_frame(client->frame);
631                 break;
632             case OB_FRAME_CONTEXT_CLOSE:
633                 client->frame->close_press = (e->type == ButtonPress);
634                 framerender_frame(client->frame);
635                 break;
636             case OB_FRAME_CONTEXT_ICONIFY:
637                 client->frame->iconify_press = (e->type == ButtonPress);
638                 framerender_frame(client->frame);
639                 break;
640             case OB_FRAME_CONTEXT_ALLDESKTOPS:
641                 client->frame->desk_press = (e->type == ButtonPress);
642                 framerender_frame(client->frame);
643                 break; 
644             case OB_FRAME_CONTEXT_SHADE:
645                 client->frame->shade_press = (e->type == ButtonPress);
646                 framerender_frame(client->frame);
647                 break;
648             default:
649                 /* nothing changes with clicks for any other contexts */
650                 break;
651             }
652         }
653         break;
654     case FocusIn:
655         if (client != focus_client) {
656             focus_set_client(client);
657             frame_adjust_focus(client->frame, TRUE);
658             client_calc_layer(client);
659         }
660         break;
661     case FocusOut:
662         /* Look for the followup FocusIn */
663         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
664             /* There is no FocusIn, move focus where we can still hear events*/
665             focus_set_client(NULL);
666         } else if (ce.xany.window == e->xany.window) {
667             /* If focus didn't actually move anywhere, there is nothing to do*/
668             break;
669         } else {
670             /* Focus did move, so process the FocusIn event */
671             ObEventData ed;
672             event_process(&ce, &ed);
673             if (ed.ignored) {
674                 /* The FocusIn was ignored, this means it was on a window
675                    that isn't a client? How did this happen? */
676                 g_assert_not_reached();
677             }
678         }
679
680         /* This client is no longer focused, so show that */
681         frame_adjust_focus(client->frame, FALSE);
682         client_calc_layer(client);
683         break;
684     case LeaveNotify:
685         con = frame_context(client, e->xcrossing.window);
686         switch (con) {
687         case OB_FRAME_CONTEXT_MAXIMIZE:
688             client->frame->max_hover = FALSE;
689             frame_adjust_state(client->frame);
690             break;
691         case OB_FRAME_CONTEXT_ALLDESKTOPS:
692             client->frame->desk_hover = FALSE;
693             frame_adjust_state(client->frame);
694             break;
695         case OB_FRAME_CONTEXT_SHADE:
696             client->frame->shade_hover = FALSE;
697             frame_adjust_state(client->frame);
698             break;
699         case OB_FRAME_CONTEXT_ICONIFY:
700             client->frame->iconify_hover = FALSE;
701             frame_adjust_state(client->frame);
702             break;
703         case OB_FRAME_CONTEXT_CLOSE:
704             client->frame->close_hover = FALSE;
705             frame_adjust_state(client->frame);
706             break;
707         case OB_FRAME_CONTEXT_FRAME:
708             if (config_focus_follow && config_focus_delay)
709                 ob_main_loop_timeout_remove_data(ob_main_loop,
710                                                  focus_delay_func,
711                                                  client, TRUE);
712             break;
713         default:
714             break;
715         }
716         break;
717     case EnterNotify:
718     {
719         gboolean nofocus = FALSE;
720
721         if (ignore_enter_focus) {
722             ignore_enter_focus--;
723             nofocus = TRUE;
724         }
725
726         con = frame_context(client, e->xcrossing.window);
727         switch (con) {
728         case OB_FRAME_CONTEXT_MAXIMIZE:
729             client->frame->max_hover = TRUE;
730             frame_adjust_state(client->frame);
731             break;
732         case OB_FRAME_CONTEXT_ALLDESKTOPS:
733             client->frame->desk_hover = TRUE;
734             frame_adjust_state(client->frame);
735             break;
736         case OB_FRAME_CONTEXT_SHADE:
737             client->frame->shade_hover = TRUE;
738             frame_adjust_state(client->frame);
739             break;
740         case OB_FRAME_CONTEXT_ICONIFY:
741             client->frame->iconify_hover = TRUE;
742             frame_adjust_state(client->frame);
743             break;
744         case OB_FRAME_CONTEXT_CLOSE:
745             client->frame->close_hover = TRUE;
746             frame_adjust_state(client->frame);
747             break;
748         case OB_FRAME_CONTEXT_FRAME:
749             if (e->xcrossing.mode == NotifyGrab ||
750                 e->xcrossing.mode == NotifyUngrab)
751             {
752 #ifdef DEBUG_FOCUS
753                 ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
754                          (e->type == EnterNotify ? "Enter" : "Leave"),
755                          e->xcrossing.mode,
756                          e->xcrossing.detail, client?client->window:0);
757 #endif
758             } else {
759 #ifdef DEBUG_FOCUS
760                 ob_debug("%sNotify mode %d detail %d on %lx, "
761                          "focusing window: %d\n",
762                          (e->type == EnterNotify ? "Enter" : "Leave"),
763                          e->xcrossing.mode,
764                          e->xcrossing.detail, (client?client->window:0),
765                          !nofocus);
766 #endif
767                 if (!nofocus && config_focus_follow)
768                     event_enter_client(client);
769             }
770             break;
771         default:
772             break;
773         }
774         break;
775     }
776     case ConfigureRequest:
777         /* compress these */
778         while (XCheckTypedWindowEvent(ob_display, client->window,
779                                       ConfigureRequest, &ce)) {
780             ++i;
781             /* XXX if this causes bad things.. we can compress config req's
782                with the same mask. */
783             e->xconfigurerequest.value_mask |=
784                 ce.xconfigurerequest.value_mask;
785             if (ce.xconfigurerequest.value_mask & CWX)
786                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
787             if (ce.xconfigurerequest.value_mask & CWY)
788                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
789             if (ce.xconfigurerequest.value_mask & CWWidth)
790                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
791             if (ce.xconfigurerequest.value_mask & CWHeight)
792                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
793             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
794                 e->xconfigurerequest.border_width =
795                     ce.xconfigurerequest.border_width;
796             if (ce.xconfigurerequest.value_mask & CWStackMode)
797                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
798         }
799
800         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
801         if (client->iconic || client->shaded) return;
802
803         /* resize, then move, as specified in the EWMH section 7.7 */
804         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
805                                                CWX | CWY |
806                                                CWBorderWidth)) {
807             gint x, y, w, h;
808             ObCorner corner;
809
810             if (e->xconfigurerequest.value_mask & CWBorderWidth)
811                 client->border_width = e->xconfigurerequest.border_width;
812
813             x = (e->xconfigurerequest.value_mask & CWX) ?
814                 e->xconfigurerequest.x : client->area.x;
815             y = (e->xconfigurerequest.value_mask & CWY) ?
816                 e->xconfigurerequest.y : client->area.y;
817             w = (e->xconfigurerequest.value_mask & CWWidth) ?
818                 e->xconfigurerequest.width : client->area.width;
819             h = (e->xconfigurerequest.value_mask & CWHeight) ?
820                 e->xconfigurerequest.height : client->area.height;
821
822             {
823                 gint newx = x;
824                 gint newy = y;
825                 gint fw = w +
826                      client->frame->size.left + client->frame->size.right;
827                 gint fh = h +
828                      client->frame->size.top + client->frame->size.bottom;
829                 /* make this rude for size-only changes but not for position
830                    changes.. */
831                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
832                                    (e->xconfigurerequest.value_mask & CWY));
833
834                 client_find_onscreen(client, &newx, &newy, fw, fh,
835                                      !moving);
836                 if (e->xconfigurerequest.value_mask & CWX)
837                     x = newx;
838                 if (e->xconfigurerequest.value_mask & CWY)
839                     y = newy;
840             }
841
842             switch (client->gravity) {
843             case NorthEastGravity:
844             case EastGravity:
845                 corner = OB_CORNER_TOPRIGHT;
846                 break;
847             case SouthWestGravity:
848             case SouthGravity:
849                 corner = OB_CORNER_BOTTOMLEFT;
850                 break;
851             case SouthEastGravity:
852                 corner = OB_CORNER_BOTTOMRIGHT;
853                 break;
854             default:     /* NorthWest, Static, etc */
855                 corner = OB_CORNER_TOPLEFT;
856             }
857
858             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
859                                   TRUE);
860         }
861
862         if (e->xconfigurerequest.value_mask & CWStackMode) {
863             switch (e->xconfigurerequest.detail) {
864             case Below:
865             case BottomIf:
866                 /* Apps are so rude. And this is totally disconnected from
867                    activation/focus. Bleh. */
868                 /*client_lower(client);*/
869                 break;
870
871             case Above:
872             case TopIf:
873             default:
874                 /* Apps are so rude. And this is totally disconnected from
875                    activation/focus. Bleh. */
876                 /*client_raise(client);*/
877                 break;
878             }
879         }
880         break;
881     case UnmapNotify:
882         if (client->ignore_unmaps) {
883             client->ignore_unmaps--;
884             break;
885         }
886         client_unmanage(client);
887         break;
888     case DestroyNotify:
889         client_unmanage(client);
890         break;
891     case ReparentNotify:
892         /* this is when the client is first taken captive in the frame */
893         if (e->xreparent.parent == client->frame->plate) break;
894
895         /*
896           This event is quite rare and is usually handled in unmapHandler.
897           However, if the window is unmapped when the reparent event occurs,
898           the window manager never sees it because an unmap event is not sent
899           to an already unmapped window.
900         */
901
902         /* we don't want the reparent event, put it back on the stack for the
903            X server to deal with after we unmanage the window */
904         XPutBackEvent(ob_display, e);
905      
906         client_unmanage(client);
907         break;
908     case MapRequest:
909         ob_debug("MapRequest for 0x%lx\n", client->window);
910         if (!client->iconic) break; /* this normally doesn't happen, but if it
911                                        does, we don't want it!
912                                        it can happen now when the window is on
913                                        another desktop, but we still don't
914                                        want it! */
915         client_activate(client, FALSE, TRUE, CurrentTime);
916         break;
917     case ClientMessage:
918         /* validate cuz we query stuff off the client here */
919         if (!client_validate(client)) break;
920
921         if (e->xclient.format != 32) return;
922
923         msgtype = e->xclient.message_type;
924         if (msgtype == prop_atoms.wm_change_state) {
925             /* compress changes into a single change */
926             while (XCheckTypedWindowEvent(ob_display, client->window,
927                                           e->type, &ce)) {
928                 /* XXX: it would be nice to compress ALL messages of a
929                    type, not just messages in a row without other
930                    message types between. */
931                 if (ce.xclient.message_type != msgtype) {
932                     XPutBackEvent(ob_display, &ce);
933                     break;
934                 }
935                 e->xclient = ce.xclient;
936             }
937             client_set_wm_state(client, e->xclient.data.l[0]);
938         } else if (msgtype == prop_atoms.net_wm_desktop) {
939             /* compress changes into a single change */
940             while (XCheckTypedWindowEvent(ob_display, client->window,
941                                           e->type, &ce)) {
942                 /* XXX: it would be nice to compress ALL messages of a
943                    type, not just messages in a row without other
944                    message types between. */
945                 if (ce.xclient.message_type != msgtype) {
946                     XPutBackEvent(ob_display, &ce);
947                     break;
948                 }
949                 e->xclient = ce.xclient;
950             }
951             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
952                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
953                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
954                                    FALSE);
955         } else if (msgtype == prop_atoms.net_wm_state) {
956             /* can't compress these */
957             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
958                      (e->xclient.data.l[0] == 0 ? "Remove" :
959                       e->xclient.data.l[0] == 1 ? "Add" :
960                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
961                      e->xclient.data.l[1], e->xclient.data.l[2],
962                      client->window);
963             client_set_state(client, e->xclient.data.l[0],
964                              e->xclient.data.l[1], e->xclient.data.l[2]);
965         } else if (msgtype == prop_atoms.net_close_window) {
966             ob_debug("net_close_window for 0x%lx\n", client->window);
967             client_close(client);
968         } else if (msgtype == prop_atoms.net_active_window) {
969             ob_debug("net_active_window for 0x%lx source=%s\n",
970                      client->window,
971                      (e->xclient.data.l[0] == 0 ? "unknown" :
972                       (e->xclient.data.l[0] == 1 ? "application" :
973                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
974             /* XXX make use of data.l[1] and [2] ! */
975             client_activate(client, FALSE,
976                             (e->xclient.data.l[0] == 0 ||
977                              e->xclient.data.l[0] == 2),
978                             e->xclient.data.l[1]);
979         } else if (msgtype == prop_atoms.net_wm_moveresize) {
980             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
981                      client->window, e->xclient.data.l[2]);
982             if ((Atom)e->xclient.data.l[2] ==
983                 prop_atoms.net_wm_moveresize_size_topleft ||
984                 (Atom)e->xclient.data.l[2] ==
985                 prop_atoms.net_wm_moveresize_size_top ||
986                 (Atom)e->xclient.data.l[2] ==
987                 prop_atoms.net_wm_moveresize_size_topright ||
988                 (Atom)e->xclient.data.l[2] ==
989                 prop_atoms.net_wm_moveresize_size_right ||
990                 (Atom)e->xclient.data.l[2] ==
991                 prop_atoms.net_wm_moveresize_size_right ||
992                 (Atom)e->xclient.data.l[2] ==
993                 prop_atoms.net_wm_moveresize_size_bottomright ||
994                 (Atom)e->xclient.data.l[2] ==
995                 prop_atoms.net_wm_moveresize_size_bottom ||
996                 (Atom)e->xclient.data.l[2] ==
997                 prop_atoms.net_wm_moveresize_size_bottomleft ||
998                 (Atom)e->xclient.data.l[2] ==
999                 prop_atoms.net_wm_moveresize_size_left ||
1000                 (Atom)e->xclient.data.l[2] ==
1001                 prop_atoms.net_wm_moveresize_move ||
1002                 (Atom)e->xclient.data.l[2] ==
1003                 prop_atoms.net_wm_moveresize_size_keyboard ||
1004                 (Atom)e->xclient.data.l[2] ==
1005                 prop_atoms.net_wm_moveresize_move_keyboard) {
1006
1007                 moveresize_start(client, e->xclient.data.l[0],
1008                                  e->xclient.data.l[1], e->xclient.data.l[3],
1009                                  e->xclient.data.l[2]);
1010             }
1011             else if ((Atom)e->xclient.data.l[2] ==
1012                      prop_atoms.net_wm_moveresize_cancel)
1013                 moveresize_end(TRUE);
1014         } else if (msgtype == prop_atoms.net_moveresize_window) {
1015             gint oldg = client->gravity;
1016             gint tmpg, x, y, w, h;
1017
1018             if (e->xclient.data.l[0] & 0xff)
1019                 tmpg = e->xclient.data.l[0] & 0xff;
1020             else
1021                 tmpg = oldg;
1022
1023             if (e->xclient.data.l[0] & 1 << 8)
1024                 x = e->xclient.data.l[1];
1025             else
1026                 x = client->area.x;
1027             if (e->xclient.data.l[0] & 1 << 9)
1028                 y = e->xclient.data.l[2];
1029             else
1030                 y = client->area.y;
1031             if (e->xclient.data.l[0] & 1 << 10)
1032                 w = e->xclient.data.l[3];
1033             else
1034                 w = client->area.width;
1035             if (e->xclient.data.l[0] & 1 << 11)
1036                 h = e->xclient.data.l[4];
1037             else
1038                 h = client->area.height;
1039             client->gravity = tmpg;
1040
1041             {
1042                 gint newx = x;
1043                 gint newy = y;
1044                 gint fw = w +
1045                      client->frame->size.left + client->frame->size.right;
1046                 gint fh = h +
1047                      client->frame->size.top + client->frame->size.bottom;
1048                 client_find_onscreen(client, &newx, &newy, fw, fh,
1049                                      client_normal(client));
1050                 if (e->xclient.data.l[0] & 1 << 8)
1051                     x = newx;
1052                 if (e->xclient.data.l[0] & 1 << 9)
1053                     y = newy;
1054             }
1055
1056             client_configure(client, OB_CORNER_TOPLEFT,
1057                              x, y, w, h, FALSE, TRUE);
1058
1059             client->gravity = oldg;
1060         }
1061         break;
1062     case PropertyNotify:
1063         /* validate cuz we query stuff off the client here */
1064         if (!client_validate(client)) break;
1065   
1066         /* compress changes to a single property into a single change */
1067         while (XCheckTypedWindowEvent(ob_display, client->window,
1068                                       e->type, &ce)) {
1069             Atom a, b;
1070
1071             /* XXX: it would be nice to compress ALL changes to a property,
1072                not just changes in a row without other props between. */
1073
1074             a = ce.xproperty.atom;
1075             b = e->xproperty.atom;
1076
1077             if (a == b)
1078                 continue;
1079             if ((a == prop_atoms.net_wm_name ||
1080                  a == prop_atoms.wm_name ||
1081                  a == prop_atoms.net_wm_icon_name ||
1082                  a == prop_atoms.wm_icon_name)
1083                 &&
1084                 (b == prop_atoms.net_wm_name ||
1085                  b == prop_atoms.wm_name ||
1086                  b == prop_atoms.net_wm_icon_name ||
1087                  b == prop_atoms.wm_icon_name)) {
1088                 continue;
1089             }
1090             if (a == prop_atoms.net_wm_icon &&
1091                 b == prop_atoms.net_wm_icon)
1092                 continue;
1093
1094             XPutBackEvent(ob_display, &ce);
1095             break;
1096         }
1097
1098         msgtype = e->xproperty.atom;
1099         if (msgtype == XA_WM_NORMAL_HINTS) {
1100             client_update_normal_hints(client);
1101             /* normal hints can make a window non-resizable */
1102             client_setup_decor_and_functions(client);
1103         } else if (msgtype == XA_WM_HINTS) {
1104             client_update_wmhints(client);
1105         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1106             client_update_transient_for(client);
1107             client_get_type(client);
1108             /* type may have changed, so update the layer */
1109             client_calc_layer(client);
1110             client_setup_decor_and_functions(client);
1111         } else if (msgtype == prop_atoms.net_wm_name ||
1112                    msgtype == prop_atoms.wm_name ||
1113                    msgtype == prop_atoms.net_wm_icon_name ||
1114                    msgtype == prop_atoms.wm_icon_name) {
1115             client_update_title(client);
1116         } else if (msgtype == prop_atoms.wm_class) {
1117             client_update_class(client);
1118         } else if (msgtype == prop_atoms.wm_protocols) {
1119             client_update_protocols(client);
1120             client_setup_decor_and_functions(client);
1121         }
1122         else if (msgtype == prop_atoms.net_wm_strut) {
1123             client_update_strut(client);
1124         }
1125         else if (msgtype == prop_atoms.net_wm_icon) {
1126             client_update_icons(client);
1127         }
1128         else if (msgtype == prop_atoms.net_wm_user_time) {
1129             client_update_user_time(client, TRUE);
1130         }
1131         else if (msgtype == prop_atoms.sm_client_id) {
1132             client_update_sm_client_id(client);
1133         }
1134     default:
1135         ;
1136 #ifdef SHAPE
1137         if (extensions_shape && e->type == extensions_shape_event_basep) {
1138             client->shaped = ((XShapeEvent*)e)->shaped;
1139             frame_adjust_shape(client->frame);
1140         }
1141 #endif
1142     }
1143 }
1144
1145 static void event_handle_dock(ObDock *s, XEvent *e)
1146 {
1147     switch (e->type) {
1148     case ButtonPress:
1149         if (e->xbutton.button == 1)
1150             stacking_raise(DOCK_AS_WINDOW(s));
1151         else if (e->xbutton.button == 2)
1152             stacking_lower(DOCK_AS_WINDOW(s));
1153         break;
1154     case EnterNotify:
1155         dock_hide(FALSE);
1156         break;
1157     case LeaveNotify:
1158         dock_hide(TRUE);
1159         break;
1160     }
1161 }
1162
1163 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1164 {
1165     switch (e->type) {
1166     case MotionNotify:
1167         dock_app_drag(app, &e->xmotion);
1168         break;
1169     case UnmapNotify:
1170         if (app->ignore_unmaps) {
1171             app->ignore_unmaps--;
1172             break;
1173         }
1174         dock_remove(app, TRUE);
1175         break;
1176     case DestroyNotify:
1177         dock_remove(app, FALSE);
1178         break;
1179     case ReparentNotify:
1180         dock_remove(app, FALSE);
1181         break;
1182     case ConfigureNotify:
1183         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1184         break;
1185     }
1186 }
1187
1188 ObMenuFrame* find_active_menu()
1189 {
1190     GList *it;
1191     ObMenuFrame *ret = NULL;
1192
1193     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1194         ret = it->data;
1195         if (ret->selected)
1196             break;
1197         ret = NULL;
1198     }
1199     return ret;
1200 }
1201
1202 ObMenuFrame* find_active_or_last_menu()
1203 {
1204     ObMenuFrame *ret = NULL;
1205
1206     ret = find_active_menu();
1207     if (!ret && menu_frame_visible)
1208         ret = menu_frame_visible->data;
1209     return ret;
1210 }
1211
1212 static void event_handle_menu(XEvent *ev)
1213 {
1214     ObMenuFrame *f;
1215     ObMenuEntryFrame *e;
1216
1217     switch (ev->type) {
1218     case ButtonRelease:
1219         if (menu_can_hide) {
1220             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1221                                             ev->xbutton.y_root)))
1222                 menu_entry_frame_execute(e, ev->xbutton.state,
1223                                          ev->xbutton.time);
1224             else
1225                 menu_frame_hide_all();
1226         }
1227         break;
1228     case MotionNotify:
1229         if ((f = menu_frame_under(ev->xmotion.x_root,
1230                                   ev->xmotion.y_root))) {
1231             menu_frame_move_on_screen(f);
1232             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1233                                             ev->xmotion.y_root)))
1234                 menu_frame_select(f, e);
1235         }
1236         {
1237             ObMenuFrame *a;
1238
1239             a = find_active_menu();
1240             if (a && a != f &&
1241                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1242             {
1243                 menu_frame_select(a, NULL);
1244             }
1245         }
1246         break;
1247     case KeyPress:
1248         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1249             menu_frame_hide_all();
1250         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1251             ObMenuFrame *f;
1252             if ((f = find_active_menu()))
1253                 menu_entry_frame_execute(f->selected, ev->xkey.state,
1254                                          ev->xkey.time);
1255         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1256             ObMenuFrame *f;
1257             if ((f = find_active_or_last_menu()) && f->parent)
1258                 menu_frame_select(f, NULL);
1259         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1260             ObMenuFrame *f;
1261             if ((f = find_active_or_last_menu()) && f->child)
1262                 menu_frame_select_next(f->child);
1263         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1264             ObMenuFrame *f;
1265             if ((f = find_active_or_last_menu()))
1266                 menu_frame_select_previous(f);
1267         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1268             ObMenuFrame *f;
1269             if ((f = find_active_or_last_menu()))
1270                 menu_frame_select_next(f);
1271         }
1272         break;
1273     }
1274 }
1275
1276 static gboolean menu_hide_delay_func(gpointer data)
1277 {
1278     menu_can_hide = TRUE;
1279     return FALSE; /* no repeat */
1280 }
1281
1282 static gboolean focus_delay_func(gpointer data)
1283 {
1284     ObClient *c = data;
1285
1286     if (focus_client != c) {
1287         client_focus(c);
1288         if (config_focus_raise)
1289             client_raise(c);
1290     }
1291     return FALSE; /* no repeat */
1292 }
1293
1294 static void focus_delay_client_dest(ObClient *client, gpointer data)
1295 {
1296     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1297                                      client, TRUE);
1298 }
1299
1300 static void event_client_dest(ObClient *client, gpointer data)
1301 {
1302     if (client == focus_hilite)
1303         focus_hilite = NULL;
1304 }
1305
1306 void event_halt_focus_delay()
1307 {
1308     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1309 }
1310
1311 void event_ignore_queued_enters()
1312 {
1313     GSList *saved = NULL, *it;
1314     XEvent *e;
1315                 
1316     XSync(ob_display, FALSE);
1317
1318     /* count the events */
1319     while (TRUE) {
1320         e = g_new(XEvent, 1);
1321         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1322             ObWindow *win;
1323             
1324             win = g_hash_table_lookup(window_map, &e->xany.window);
1325             if (win && WINDOW_IS_CLIENT(win))
1326                 ++ignore_enter_focus;
1327             
1328             saved = g_slist_append(saved, e);
1329         } else {
1330             g_free(e);
1331             break;
1332         }
1333     }
1334     /* put the events back */
1335     for (it = saved; it; it = g_slist_next(it)) {
1336         XPutBackEvent(ob_display, it->data);
1337         g_free(it->data);
1338     }
1339     g_slist_free(saved);
1340 }