]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
be more careful about ignoring focusin/out event pairs
[dana/openbox.git] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "config.h"
6 #include "screen.h"
7 #include "frame.h"
8 #include "menu.h"
9 #include "framerender.h"
10 #include "focus.h"
11 #include "moveresize.h"
12 #include "stacking.h"
13 #include "extensions.h"
14 #include "timer.h"
15 #include "dispatch.h"
16
17 #include <X11/Xlib.h>
18 #include <X11/keysym.h>
19 #include <X11/Xatom.h>
20 #ifdef HAVE_SYS_SELECT_H
21 #  include <sys/select.h>
22 #endif
23
24 static void event_process(XEvent *e);
25 static void event_handle_root(XEvent *e);
26 static void event_handle_client(Client *c, XEvent *e);
27 static void event_handle_menu(Menu *menu, XEvent *e);
28
29 #define INVALID_FOCUSIN(e) ((e)->xfocus.detail == NotifyInferior || \
30                             (e)->xfocus.detail > NotifyNonlinearVirtual)
31 #define INVALID_FOCUSOUT(e) ((e)->xfocus.mode == NotifyGrab || \
32                              (e)->xfocus.detail == NotifyInferior || \
33                              (e)->xfocus.detail == NotifyAncestor || \
34                              (e)->xfocus.detail > NotifyNonlinearVirtual)
35
36 Time event_lasttime = 0;
37
38 /*! The value of the mask for the NumLock modifier */
39 unsigned int NumLockMask;
40 /*! The value of the mask for the ScrollLock modifier */
41 unsigned int ScrollLockMask;
42 /*! The key codes for the modifier keys */
43 static XModifierKeymap *modmap;
44 /*! Table of the constant modifier masks */
45 static const int mask_table[] = {
46     ShiftMask, LockMask, ControlMask, Mod1Mask,
47     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
48 };
49 static int mask_table_size;
50
51 void event_startup()
52 {
53     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
54      
55     /* get lock masks that are defined by the display (not constant) */
56     modmap = XGetModifierMapping(ob_display);
57     g_assert(modmap);
58     if (modmap && modmap->max_keypermod > 0) {
59         size_t cnt;
60         const size_t size = mask_table_size * modmap->max_keypermod;
61         /* get the values of the keyboard lock modifiers
62            Note: Caps lock is not retrieved the same way as Scroll and Num
63            lock since it doesn't need to be. */
64         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
65         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
66                                                      XK_Scroll_Lock);
67           
68         for (cnt = 0; cnt < size; ++cnt) {
69             if (! modmap->modifiermap[cnt]) continue;
70                
71             if (num_lock == modmap->modifiermap[cnt])
72                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
73             if (scroll_lock == modmap->modifiermap[cnt])
74                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
75         }
76     }
77 }
78
79 void event_shutdown()
80 {
81     XFreeModifiermap(modmap);
82 }
83
84 void event_loop()
85 {
86     fd_set selset;
87     XEvent e;
88     int x_fd;
89     struct timeval *wait;
90     gboolean had_event = FALSE;
91
92     while (TRUE) {
93         /*
94           There are slightly different event retrieval semantics here for
95           local (or high bandwidth) versus remote (or low bandwidth)
96           connections to the display/Xserver.
97         */
98         if (ob_remote) {
99             if (!XPending(ob_display))
100                 break;
101         } else {
102             /*
103               This XSync allows for far more compression of events, which
104               makes things like Motion events perform far far better. Since
105               it also means network traffic for every event instead of every
106               X events (where X is the number retrieved at a time), it
107               probably should not be used for setups where Openbox is
108               running on a remote/low bandwidth display/Xserver.
109             */
110             XSync(ob_display, FALSE);
111             if (!XEventsQueued(ob_display, QueuedAlready))
112                 break;
113         }
114         XNextEvent(ob_display, &e);
115
116         event_process(&e);
117        had_event = TRUE;
118     }
119
120     if (!had_event) {
121         timer_dispatch((GTimeVal**)&wait);
122         x_fd = ConnectionNumber(ob_display);
123         FD_ZERO(&selset);
124         FD_SET(x_fd, &selset);
125         select(x_fd + 1, &selset, NULL, NULL, wait);
126     }
127 }
128
129 static Window event_get_window(XEvent *e)
130 {
131     Window window;
132
133     /* pick a window */
134     switch (e->type) {
135     case MapRequest:
136         window = e->xmap.window;
137         break;
138     case UnmapNotify:
139         window = e->xunmap.window;
140         break;
141     case DestroyNotify:
142         window = e->xdestroywindow.window;
143         break;
144     case ConfigureRequest:
145         window = e->xconfigurerequest.window;
146         break;
147     case ConfigureNotify:
148         window = e->xconfigure.window;
149         break;
150     default:
151 #ifdef XKB
152         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
153             switch (((XkbAnyEvent*)&e)->xkb_type) {
154             case XkbBellNotify:
155                 window = ((XkbBellNotifyEvent*)&e)->window;
156             default:
157                 window = None;
158             }
159         } else
160 #endif
161             window = e->xany.window;
162     }
163     return window;
164 }
165
166 static void event_set_lasttime(XEvent *e)
167 {
168     /* grab the lasttime and hack up the state */
169     switch (e->type) {
170     case ButtonPress:
171     case ButtonRelease:
172         event_lasttime = e->xbutton.time;
173         break;
174     case KeyPress:
175         event_lasttime = e->xkey.time;
176         break;
177     case KeyRelease:
178         event_lasttime = e->xkey.time;
179         break;
180     case MotionNotify:
181         event_lasttime = e->xmotion.time;
182         break;
183     case PropertyNotify:
184         event_lasttime = e->xproperty.time;
185         break;
186     case EnterNotify:
187     case LeaveNotify:
188         event_lasttime = e->xcrossing.time;
189         break;
190     default:
191         event_lasttime = CurrentTime;
192         break;
193     }
194 }
195
196 #define STRIP_MODS(s) \
197         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
198         /* kill off the Button1Mask etc, only want the modifiers */ \
199         s &= (ControlMask | ShiftMask | Mod1Mask | \
200               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
201
202 static void event_hack_mods(XEvent *e)
203 {
204     KeyCode *kp;
205     int i, k;
206
207     switch (e->type) {
208     case ButtonPress:
209     case ButtonRelease:
210         STRIP_MODS(e->xbutton.state);
211         break;
212     case KeyPress:
213         STRIP_MODS(e->xkey.state);
214         break;
215     case KeyRelease:
216         STRIP_MODS(e->xkey.state);
217         /* remove from the state the mask of the modifier being released, if
218            it is a modifier key being released (this is a little ugly..) */
219         kp = modmap->modifiermap;
220         for (i = 0; i < mask_table_size; ++i) {
221             for (k = 0; k < modmap->max_keypermod; ++k) {
222                 if (*kp == e->xkey.keycode) { /* found the keycode */
223                     /* remove the mask for it */
224                     e->xkey.state &= ~mask_table[i];
225                     /* cause the first loop to break; */
226                     i = mask_table_size;
227                     break; /* get outta here! */
228                 }
229                 ++kp;
230             }
231         }
232         break;
233     case MotionNotify:
234         STRIP_MODS(e->xmotion.state);
235         /* compress events */
236         {
237             XEvent ce;
238             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
239                                           e->type, &ce)) {
240                 e->xmotion.x_root = ce.xmotion.x_root;
241                 e->xmotion.y_root = ce.xmotion.y_root;
242             }
243         }
244         break;
245     }
246 }
247
248 static gboolean event_ignore(XEvent *e, Client *client)
249 {
250     switch(e->type) {
251     case FocusIn:
252         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
253            because of RevertToPointerRoot. If the focus ends up reverting to
254            pointer root on a workspace change, then the FocusIn event that we
255            want will be of type NotifyAncestor. This situation does not occur
256            for FocusOut, so it is safely ignored there.
257         */
258         if (INVALID_FOCUSIN(e) ||
259             client == NULL) {
260 #ifdef DEBUG_FOCUS
261         g_message("FocusIn on %lx mode %d detail %d IGNORED", e->xfocus.window,
262                   e->xfocus.mode, e->xfocus.detail);
263 #endif
264             /* says a client was not found for the event (or a valid FocusIn
265                event was not found.
266             */
267             e->xfocus.window = None;
268             return TRUE;
269         }
270
271 #ifdef DEBUG_FOCUS
272         g_message("FocusIn on %lx mode %d detail %d", e->xfocus.window,
273                   e->xfocus.mode, e->xfocus.detail);
274 #endif
275         break;
276     case FocusOut:
277         if (INVALID_FOCUSOUT(e)) {
278 #ifdef DEBUG_FOCUS
279         g_message("FocusOut on %lx mode %d detail %d IGNORED",
280                   e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
281 #endif
282             return TRUE;
283         }
284
285 #ifdef DEBUG_FOCUS
286         g_message("FocusOut on %lx mode %d detail %d",
287                   e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
288 #endif
289
290         {
291             XEvent fe;
292             gboolean fallback = TRUE;
293
294             while (TRUE) {
295                 if (!XCheckTypedWindowEvent(ob_display, FocusOut,
296                                             e->xfocus.window,&fe))
297                     if (!XCheckTypedEvent(ob_display, FocusIn, &fe))
298                         break;
299                 if (fe.type == FocusOut) {
300 #ifdef DEBUG_FOCUS
301                     g_message("found pending FocusOut");
302 #endif
303                     if (!INVALID_FOCUSOUT(&fe)) {
304                         /* if there is a VALID FocusOut still coming, don't
305                            fallback focus yet, we'll deal with it then */
306                         XPutBackEvent(ob_display, &fe);
307                         fallback = FALSE;
308                         break;
309                     }
310                 } else {
311 #ifdef DEBUG_FOCUS
312                     g_message("found pending FocusIn");
313 #endif
314                     /* is the focused window getting a FocusOut/In back to
315                        itself? */
316                     if (fe.xfocus.window == e->xfocus.window &&
317                         !event_ignore(&fe, client)) {
318 #ifdef DEBUG_FOCUS
319                         g_message("focused window got an Out/In back to "
320                                   "itself IGNORED both");
321 #endif
322                         return TRUE;
323                     }
324
325                     /* once all the FocusOut's have been dealt with, if there
326                        is a FocusIn still left and it is valid, then use it */
327                     event_process(&fe);
328                     /* secret magic way of event_process telling us that no
329                        client was found for the FocusIn event. ^_^ */
330                     if (fe.xfocus.window != None) {
331                         fallback = FALSE;
332                         break;
333                     }
334                 }
335             }
336             if (fallback) {
337 #ifdef DEBUG_FOCUS
338                 g_message("no valid FocusIn and no FocusOut events found, "
339                           "falling back");
340 #endif
341                 focus_fallback(Fallback_NoFocus);
342             }
343         }
344         break;
345     case EnterNotify:
346     case LeaveNotify:
347         /* NotifyUngrab occurs when a mouse button is released and the event is
348            caused, like when lowering a window */
349         /* NotifyVirtual occurs when ungrabbing the pointer */
350         if (e->xcrossing.mode == NotifyGrab ||
351             e->xcrossing.detail == NotifyInferior ||
352             (e->xcrossing.mode == NotifyUngrab &&
353              e->xcrossing.detail == NotifyVirtual)) {
354 #ifdef DEBUG_FOCUS
355             g_message("%sNotify mode %d detail %d on %lx IGNORED",
356                       (e->type == EnterNotify ? "Enter" : "Leave"),
357                       e->xcrossing.mode,
358                       e->xcrossing.detail, client?client->window:0);
359 #endif
360             return TRUE;
361         }
362 #ifdef DEBUG_FOCUS
363         g_message("%sNotify mode %d detail %d on %lx",
364                   (e->type == EnterNotify ? "Enter" : "Leave"),
365                   e->xcrossing.mode,
366                   e->xcrossing.detail, client?client->window:0);
367 #endif
368         break;
369     }
370     return FALSE;
371 }
372
373 static void event_process(XEvent *e)
374 {
375     Window window;
376     Client *client;
377     Menu *menu = NULL;
378
379     window = event_get_window(e);
380     if (!(client = g_hash_table_lookup(client_map, &window)))
381         menu = g_hash_table_lookup(menu_map, &window);
382
383     event_set_lasttime(e);
384     event_hack_mods(e);
385     if (event_ignore(e, client))
386         return;
387
388     /* deal with it in the kernel */
389     if (menu) {
390         event_handle_menu(menu, e);
391         return;
392     } else if (client)
393         event_handle_client(client, e);
394     else if (window == ob_root)
395         event_handle_root(e);
396     else if (e->type == MapRequest)
397         client_manage(window);
398     else if (e->type == ConfigureRequest) {
399         /* unhandled configure requests must be used to configure the
400            window directly */
401         XWindowChanges xwc;
402                
403         xwc.x = e->xconfigurerequest.x;
404         xwc.y = e->xconfigurerequest.y;
405         xwc.width = e->xconfigurerequest.width;
406         xwc.height = e->xconfigurerequest.height;
407         xwc.border_width = e->xconfigurerequest.border_width;
408         xwc.sibling = e->xconfigurerequest.above;
409         xwc.stack_mode = e->xconfigurerequest.detail;
410        
411         /* we are not to be held responsible if someone sends us an
412            invalid request! */
413         xerror_set_ignore(TRUE);
414         XConfigureWindow(ob_display, window,
415                          e->xconfigurerequest.value_mask, &xwc);
416         xerror_set_ignore(FALSE);
417     }
418
419     if (moveresize_in_progress)
420         if (e->type == MotionNotify || e->type == ButtonRelease ||
421             e->type == ButtonPress ||
422             e->type == KeyPress || e->type == KeyRelease) {
423             moveresize_event(e);
424
425             return; /* no dispatch! */
426             
427         }
428
429     /* user input (action-bound) events */
430     /*
431     if (e->type == ButtonPress || e->type == ButtonRelease ||
432         e->type == MotionNotify)
433         mouse_event(e, client);
434     else if (e->type == KeyPress || e->type == KeyRelease)
435         ;
436     */
437
438     /* dispatch the event to registered handlers */
439     dispatch_x(e, client);
440 }
441
442 static void event_handle_root(XEvent *e)
443 {
444     Atom msgtype;
445      
446     switch(e->type) {
447     case ClientMessage:
448         if (e->xclient.format != 32) break;
449
450         msgtype = e->xclient.message_type;
451         if (msgtype == prop_atoms.net_current_desktop) {
452             unsigned int d = e->xclient.data.l[0];
453             if (d < screen_num_desktops)
454                 screen_set_desktop(d);
455         } else if (msgtype == prop_atoms.net_number_of_desktops) {
456             unsigned int d = e->xclient.data.l[0];
457             if (d > 0)
458                 screen_set_num_desktops(d);
459         } else if (msgtype == prop_atoms.net_showing_desktop) {
460             screen_show_desktop(e->xclient.data.l[0] != 0);
461         }
462         break;
463     case PropertyNotify:
464         if (e->xproperty.atom == prop_atoms.net_desktop_names)
465             screen_update_desktop_names();
466         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
467             screen_update_layout();
468         break;
469     case ConfigureNotify:
470 #ifdef XRANDR
471         XRRUpdateConfiguration(e);
472 #endif
473         if (e->xconfigure.width != screen_physical_size.width ||
474             e->xconfigure.height != screen_physical_size.height)
475             screen_resize(e->xconfigure.width, e->xconfigure.height);
476         break;
477     default:
478         ;
479 #ifdef VIDMODE
480         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
481             g_message("VIDMODE EVENT");
482         }
483 #endif
484     }
485 }
486
487 static void event_handle_client(Client *client, XEvent *e)
488 {
489     XEvent ce;
490     Atom msgtype;
491     int i=0;
492      
493     switch (e->type) {
494     case ButtonPress:
495     case ButtonRelease:
496         switch (frame_context(client, e->xbutton.window)) {
497         case Context_Maximize:
498             client->frame->max_press = (e->type == ButtonPress);
499             framerender_frame(client->frame);
500             break;
501         case Context_Close:
502             client->frame->close_press = (e->type == ButtonPress);
503             framerender_frame(client->frame);
504             break;
505         case Context_Iconify:
506             client->frame->iconify_press = (e->type == ButtonPress);
507             framerender_frame(client->frame);
508             break;
509         case Context_AllDesktops:
510             client->frame->desk_press = (e->type == ButtonPress);
511             framerender_frame(client->frame);
512             break; 
513         case Context_Shade:
514             client->frame->shade_press = (e->type == ButtonPress);
515             framerender_frame(client->frame);
516             break;
517         default:
518             /* nothing changes with clicks for any other contexts */
519             break;
520         }
521         break;
522     case FocusIn:
523 #ifdef DEBUG_FOCUS
524         g_message("FocusIn on client for %lx", client->window);
525 #endif
526         focus_set_client(client);
527         frame_adjust_focus(client->frame, TRUE);
528         break;
529     case FocusOut:
530 #ifdef DEBUG_FOCUS
531         g_message("FocusOut on client for %lx", client->window);
532 #endif
533         /* are we a fullscreen window or a transient of one? (checks layer)
534            if we are then we need to be iconified since we are losing focus
535          */
536         if (client->layer == Layer_Fullscreen && !client->iconic &&
537             !client_search_focus_tree_full(client))
538             /* iconify fullscreen windows when they and their transients
539                aren't focused */
540             client_iconify(client, TRUE, TRUE);
541         frame_adjust_focus(client->frame, FALSE);
542         break;
543     case EnterNotify:
544         if (client_normal(client)) {
545             if (ob_state == State_Starting) {
546                 /* move it to the top of the focus order */
547                 guint desktop = client->desktop;
548                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
549                 focus_order[desktop] = g_list_remove(focus_order[desktop],
550                                                      client);
551                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
552                                                       client);
553             } else if (config_focus_follow) {
554 #ifdef DEBUG_FOCUS
555                 g_message("EnterNotify on %lx, focusing window",
556                           client->window);
557 #endif
558                 client_focus(client);
559             }
560         }
561         break;
562     case ConfigureRequest:
563         /* compress these */
564         while (XCheckTypedWindowEvent(ob_display, client->window,
565                                       ConfigureRequest, &ce)) {
566             ++i;
567             /* XXX if this causes bad things.. we can compress config req's
568                with the same mask. */
569             e->xconfigurerequest.value_mask |=
570                 ce.xconfigurerequest.value_mask;
571             if (ce.xconfigurerequest.value_mask & CWX)
572                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
573             if (ce.xconfigurerequest.value_mask & CWY)
574                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
575             if (ce.xconfigurerequest.value_mask & CWWidth)
576                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
577             if (ce.xconfigurerequest.value_mask & CWHeight)
578                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
579             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
580                 e->xconfigurerequest.border_width =
581                     ce.xconfigurerequest.border_width;
582             if (ce.xconfigurerequest.value_mask & CWStackMode)
583                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
584         }
585
586         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
587         if (client->iconic || client->shaded) return;
588
589         if (e->xconfigurerequest.value_mask & CWBorderWidth)
590             client->border_width = e->xconfigurerequest.border_width;
591
592         /* resize, then move, as specified in the EWMH section 7.7 */
593         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
594                                                CWX | CWY)) {
595             int x, y, w, h;
596             Corner corner;
597                
598             x = (e->xconfigurerequest.value_mask & CWX) ?
599                 e->xconfigurerequest.x : client->area.x;
600             y = (e->xconfigurerequest.value_mask & CWY) ?
601                 e->xconfigurerequest.y : client->area.y;
602             w = (e->xconfigurerequest.value_mask & CWWidth) ?
603                 e->xconfigurerequest.width : client->area.width;
604             h = (e->xconfigurerequest.value_mask & CWHeight) ?
605                 e->xconfigurerequest.height : client->area.height;
606                
607             switch (client->gravity) {
608             case NorthEastGravity:
609             case EastGravity:
610                 corner = Corner_TopRight;
611                 break;
612             case SouthWestGravity:
613             case SouthGravity:
614                 corner = Corner_BottomLeft;
615                 break;
616             case SouthEastGravity:
617                 corner = Corner_BottomRight;
618                 break;
619             default:     /* NorthWest, Static, etc */
620                 corner = Corner_TopLeft;
621             }
622
623             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
624         }
625
626         if (e->xconfigurerequest.value_mask & CWStackMode) {
627             switch (e->xconfigurerequest.detail) {
628             case Below:
629             case BottomIf:
630                 stacking_lower(client);
631                 break;
632
633             case Above:
634             case TopIf:
635             default:
636                 stacking_raise(client);
637                 break;
638             }
639         }
640         break;
641     case UnmapNotify:
642         if (client->ignore_unmaps) {
643             client->ignore_unmaps--;
644             break;
645         }
646         client_unmanage(client);
647         break;
648     case DestroyNotify:
649         client_unmanage(client);
650         break;
651     case ReparentNotify:
652         /* this is when the client is first taken captive in the frame */
653         if (e->xreparent.parent == client->frame->plate) break;
654
655         /*
656           This event is quite rare and is usually handled in unmapHandler.
657           However, if the window is unmapped when the reparent event occurs,
658           the window manager never sees it because an unmap event is not sent
659           to an already unmapped window.
660         */
661
662         /* we don't want the reparent event, put it back on the stack for the
663            X server to deal with after we unmanage the window */
664         XPutBackEvent(ob_display, e);
665      
666         client_unmanage(client);
667         break;
668     case MapRequest:
669         g_message("MapRequest for 0x%lx", client->window);
670         if (!client->iconic) break; /* this normally doesn't happen, but if it
671                                        does, we don't want it! */
672         if (screen_showing_desktop)
673             screen_show_desktop(FALSE);
674         client_iconify(client, FALSE, TRUE);
675         if (!client->frame->visible)
676             /* if its not visible still, then don't mess with it */
677             break;
678         if (client->shaded)
679             client_shade(client, FALSE);
680         client_focus(client);
681         stacking_raise(client);
682         break;
683     case ClientMessage:
684         /* validate cuz we query stuff off the client here */
685         if (!client_validate(client)) break;
686   
687         if (e->xclient.format != 32) return;
688
689         msgtype = e->xclient.message_type;
690         if (msgtype == prop_atoms.wm_change_state) {
691             /* compress changes into a single change */
692             while (XCheckTypedWindowEvent(ob_display, e->type,
693                                           client->window, &ce)) {
694                 /* XXX: it would be nice to compress ALL messages of a
695                    type, not just messages in a row without other
696                    message types between. */
697                 if (ce.xclient.message_type != msgtype) {
698                     XPutBackEvent(ob_display, &ce);
699                     break;
700                 }
701                 e->xclient = ce.xclient;
702             }
703             client_set_wm_state(client, e->xclient.data.l[0]);
704         } else if (msgtype == prop_atoms.net_wm_desktop) {
705             /* compress changes into a single change */
706             while (XCheckTypedWindowEvent(ob_display, e->type,
707                                           client->window, &ce)) {
708                 /* XXX: it would be nice to compress ALL messages of a
709                    type, not just messages in a row without other
710                    message types between. */
711                 if (ce.xclient.message_type != msgtype) {
712                     XPutBackEvent(ob_display, &ce);
713                     break;
714                 }
715                 e->xclient = ce.xclient;
716             }
717             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
718                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
719                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
720                                    FALSE);
721         } else if (msgtype == prop_atoms.net_wm_state) {
722             /* can't compress these */
723             g_message("net_wm_state %s %ld %ld for 0x%lx",
724                       (e->xclient.data.l[0] == 0 ? "Remove" :
725                        e->xclient.data.l[0] == 1 ? "Add" :
726                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
727                       e->xclient.data.l[1], e->xclient.data.l[2],
728                       client->window);
729             client_set_state(client, e->xclient.data.l[0],
730                              e->xclient.data.l[1], e->xclient.data.l[2]);
731         } else if (msgtype == prop_atoms.net_close_window) {
732             g_message("net_close_window for 0x%lx", client->window);
733             client_close(client);
734         } else if (msgtype == prop_atoms.net_active_window) {
735             g_message("net_active_window for 0x%lx", client->window);
736             client_activate(client);
737         } else if (msgtype == prop_atoms.net_wm_moveresize) {
738             g_message("net_wm_moveresize for 0x%lx", client->window);
739             if ((Atom)e->xclient.data.l[2] ==
740                 prop_atoms.net_wm_moveresize_size_topleft ||
741                 (Atom)e->xclient.data.l[2] ==
742                 prop_atoms.net_wm_moveresize_size_top ||
743                 (Atom)e->xclient.data.l[2] ==
744                 prop_atoms.net_wm_moveresize_size_topright ||
745                 (Atom)e->xclient.data.l[2] ==
746                 prop_atoms.net_wm_moveresize_size_right ||
747                 (Atom)e->xclient.data.l[2] ==
748                 prop_atoms.net_wm_moveresize_size_right ||
749                 (Atom)e->xclient.data.l[2] ==
750                 prop_atoms.net_wm_moveresize_size_bottomright ||
751                 (Atom)e->xclient.data.l[2] ==
752                 prop_atoms.net_wm_moveresize_size_bottom ||
753                 (Atom)e->xclient.data.l[2] ==
754                 prop_atoms.net_wm_moveresize_size_bottomleft ||
755                 (Atom)e->xclient.data.l[2] ==
756                 prop_atoms.net_wm_moveresize_size_left ||
757                 (Atom)e->xclient.data.l[2] ==
758                 prop_atoms.net_wm_moveresize_move ||
759                 (Atom)e->xclient.data.l[2] ==
760                 prop_atoms.net_wm_moveresize_size_keyboard ||
761                 (Atom)e->xclient.data.l[2] ==
762                 prop_atoms.net_wm_moveresize_move_keyboard) {
763
764                 moveresize_start(client, e->xclient.data.l[0],
765                                  e->xclient.data.l[1], e->xclient.data.l[3],
766                                  e->xclient.data.l[2]);
767             }
768         } else if (msgtype == prop_atoms.net_moveresize_window) {
769             int oldg = client->gravity;
770             int tmpg, x, y, w, h;
771
772             if (e->xclient.data.l[0] & 0xff)
773                 tmpg = e->xclient.data.l[0] & 0xff;
774             else
775                 tmpg = oldg;
776
777             if (e->xclient.data.l[0] & 1 << 8)
778                 x = e->xclient.data.l[1];
779             else
780                 x = client->area.x;
781             if (e->xclient.data.l[0] & 1 << 9)
782                 y = e->xclient.data.l[2];
783             else
784                 y = client->area.y;
785             if (e->xclient.data.l[0] & 1 << 10)
786                 w = e->xclient.data.l[3];
787             else
788                 w = client->area.y;
789             if (e->xclient.data.l[0] & 1 << 11)
790                 h = e->xclient.data.l[4];
791             else
792                 h = client->area.y;
793             client->gravity = tmpg;
794             client_configure(client, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
795             client->gravity = oldg;
796         }
797         break;
798     case PropertyNotify:
799         /* validate cuz we query stuff off the client here */
800         if (!client_validate(client)) break;
801   
802         /* compress changes to a single property into a single change */
803         while (XCheckTypedWindowEvent(ob_display, e->type,
804                                       client->window, &ce)) {
805             /* XXX: it would be nice to compress ALL changes to a property,
806                not just changes in a row without other props between. */
807             if (ce.xproperty.atom != e->xproperty.atom) {
808                 XPutBackEvent(ob_display, &ce);
809                 break;
810             }
811         }
812
813         msgtype = e->xproperty.atom;
814         if (msgtype == XA_WM_NORMAL_HINTS) {
815             client_update_normal_hints(client);
816             /* normal hints can make a window non-resizable */
817             client_setup_decor_and_functions(client);
818         }
819         else if (msgtype == XA_WM_HINTS)
820             client_update_wmhints(client);
821         else if (msgtype == XA_WM_TRANSIENT_FOR) {
822             client_update_transient_for(client);
823             client_get_type(client);
824             /* type may have changed, so update the layer */
825             client_calc_layer(client);
826             client_setup_decor_and_functions(client);
827         }
828         else if (msgtype == prop_atoms.net_wm_name ||
829                  msgtype == prop_atoms.wm_name ||
830                  msgtype == prop_atoms.net_wm_icon_name ||
831                  msgtype == prop_atoms.wm_icon_name)
832             client_update_title(client);
833         else if (msgtype == prop_atoms.wm_class)
834             client_update_class(client);
835         else if (msgtype == prop_atoms.wm_protocols) {
836             client_update_protocols(client);
837             client_setup_decor_and_functions(client);
838         }
839         else if (msgtype == prop_atoms.net_wm_strut)
840             client_update_strut(client);
841         else if (msgtype == prop_atoms.net_wm_icon)
842             client_update_icons(client);
843         else if (msgtype == prop_atoms.kwm_win_icon)
844             client_update_kwm_icon(client);
845     default:
846         ;
847 #ifdef SHAPE
848         if (extensions_shape && e->type == extensions_shape_event_basep) {
849             client->shaped = ((XShapeEvent*)e)->shaped;
850             frame_adjust_shape(client->frame);
851         }
852 #endif
853     }
854 }
855
856 static void event_handle_menu(Menu *menu, XEvent *e)
857 {
858     MenuEntry *entry;
859
860     g_message("EVENT %d", e->type);
861     switch (e->type) {
862     case ButtonPress:
863         g_message("BUTTON PRESS");
864         if (e->xbutton.button == 3)
865             menu_hide(menu);
866         break;
867     case ButtonRelease:
868         g_message("BUTTON RELEASED");
869         if (!menu->shown) break;
870
871 /*        grab_pointer_window(FALSE, None, menu->frame);*/
872
873         entry = menu_find_entry(menu, e->xbutton.window);
874         if (entry) {
875             int junk;
876             Window wjunk;
877             guint ujunk, b, w, h;
878             XGetGeometry(ob_display, e->xbutton.window,
879                          &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
880             if (e->xbutton.x >= (signed)-b &&
881                 e->xbutton.y >= (signed)-b &&
882                 e->xbutton.x < (signed)(w+b) &&
883                 e->xbutton.y < (signed)(h+b)) {
884                 menu_entry_fire(entry);
885             }
886         
887         break;
888     case EnterNotify:
889     case LeaveNotify:
890         g_message("enter/leave");
891         entry = menu_find_entry(menu, e->xcrossing.window);
892         if (entry) {
893             if (menu->mouseover)
894                 menu->mouseover(entry, e->type == EnterNotify);
895             else
896                 menu_control_mouseover(entry, e->type == EnterNotify);
897             
898             menu_entry_render(entry);
899         }
900         break;
901         }
902     }
903 }