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