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