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