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