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