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