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