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