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