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