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