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