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