]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
wow... this is a big commit...
[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 (e->xclient.data.l[0] == 1)
601                 ob_reconfigure();
602             else if (e->xclient.data.l[0] == 2)
603                 ob_restart();
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                 /* make this rude for size-only changes but not for position
862                    changes.. */
863                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
864                                    (e->xconfigurerequest.value_mask & CWY));
865
866                 client_find_onscreen(client, &newx, &newy, fw, fh,
867                                      !moving);
868                 if (e->xconfigurerequest.value_mask & CWX)
869                     x = newx;
870                 if (e->xconfigurerequest.value_mask & CWY)
871                     y = newy;
872             }
873
874             switch (client->gravity) {
875             case NorthEastGravity:
876             case EastGravity:
877                 corner = OB_CORNER_TOPRIGHT;
878                 break;
879             case SouthWestGravity:
880             case SouthGravity:
881                 corner = OB_CORNER_BOTTOMLEFT;
882                 break;
883             case SouthEastGravity:
884                 corner = OB_CORNER_BOTTOMRIGHT;
885                 break;
886             default:     /* NorthWest, Static, etc */
887                 corner = OB_CORNER_TOPLEFT;
888             }
889
890             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
891                                   TRUE);
892         }
893
894         if (e->xconfigurerequest.value_mask & CWStackMode) {
895             switch (e->xconfigurerequest.detail) {
896             case Below:
897             case BottomIf:
898                 /* Apps are so rude. And this is totally disconnected from
899                    activation/focus. Bleh. */
900                 /*client_lower(client);*/
901                 break;
902
903             case Above:
904             case TopIf:
905             default:
906                 /* Apps are so rude. And this is totally disconnected from
907                    activation/focus. Bleh. */
908                 /*client_raise(client);*/
909                 break;
910             }
911         }
912         break;
913     case UnmapNotify:
914         if (client->ignore_unmaps) {
915             client->ignore_unmaps--;
916             break;
917         }
918         client_unmanage(client);
919         break;
920     case DestroyNotify:
921         client_unmanage(client);
922         break;
923     case ReparentNotify:
924         /* this is when the client is first taken captive in the frame */
925         if (e->xreparent.parent == client->frame->plate) break;
926
927         /*
928           This event is quite rare and is usually handled in unmapHandler.
929           However, if the window is unmapped when the reparent event occurs,
930           the window manager never sees it because an unmap event is not sent
931           to an already unmapped window.
932         */
933
934         /* we don't want the reparent event, put it back on the stack for the
935            X server to deal with after we unmanage the window */
936         XPutBackEvent(ob_display, e);
937      
938         client_unmanage(client);
939         break;
940     case MapRequest:
941         ob_debug("MapRequest for 0x%lx\n", client->window);
942         if (!client->iconic) break; /* this normally doesn't happen, but if it
943                                        does, we don't want it!
944                                        it can happen now when the window is on
945                                        another desktop, but we still don't
946                                        want it! */
947         client_activate(client, FALSE, TRUE, CurrentTime);
948         break;
949     case ClientMessage:
950         /* validate cuz we query stuff off the client here */
951         if (!client_validate(client)) break;
952
953         if (e->xclient.format != 32) return;
954
955         msgtype = e->xclient.message_type;
956         if (msgtype == prop_atoms.wm_change_state) {
957             /* compress changes into a single change */
958             while (XCheckTypedWindowEvent(ob_display, client->window,
959                                           e->type, &ce)) {
960                 /* XXX: it would be nice to compress ALL messages of a
961                    type, not just messages in a row without other
962                    message types between. */
963                 if (ce.xclient.message_type != msgtype) {
964                     XPutBackEvent(ob_display, &ce);
965                     break;
966                 }
967                 e->xclient = ce.xclient;
968             }
969             client_set_wm_state(client, e->xclient.data.l[0]);
970         } else if (msgtype == prop_atoms.net_wm_desktop) {
971             /* compress changes into a single change */
972             while (XCheckTypedWindowEvent(ob_display, client->window,
973                                           e->type, &ce)) {
974                 /* XXX: it would be nice to compress ALL messages of a
975                    type, not just messages in a row without other
976                    message types between. */
977                 if (ce.xclient.message_type != msgtype) {
978                     XPutBackEvent(ob_display, &ce);
979                     break;
980                 }
981                 e->xclient = ce.xclient;
982             }
983             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
984                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
985                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
986                                    FALSE);
987         } else if (msgtype == prop_atoms.net_wm_state) {
988             /* can't compress these */
989             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
990                      (e->xclient.data.l[0] == 0 ? "Remove" :
991                       e->xclient.data.l[0] == 1 ? "Add" :
992                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
993                      e->xclient.data.l[1], e->xclient.data.l[2],
994                      client->window);
995             client_set_state(client, e->xclient.data.l[0],
996                              e->xclient.data.l[1], e->xclient.data.l[2]);
997         } else if (msgtype == prop_atoms.net_close_window) {
998             ob_debug("net_close_window for 0x%lx\n", client->window);
999             client_close(client);
1000         } else if (msgtype == prop_atoms.net_active_window) {
1001             ob_debug("net_active_window for 0x%lx source=%s\n",
1002                      client->window,
1003                      (e->xclient.data.l[0] == 0 ? "unknown" :
1004                       (e->xclient.data.l[0] == 1 ? "application" :
1005                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1006             /* XXX make use of data.l[1] and [2] ! */
1007             client_activate(client, FALSE,
1008                             (e->xclient.data.l[0] == 0 ||
1009                              e->xclient.data.l[0] == 2),
1010                             e->xclient.data.l[1]);
1011         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1012             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
1013             if ((Atom)e->xclient.data.l[2] ==
1014                 prop_atoms.net_wm_moveresize_size_topleft ||
1015                 (Atom)e->xclient.data.l[2] ==
1016                 prop_atoms.net_wm_moveresize_size_top ||
1017                 (Atom)e->xclient.data.l[2] ==
1018                 prop_atoms.net_wm_moveresize_size_topright ||
1019                 (Atom)e->xclient.data.l[2] ==
1020                 prop_atoms.net_wm_moveresize_size_right ||
1021                 (Atom)e->xclient.data.l[2] ==
1022                 prop_atoms.net_wm_moveresize_size_right ||
1023                 (Atom)e->xclient.data.l[2] ==
1024                 prop_atoms.net_wm_moveresize_size_bottomright ||
1025                 (Atom)e->xclient.data.l[2] ==
1026                 prop_atoms.net_wm_moveresize_size_bottom ||
1027                 (Atom)e->xclient.data.l[2] ==
1028                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1029                 (Atom)e->xclient.data.l[2] ==
1030                 prop_atoms.net_wm_moveresize_size_left ||
1031                 (Atom)e->xclient.data.l[2] ==
1032                 prop_atoms.net_wm_moveresize_move ||
1033                 (Atom)e->xclient.data.l[2] ==
1034                 prop_atoms.net_wm_moveresize_size_keyboard ||
1035                 (Atom)e->xclient.data.l[2] ==
1036                 prop_atoms.net_wm_moveresize_move_keyboard) {
1037
1038                 moveresize_start(client, e->xclient.data.l[0],
1039                                  e->xclient.data.l[1], e->xclient.data.l[3],
1040                                  e->xclient.data.l[2]);
1041             }
1042         } else if (msgtype == prop_atoms.net_moveresize_window) {
1043             gint oldg = client->gravity;
1044             gint tmpg, x, y, w, h;
1045
1046             if (e->xclient.data.l[0] & 0xff)
1047                 tmpg = e->xclient.data.l[0] & 0xff;
1048             else
1049                 tmpg = oldg;
1050
1051             if (e->xclient.data.l[0] & 1 << 8)
1052                 x = e->xclient.data.l[1];
1053             else
1054                 x = client->area.x;
1055             if (e->xclient.data.l[0] & 1 << 9)
1056                 y = e->xclient.data.l[2];
1057             else
1058                 y = client->area.y;
1059             if (e->xclient.data.l[0] & 1 << 10)
1060                 w = e->xclient.data.l[3];
1061             else
1062                 w = client->area.width;
1063             if (e->xclient.data.l[0] & 1 << 11)
1064                 h = e->xclient.data.l[4];
1065             else
1066                 h = client->area.height;
1067             client->gravity = tmpg;
1068
1069             {
1070                 gint newx = x;
1071                 gint newy = y;
1072                 gint fw = w +
1073                      client->frame->size.left + client->frame->size.right;
1074                 gint fh = h +
1075                      client->frame->size.top + client->frame->size.bottom;
1076                 client_find_onscreen(client, &newx, &newy, fw, fh,
1077                                      client_normal(client));
1078                 if (e->xclient.data.l[0] & 1 << 8)
1079                     x = newx;
1080                 if (e->xclient.data.l[0] & 1 << 9)
1081                     y = newy;
1082             }
1083
1084             client_configure(client, OB_CORNER_TOPLEFT,
1085                              x, y, w, h, FALSE, TRUE);
1086
1087             client->gravity = oldg;
1088         }
1089         break;
1090     case PropertyNotify:
1091         /* validate cuz we query stuff off the client here */
1092         if (!client_validate(client)) break;
1093   
1094         /* compress changes to a single property into a single change */
1095         while (XCheckTypedWindowEvent(ob_display, client->window,
1096                                       e->type, &ce)) {
1097             Atom a, b;
1098
1099             /* XXX: it would be nice to compress ALL changes to a property,
1100                not just changes in a row without other props between. */
1101
1102             a = ce.xproperty.atom;
1103             b = e->xproperty.atom;
1104
1105             if (a == b)
1106                 continue;
1107             if ((a == prop_atoms.net_wm_name ||
1108                  a == prop_atoms.wm_name ||
1109                  a == prop_atoms.net_wm_icon_name ||
1110                  a == prop_atoms.wm_icon_name)
1111                 &&
1112                 (b == prop_atoms.net_wm_name ||
1113                  b == prop_atoms.wm_name ||
1114                  b == prop_atoms.net_wm_icon_name ||
1115                  b == prop_atoms.wm_icon_name)) {
1116                 continue;
1117             }
1118             if (a == prop_atoms.net_wm_icon &&
1119                 b == prop_atoms.net_wm_icon)
1120                 continue;
1121
1122             XPutBackEvent(ob_display, &ce);
1123             break;
1124         }
1125
1126         msgtype = e->xproperty.atom;
1127         if (msgtype == XA_WM_NORMAL_HINTS) {
1128             client_update_normal_hints(client);
1129             /* normal hints can make a window non-resizable */
1130             client_setup_decor_and_functions(client);
1131         } else if (msgtype == XA_WM_HINTS) {
1132             client_update_wmhints(client);
1133         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1134             client_update_transient_for(client);
1135             client_get_type(client);
1136             /* type may have changed, so update the layer */
1137             client_calc_layer(client);
1138             client_setup_decor_and_functions(client);
1139         } else if (msgtype == prop_atoms.net_wm_name ||
1140                    msgtype == prop_atoms.wm_name ||
1141                    msgtype == prop_atoms.net_wm_icon_name ||
1142                    msgtype == prop_atoms.wm_icon_name) {
1143             client_update_title(client);
1144         } else if (msgtype == prop_atoms.wm_class) {
1145             client_update_class(client);
1146         } else if (msgtype == prop_atoms.wm_protocols) {
1147             client_update_protocols(client);
1148             client_setup_decor_and_functions(client);
1149         }
1150         else if (msgtype == prop_atoms.net_wm_strut) {
1151             client_update_strut(client);
1152         }
1153         else if (msgtype == prop_atoms.net_wm_icon) {
1154             client_update_icons(client);
1155         }
1156         else if (msgtype == prop_atoms.net_wm_user_time) {
1157             client_update_user_time(client, TRUE);
1158         }
1159         else if (msgtype == prop_atoms.sm_client_id) {
1160             client_update_sm_client_id(client);
1161         }
1162     default:
1163         ;
1164 #ifdef SHAPE
1165         if (extensions_shape && e->type == extensions_shape_event_basep) {
1166             client->shaped = ((XShapeEvent*)e)->shaped;
1167             frame_adjust_shape(client->frame);
1168         }
1169 #endif
1170     }
1171 }
1172
1173 static void event_handle_dock(ObDock *s, XEvent *e)
1174 {
1175     switch (e->type) {
1176     case ButtonPress:
1177         if (e->xbutton.button == 1)
1178             stacking_raise(DOCK_AS_WINDOW(s), FALSE);
1179         else if (e->xbutton.button == 2)
1180             stacking_lower(DOCK_AS_WINDOW(s), FALSE);
1181         break;
1182     case EnterNotify:
1183         dock_hide(FALSE);
1184         break;
1185     case LeaveNotify:
1186         dock_hide(TRUE);
1187         break;
1188     }
1189 }
1190
1191 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1192 {
1193     switch (e->type) {
1194     case MotionNotify:
1195         dock_app_drag(app, &e->xmotion);
1196         break;
1197     case UnmapNotify:
1198         if (app->ignore_unmaps) {
1199             app->ignore_unmaps--;
1200             break;
1201         }
1202         dock_remove(app, TRUE);
1203         break;
1204     case DestroyNotify:
1205         dock_remove(app, FALSE);
1206         break;
1207     case ReparentNotify:
1208         dock_remove(app, FALSE);
1209         break;
1210     case ConfigureNotify:
1211         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1212         break;
1213     }
1214 }
1215
1216 ObMenuFrame* find_active_menu()
1217 {
1218     GList *it;
1219     ObMenuFrame *ret = NULL;
1220
1221     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1222         ret = it->data;
1223         if (ret->selected)
1224             break;
1225         ret = NULL;
1226     }
1227     return ret;
1228 }
1229
1230 ObMenuFrame* find_active_or_last_menu()
1231 {
1232     ObMenuFrame *ret = NULL;
1233
1234     ret = find_active_menu();
1235     if (!ret && menu_frame_visible)
1236         ret = menu_frame_visible->data;
1237     return ret;
1238 }
1239
1240 static void event_handle_menu(XEvent *ev)
1241 {
1242     ObMenuFrame *f;
1243     ObMenuEntryFrame *e;
1244
1245     switch (ev->type) {
1246     case ButtonRelease:
1247         if (menu_can_hide) {
1248             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1249                                             ev->xbutton.y_root)))
1250                 menu_entry_frame_execute(e, ev->xbutton.state,
1251                                          ev->xbutton.time);
1252             else
1253                 menu_frame_hide_all();
1254         }
1255         break;
1256     case MotionNotify:
1257         if ((f = menu_frame_under(ev->xmotion.x_root,
1258                                   ev->xmotion.y_root))) {
1259             menu_frame_move_on_screen(f);
1260             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1261                                             ev->xmotion.y_root)))
1262                 menu_frame_select(f, e);
1263         }
1264         {
1265             ObMenuFrame *a;
1266
1267             a = find_active_menu();
1268             if (a && a != f &&
1269                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1270             {
1271                 menu_frame_select(a, NULL);
1272             }
1273         }
1274         break;
1275     case KeyPress:
1276         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1277             menu_frame_hide_all();
1278         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1279             ObMenuFrame *f;
1280             if ((f = find_active_menu()))
1281                 menu_entry_frame_execute(f->selected, ev->xkey.state,
1282                                          ev->xkey.time);
1283         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1284             ObMenuFrame *f;
1285             if ((f = find_active_or_last_menu()) && f->parent)
1286                 menu_frame_select(f, NULL);
1287         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1288             ObMenuFrame *f;
1289             if ((f = find_active_or_last_menu()) && f->child)
1290                 menu_frame_select_next(f->child);
1291         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1292             ObMenuFrame *f;
1293             if ((f = find_active_or_last_menu()))
1294                 menu_frame_select_previous(f);
1295         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1296             ObMenuFrame *f;
1297             if ((f = find_active_or_last_menu()))
1298                 menu_frame_select_next(f);
1299         }
1300         break;
1301     }
1302 }
1303
1304 static gboolean menu_hide_delay_func(gpointer data)
1305 {
1306     menu_can_hide = TRUE;
1307     return FALSE; /* no repeat */
1308 }
1309
1310 static gboolean focus_delay_func(gpointer data)
1311 {
1312     ObClient *c = data;
1313
1314     if (focus_client != c) {
1315         client_focus(c);
1316         if (config_focus_raise)
1317             client_raise(c);
1318     }
1319     return FALSE; /* no repeat */
1320 }
1321
1322 static void focus_delay_client_dest(ObClient *client, gpointer data)
1323 {
1324     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1325                                      client, TRUE);
1326 }
1327
1328 static void event_client_dest(ObClient *client, gpointer data)
1329 {
1330     if (client == focus_hilite)
1331         focus_hilite = NULL;
1332 }
1333
1334 void event_halt_focus_delay()
1335 {
1336     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1337 }
1338
1339 void event_ignore_queued_enters()
1340 {
1341     GSList *saved = NULL, *it;
1342     XEvent *e;
1343                 
1344     XSync(ob_display, FALSE);
1345
1346     /* count the events */
1347     while (TRUE) {
1348         e = g_new(XEvent, 1);
1349         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1350             ObWindow *win;
1351             
1352             win = g_hash_table_lookup(window_map, &e->xany.window);
1353             if (win && WINDOW_IS_CLIENT(win))
1354                 ++ignore_enter_focus;
1355             
1356             saved = g_slist_append(saved, e);
1357         } else {
1358             g_free(e);
1359             break;
1360         }
1361     }
1362     /* put the events back */
1363     for (it = saved; it; it = g_slist_next(it)) {
1364         XPutBackEvent(ob_display, it->data);
1365         g_free(it->data);
1366     }
1367     g_slist_free(saved);
1368 }