]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
make menu hiding-on-release based on a timer rather than on where it is released...
[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             switch (frame_context(client, e->xbutton.window)) {
585             case OB_FRAME_CONTEXT_MAXIMIZE:
586                 client->frame->max_press = (e->type == ButtonPress);
587                 framerender_frame(client->frame);
588                 break;
589             case OB_FRAME_CONTEXT_CLOSE:
590                 client->frame->close_press = (e->type == ButtonPress);
591                 framerender_frame(client->frame);
592                 break;
593             case OB_FRAME_CONTEXT_ICONIFY:
594                 client->frame->iconify_press = (e->type == ButtonPress);
595                 framerender_frame(client->frame);
596                 break;
597             case OB_FRAME_CONTEXT_ALLDESKTOPS:
598                 client->frame->desk_press = (e->type == ButtonPress);
599                 framerender_frame(client->frame);
600                 break; 
601             case OB_FRAME_CONTEXT_SHADE:
602                 client->frame->shade_press = (e->type == ButtonPress);
603                 framerender_frame(client->frame);
604                 break;
605             default:
606                 /* nothing changes with clicks for any other contexts */
607                 break;
608             }
609         }
610         break;
611     case FocusIn:
612 #ifdef DEBUG_FOCUS
613         ob_debug("FocusIn on client for %lx\n", client->window);
614 #endif
615         if (client != focus_client) {
616             focus_set_client(client);
617             frame_adjust_focus(client->frame, TRUE);
618         }
619         break;
620     case FocusOut:
621 #ifdef DEBUG_FOCUS
622         ob_debug("FocusOut on client for %lx\n", client->window);
623 #endif
624         /* are we a fullscreen window or a transient of one? (checks layer)
625            if we are then we need to be iconified since we are losing focus
626          */
627         if (client->layer == OB_STACKING_LAYER_FULLSCREEN && !client->iconic &&
628             !client_search_focus_tree_full(client))
629             /* iconify fullscreen windows when they and their transients
630                aren't focused */
631             client_iconify(client, TRUE, TRUE);
632         frame_adjust_focus(client->frame, FALSE);
633         break;
634     case LeaveNotify:
635         con = frame_context(client, e->xcrossing.window);
636         switch (con) {
637         case OB_FRAME_CONTEXT_MAXIMIZE:
638             client->frame->max_hover = FALSE;
639             frame_adjust_state(client->frame);
640             break;
641         case OB_FRAME_CONTEXT_ALLDESKTOPS:
642             client->frame->desk_hover = FALSE;
643             frame_adjust_state(client->frame);
644             break;
645         case OB_FRAME_CONTEXT_SHADE:
646             client->frame->shade_hover = FALSE;
647             frame_adjust_state(client->frame);
648             break;
649         case OB_FRAME_CONTEXT_ICONIFY:
650             client->frame->iconify_hover = FALSE;
651             frame_adjust_state(client->frame);
652             break;
653         case OB_FRAME_CONTEXT_CLOSE:
654             client->frame->close_hover = FALSE;
655             frame_adjust_state(client->frame);
656             break;
657         case OB_FRAME_CONTEXT_FRAME:
658             /* XXX if doing a 'reconfigure' make sure you kill this timer,
659                maybe all timers.. */
660             if (config_focus_delay && client == focus_delay_client) {
661                 ob_main_loop_timeout_remove_data(ob_main_loop,
662                                                  focus_delay_func,
663                                                  focus_delay_client);
664                 focus_delay_client = NULL;
665             }
666         default:
667             break;
668         }
669         break;
670     case EnterNotify:
671         con = frame_context(client, e->xcrossing.window);
672         switch (con) {
673         case OB_FRAME_CONTEXT_MAXIMIZE:
674             client->frame->max_hover = TRUE;
675             frame_adjust_state(client->frame);
676             break;
677         case OB_FRAME_CONTEXT_ALLDESKTOPS:
678             client->frame->desk_hover = TRUE;
679             frame_adjust_state(client->frame);
680             break;
681         case OB_FRAME_CONTEXT_SHADE:
682             client->frame->shade_hover = TRUE;
683             frame_adjust_state(client->frame);
684             break;
685         case OB_FRAME_CONTEXT_ICONIFY:
686             client->frame->iconify_hover = TRUE;
687             frame_adjust_state(client->frame);
688             break;
689         case OB_FRAME_CONTEXT_CLOSE:
690             client->frame->close_hover = TRUE;
691             frame_adjust_state(client->frame);
692             break;
693         case OB_FRAME_CONTEXT_FRAME:
694             if (client_normal(client)) {
695                 if (ob_state() == OB_STATE_STARTING) {
696                     /* move it to the top of the focus order */
697                     guint desktop = client->desktop;
698                     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
699                     focus_order[desktop] = g_list_remove(focus_order[desktop],
700                                                          client);
701                     focus_order[desktop] = g_list_prepend(focus_order[desktop],
702                                                           client);
703                 } else if (config_focus_follow) {
704 #ifdef DEBUG_FOCUS
705                     ob_debug("EnterNotify on %lx, focusing window\n",
706                              client->window);
707 #endif
708                     if (config_focus_delay) {
709                         ob_main_loop_timeout_add(ob_main_loop,
710                                                  config_focus_delay,
711                                                  focus_delay_func,
712                                                  client, NULL);
713                         focus_delay_client = client;
714                     } else
715                         client_focus(client);
716                 }
717             }
718             break;
719         default:
720             break;
721         }
722         break;
723     case ConfigureRequest:
724         /* compress these */
725         while (XCheckTypedWindowEvent(ob_display, client->window,
726                                       ConfigureRequest, &ce)) {
727             ++i;
728             /* XXX if this causes bad things.. we can compress config req's
729                with the same mask. */
730             e->xconfigurerequest.value_mask |=
731                 ce.xconfigurerequest.value_mask;
732             if (ce.xconfigurerequest.value_mask & CWX)
733                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
734             if (ce.xconfigurerequest.value_mask & CWY)
735                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
736             if (ce.xconfigurerequest.value_mask & CWWidth)
737                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
738             if (ce.xconfigurerequest.value_mask & CWHeight)
739                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
740             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
741                 e->xconfigurerequest.border_width =
742                     ce.xconfigurerequest.border_width;
743             if (ce.xconfigurerequest.value_mask & CWStackMode)
744                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
745         }
746
747         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
748         if (client->iconic || client->shaded) return;
749
750         /* resize, then move, as specified in the EWMH section 7.7 */
751         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
752                                                CWX | CWY |
753                                                CWBorderWidth)) {
754             int x, y, w, h;
755             ObCorner corner;
756
757             if (e->xconfigurerequest.value_mask & CWBorderWidth)
758                 client->border_width = e->xconfigurerequest.border_width;
759
760             x = (e->xconfigurerequest.value_mask & CWX) ?
761                 e->xconfigurerequest.x : client->area.x;
762             y = (e->xconfigurerequest.value_mask & CWY) ?
763                 e->xconfigurerequest.y : client->area.y;
764             w = (e->xconfigurerequest.value_mask & CWWidth) ?
765                 e->xconfigurerequest.width : client->area.width;
766             h = (e->xconfigurerequest.value_mask & CWHeight) ?
767                 e->xconfigurerequest.height : client->area.height;
768
769             {
770                 int newx = x;
771                 int newy = y;
772                 int fw = w +
773                     client->frame->size.left + client->frame->size.right;
774                 int fh = h +
775                     client->frame->size.top + client->frame->size.bottom;
776                 client_find_onscreen(client, &newx, &newy, fw, fh,
777                                      client_normal(client));
778                 if (e->xconfigurerequest.value_mask & CWX)
779                     x = newx;
780                 if (e->xconfigurerequest.value_mask & CWY)
781                     y = newy;
782             }
783                
784             switch (client->gravity) {
785             case NorthEastGravity:
786             case EastGravity:
787                 corner = OB_CORNER_TOPRIGHT;
788                 break;
789             case SouthWestGravity:
790             case SouthGravity:
791                 corner = OB_CORNER_BOTTOMLEFT;
792                 break;
793             case SouthEastGravity:
794                 corner = OB_CORNER_BOTTOMRIGHT;
795                 break;
796             default:     /* NorthWest, Static, etc */
797                 corner = OB_CORNER_TOPLEFT;
798             }
799
800             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
801                                   TRUE);
802         }
803
804         if (e->xconfigurerequest.value_mask & CWStackMode) {
805             switch (e->xconfigurerequest.detail) {
806             case Below:
807             case BottomIf:
808                 stacking_lower(CLIENT_AS_WINDOW(client));
809                 break;
810
811             case Above:
812             case TopIf:
813             default:
814                 stacking_raise(CLIENT_AS_WINDOW(client));
815                 break;
816             }
817         }
818         break;
819     case UnmapNotify:
820         if (client->ignore_unmaps) {
821             client->ignore_unmaps--;
822             break;
823         }
824         client_unmanage(client);
825         break;
826     case DestroyNotify:
827         client_unmanage(client);
828         break;
829     case ReparentNotify:
830         /* this is when the client is first taken captive in the frame */
831         if (e->xreparent.parent == client->frame->plate) break;
832
833         /*
834           This event is quite rare and is usually handled in unmapHandler.
835           However, if the window is unmapped when the reparent event occurs,
836           the window manager never sees it because an unmap event is not sent
837           to an already unmapped window.
838         */
839
840         /* we don't want the reparent event, put it back on the stack for the
841            X server to deal with after we unmanage the window */
842         XPutBackEvent(ob_display, e);
843      
844         client_unmanage(client);
845         break;
846     case MapRequest:
847         ob_debug("MapRequest for 0x%lx\n", client->window);
848         if (!client->iconic) break; /* this normally doesn't happen, but if it
849                                        does, we don't want it! */
850         if (screen_showing_desktop)
851             screen_show_desktop(FALSE);
852         client_iconify(client, FALSE, TRUE);
853         if (!client->frame->visible)
854             /* if its not visible still, then don't mess with it */
855             break;
856         if (client->shaded)
857             client_shade(client, FALSE);
858         client_focus(client);
859         stacking_raise(CLIENT_AS_WINDOW(client));
860         break;
861     case ClientMessage:
862         /* validate cuz we query stuff off the client here */
863         if (!client_validate(client)) break;
864   
865         if (e->xclient.format != 32) return;
866
867         msgtype = e->xclient.message_type;
868         if (msgtype == prop_atoms.wm_change_state) {
869             /* compress changes into a single change */
870             while (XCheckTypedWindowEvent(ob_display, client->window,
871                                           e->type, &ce)) {
872                 /* XXX: it would be nice to compress ALL messages of a
873                    type, not just messages in a row without other
874                    message types between. */
875                 if (ce.xclient.message_type != msgtype) {
876                     XPutBackEvent(ob_display, &ce);
877                     break;
878                 }
879                 e->xclient = ce.xclient;
880             }
881             client_set_wm_state(client, e->xclient.data.l[0]);
882         } else if (msgtype == prop_atoms.net_wm_desktop) {
883             /* compress changes into a single change */
884             while (XCheckTypedWindowEvent(ob_display, client->window,
885                                           e->type, &ce)) {
886                 /* XXX: it would be nice to compress ALL messages of a
887                    type, not just messages in a row without other
888                    message types between. */
889                 if (ce.xclient.message_type != msgtype) {
890                     XPutBackEvent(ob_display, &ce);
891                     break;
892                 }
893                 e->xclient = ce.xclient;
894             }
895             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
896                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
897                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
898                                    FALSE);
899         } else if (msgtype == prop_atoms.net_wm_state) {
900             /* can't compress these */
901             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
902                      (e->xclient.data.l[0] == 0 ? "Remove" :
903                       e->xclient.data.l[0] == 1 ? "Add" :
904                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
905                      e->xclient.data.l[1], e->xclient.data.l[2],
906                      client->window);
907             client_set_state(client, e->xclient.data.l[0],
908                              e->xclient.data.l[1], e->xclient.data.l[2]);
909         } else if (msgtype == prop_atoms.net_close_window) {
910             ob_debug("net_close_window for 0x%lx\n", client->window);
911             client_close(client);
912         } else if (msgtype == prop_atoms.net_active_window) {
913             ob_debug("net_active_window for 0x%lx\n", client->window);
914             client_activate(client, FALSE);
915         } else if (msgtype == prop_atoms.net_wm_moveresize) {
916             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
917             if ((Atom)e->xclient.data.l[2] ==
918                 prop_atoms.net_wm_moveresize_size_topleft ||
919                 (Atom)e->xclient.data.l[2] ==
920                 prop_atoms.net_wm_moveresize_size_top ||
921                 (Atom)e->xclient.data.l[2] ==
922                 prop_atoms.net_wm_moveresize_size_topright ||
923                 (Atom)e->xclient.data.l[2] ==
924                 prop_atoms.net_wm_moveresize_size_right ||
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_bottomright ||
929                 (Atom)e->xclient.data.l[2] ==
930                 prop_atoms.net_wm_moveresize_size_bottom ||
931                 (Atom)e->xclient.data.l[2] ==
932                 prop_atoms.net_wm_moveresize_size_bottomleft ||
933                 (Atom)e->xclient.data.l[2] ==
934                 prop_atoms.net_wm_moveresize_size_left ||
935                 (Atom)e->xclient.data.l[2] ==
936                 prop_atoms.net_wm_moveresize_move ||
937                 (Atom)e->xclient.data.l[2] ==
938                 prop_atoms.net_wm_moveresize_size_keyboard ||
939                 (Atom)e->xclient.data.l[2] ==
940                 prop_atoms.net_wm_moveresize_move_keyboard) {
941
942                 moveresize_start(client, e->xclient.data.l[0],
943                                  e->xclient.data.l[1], e->xclient.data.l[3],
944                                  e->xclient.data.l[2]);
945             }
946         } else if (msgtype == prop_atoms.net_moveresize_window) {
947             int oldg = client->gravity;
948             int tmpg, x, y, w, h;
949
950             if (e->xclient.data.l[0] & 0xff)
951                 tmpg = e->xclient.data.l[0] & 0xff;
952             else
953                 tmpg = oldg;
954
955             if (e->xclient.data.l[0] & 1 << 8)
956                 x = e->xclient.data.l[1];
957             else
958                 x = client->area.x;
959             if (e->xclient.data.l[0] & 1 << 9)
960                 y = e->xclient.data.l[2];
961             else
962                 y = client->area.y;
963             if (e->xclient.data.l[0] & 1 << 10)
964                 w = e->xclient.data.l[3];
965             else
966                 w = client->area.width;
967             if (e->xclient.data.l[0] & 1 << 11)
968                 h = e->xclient.data.l[4];
969             else
970                 h = client->area.height;
971             client->gravity = tmpg;
972
973             {
974                 int newx = x;
975                 int newy = y;
976                 int fw = w +
977                     client->frame->size.left + client->frame->size.right;
978                 int fh = h +
979                     client->frame->size.top + client->frame->size.bottom;
980                 client_find_onscreen(client, &newx, &newy, fw, fh,
981                                      client_normal(client));
982                 if (e->xclient.data.l[0] & 1 << 8)
983                     x = newx;
984                 if (e->xclient.data.l[0] & 1 << 9)
985                     y = newy;
986             }
987                
988             client_configure(client, OB_CORNER_TOPLEFT,
989                              x, y, w, h, FALSE, TRUE);
990
991             client->gravity = oldg;
992         }
993         break;
994     case PropertyNotify:
995         /* validate cuz we query stuff off the client here */
996         if (!client_validate(client)) break;
997   
998         /* compress changes to a single property into a single change */
999         while (XCheckTypedWindowEvent(ob_display, client->window,
1000                                       e->type, &ce)) {
1001             Atom a, b;
1002
1003             /* XXX: it would be nice to compress ALL changes to a property,
1004                not just changes in a row without other props between. */
1005
1006             a = ce.xproperty.atom;
1007             b = e->xproperty.atom;
1008
1009             if (a == b)
1010                 continue;
1011             if ((a == prop_atoms.net_wm_name ||
1012                  a == prop_atoms.wm_name ||
1013                  a == prop_atoms.net_wm_icon_name ||
1014                  a == prop_atoms.wm_icon_name)
1015                 &&
1016                 (b == prop_atoms.net_wm_name ||
1017                  b == prop_atoms.wm_name ||
1018                  b == prop_atoms.net_wm_icon_name ||
1019                  b == prop_atoms.wm_icon_name)) {
1020                 continue;
1021             }
1022             if ((a == prop_atoms.net_wm_icon ||
1023                  a == prop_atoms.kwm_win_icon)
1024                 &&
1025                 (b == prop_atoms.net_wm_icon ||
1026                  b == prop_atoms.kwm_win_icon))
1027                 continue;
1028
1029             XPutBackEvent(ob_display, &ce);
1030             break;
1031         }
1032
1033         msgtype = e->xproperty.atom;
1034         if (msgtype == XA_WM_NORMAL_HINTS) {
1035             client_update_normal_hints(client);
1036             /* normal hints can make a window non-resizable */
1037             client_setup_decor_and_functions(client);
1038         } else if (msgtype == XA_WM_HINTS) {
1039             client_update_wmhints(client);
1040         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1041             client_update_transient_for(client);
1042             client_get_type(client);
1043             /* type may have changed, so update the layer */
1044             client_calc_layer(client);
1045             client_setup_decor_and_functions(client);
1046         } else if (msgtype == prop_atoms.net_wm_name ||
1047                    msgtype == prop_atoms.wm_name ||
1048                    msgtype == prop_atoms.net_wm_icon_name ||
1049                    msgtype == prop_atoms.wm_icon_name) {
1050             client_update_title(client);
1051         } else if (msgtype == prop_atoms.wm_class) {
1052             client_update_class(client);
1053         } else if (msgtype == prop_atoms.wm_protocols) {
1054             client_update_protocols(client);
1055             client_setup_decor_and_functions(client);
1056         }
1057         else if (msgtype == prop_atoms.net_wm_strut) {
1058             client_update_strut(client);
1059         }
1060         else if (msgtype == prop_atoms.net_wm_icon ||
1061                  msgtype == prop_atoms.kwm_win_icon) {
1062             client_update_icons(client);
1063         }
1064     default:
1065         ;
1066 #ifdef SHAPE
1067         if (extensions_shape && e->type == extensions_shape_event_basep) {
1068             client->shaped = ((XShapeEvent*)e)->shaped;
1069             frame_adjust_shape(client->frame);
1070         }
1071 #endif
1072     }
1073 }
1074
1075 static void event_handle_dock(ObDock *s, XEvent *e)
1076 {
1077     switch (e->type) {
1078     case ButtonPress:
1079         stacking_raise(DOCK_AS_WINDOW(s));
1080         break;
1081     case EnterNotify:
1082         dock_hide(FALSE);
1083         break;
1084     case LeaveNotify:
1085         dock_hide(TRUE);
1086         break;
1087     }
1088 }
1089
1090 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1091 {
1092     switch (e->type) {
1093     case MotionNotify:
1094         dock_app_drag(app, &e->xmotion);
1095         break;
1096     case UnmapNotify:
1097         if (app->ignore_unmaps) {
1098             app->ignore_unmaps--;
1099             break;
1100         }
1101         dock_remove(app, TRUE);
1102         break;
1103     case DestroyNotify:
1104         dock_remove(app, FALSE);
1105         break;
1106     case ReparentNotify:
1107         dock_remove(app, FALSE);
1108         break;
1109     case ConfigureNotify:
1110         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1111         break;
1112     }
1113 }
1114
1115 ObMenuFrame* find_active_menu()
1116 {
1117     GList *it;
1118     ObMenuFrame *f;
1119
1120     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1121         f = it->data;
1122         if (f->selected)
1123             break;
1124     }
1125     return it ? it->data : NULL;
1126 }
1127
1128 static void event_handle_menu(XEvent *ev)
1129 {
1130     ObMenuFrame *f;
1131     ObMenuEntryFrame *e;
1132
1133     switch (ev->type) {
1134     case ButtonRelease:
1135         if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1136                                         ev->xbutton.y_root)))
1137             menu_entry_frame_execute(e, ev->xbutton.state);
1138         else if (menu_can_hide)
1139             menu_frame_hide_all();
1140         break;
1141     case MotionNotify:
1142         if ((f = menu_frame_under(ev->xmotion.x_root,
1143                                   ev->xmotion.y_root))) {
1144             menu_frame_move_on_screen(f);
1145             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1146                                             ev->xmotion.y_root)))
1147                 menu_frame_select(f, e);
1148         }
1149         break;
1150     case KeyPress:
1151         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1152             menu_frame_hide_all();
1153         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1154             ObMenuFrame *f;
1155             if ((f = find_active_menu()))
1156                 menu_entry_frame_execute(f->selected, ev->xkey.state);
1157         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1158             ObMenuFrame *f;
1159             if ((f = find_active_menu()) && f->parent)
1160                 menu_frame_select(f, NULL);
1161         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1162             ObMenuFrame *f;
1163             if ((f = find_active_menu()) && f->child)
1164                 menu_frame_select_next(f->child);
1165         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1166             ObMenuFrame *f;
1167             if ((f = find_active_menu()))
1168                 menu_frame_select_previous(f);
1169         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1170             ObMenuFrame *f;
1171             if ((f = find_active_menu()))
1172                 menu_frame_select_next(f);
1173         }
1174         break;
1175     }
1176 }
1177
1178 static gboolean menu_hide_delay_func(gpointer data)
1179 {
1180     menu_can_hide = TRUE;
1181     return FALSE; /* no repeat */
1182 }
1183
1184 static gboolean focus_delay_func(gpointer data)
1185 {
1186     client_focus(focus_delay_client);
1187     return FALSE; /* no repeat */
1188 }
1189
1190 static void focus_delay_client_dest(gpointer data)
1191 {
1192     ObClient *c = data;
1193     if (c == focus_delay_client) {
1194         ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1195                                          focus_delay_client);
1196         focus_delay_client = NULL;
1197     }
1198 }