yet MORE changes to how focusin/out's are handled. more edge case fixing, this time...
[mikachu/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     default:
148 #ifdef XKB
149         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
150             switch (((XkbAnyEvent*)&e)->xkb_type) {
151             case XkbBellNotify:
152                 window = ((XkbBellNotifyEvent*)&e)->window;
153             default:
154                 window = None;
155             }
156         } else
157 #endif
158             window = e->xany.window;
159     }
160     return window;
161 }
162
163 static void event_set_lasttime(XEvent *e)
164 {
165     /* grab the lasttime and hack up the state */
166     switch (e->type) {
167     case ButtonPress:
168     case ButtonRelease:
169         event_lasttime = e->xbutton.time;
170         break;
171     case KeyPress:
172         event_lasttime = e->xkey.time;
173         break;
174     case KeyRelease:
175         event_lasttime = e->xkey.time;
176         break;
177     case MotionNotify:
178         event_lasttime = e->xmotion.time;
179         break;
180     case PropertyNotify:
181         event_lasttime = e->xproperty.time;
182         break;
183     case EnterNotify:
184     case LeaveNotify:
185         event_lasttime = e->xcrossing.time;
186         break;
187     default:
188         event_lasttime = CurrentTime;
189         break;
190     }
191 }
192
193 #define STRIP_MODS(s) \
194         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
195         /* kill off the Button1Mask etc, only want the modifiers */ \
196         s &= (ControlMask | ShiftMask | Mod1Mask | \
197               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
198
199 static void event_hack_mods(XEvent *e)
200 {
201     KeyCode *kp;
202     int i, k;
203
204     switch (e->type) {
205     case ButtonPress:
206     case ButtonRelease:
207         STRIP_MODS(e->xbutton.state);
208         break;
209     case KeyPress:
210         STRIP_MODS(e->xkey.state);
211         break;
212     case KeyRelease:
213         STRIP_MODS(e->xkey.state);
214         /* remove from the state the mask of the modifier being released, if
215            it is a modifier key being released (this is a little ugly..) */
216         kp = modmap->modifiermap;
217         for (i = 0; i < mask_table_size; ++i) {
218             for (k = 0; k < modmap->max_keypermod; ++k) {
219                 if (*kp == e->xkey.keycode) { /* found the keycode */
220                     /* remove the mask for it */
221                     e->xkey.state &= ~mask_table[i];
222                     /* cause the first loop to break; */
223                     i = mask_table_size;
224                     break; /* get outta here! */
225                 }
226                 ++kp;
227             }
228         }
229         break;
230     case MotionNotify:
231         STRIP_MODS(e->xmotion.state);
232         /* compress events */
233         {
234             XEvent ce;
235             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
236                                           e->type, &ce)) {
237                 e->xmotion.x_root = ce.xmotion.x_root;
238                 e->xmotion.y_root = ce.xmotion.y_root;
239             }
240         }
241         break;
242     }
243 }
244
245 static gboolean event_ignore(XEvent *e, Client *client)
246 {
247     switch(e->type) {
248     case FocusIn:
249         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
250            because of RevertToPointerRoot. If the focus ends up reverting to
251            pointer root on a workspace change, then the FocusIn event that we
252            want will be of type NotifyAncestor. This situation does not occur
253            for FocusOut, so it is safely ignored there.
254         */
255         if (INVALID_FOCUSIN(e) ||
256             client == NULL) {
257 #ifdef DEBUG_FOCUS
258         g_message("FocusIn on %lx mode %d detail %d IGNORED", e->xfocus.window,
259                   e->xfocus.mode, e->xfocus.detail);
260 #endif
261             /* says a client was not found for the event (or a valid FocusIn
262                event was not found.
263             */
264             e->xfocus.window = None;
265             return TRUE;
266         }
267
268 #ifdef DEBUG_FOCUS
269         g_message("FocusIn on %lx mode %d detail %d", e->xfocus.window,
270                   e->xfocus.mode, e->xfocus.detail);
271 #endif
272         break;
273     case FocusOut:
274         if (INVALID_FOCUSOUT(e)) {
275 #ifdef DEBUG_FOCUS
276         g_message("FocusOut on %lx mode %d detail %d IGNORED",
277                   e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
278 #endif
279             return TRUE;
280         }
281
282 #ifdef DEBUG_FOCUS
283         g_message("FocusOut on %lx mode %d detail %d",
284                   e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
285 #endif
286
287         /* Try process a FocusIn first, and if a legit one isn't found, then
288            do the fallback shiznit. */
289         {
290             XEvent fe;
291             gboolean fallback = TRUE;
292
293             while (TRUE) {
294                 if (!XCheckTypedEvent(ob_display, FocusOut, &fe))
295                     if (!XCheckTypedEvent(ob_display, FocusIn, &fe))
296                         break;
297                 if (fe.type == FocusOut) {
298 #ifdef DEBUG_FOCUS
299                     g_message("found pending FocusOut");
300 #endif
301                     if (!INVALID_FOCUSOUT(&fe)) {
302                         /* if there is a VALID FocusOut still coming, don't
303                            fallback focus yet, we'll deal with it then */
304                         XPutBackEvent(ob_display, &fe);
305                         fallback = FALSE;
306                         break;
307                     }
308                 } else {
309 #ifdef DEBUG_FOCUS
310                     g_message("found pending FocusIn");
311 #endif
312                     /* once all the FocusOut's have been dealt with, if there
313                        is a FocusIn still left and it is valid, then use it */
314                     event_process(&fe);
315                     /* secret magic way of event_process telling us that no
316                        client was found for the FocusIn event. ^_^ */
317                     if (fe.xfocus.window != None) {
318                         fallback = FALSE;
319                         break;
320                     }
321                 }
322             }
323             if (fallback) {
324 #ifdef DEBUG_FOCUS
325                 g_message("no valid FocusIn and no FocusOut events found, "
326                           "falling back");
327 #endif
328                 focus_fallback(Fallback_NoFocus);
329             }
330         }
331         break;
332     case EnterNotify:
333     case LeaveNotify:
334         /* NotifyUngrab occurs when a mouse button is released and the event is
335            caused, like when lowering a window */
336         /* NotifyVirtual occurs when ungrabbing the pointer */
337         if (e->xcrossing.mode == NotifyGrab ||
338             e->xcrossing.detail == NotifyInferior ||
339             (e->xcrossing.mode == NotifyUngrab &&
340              e->xcrossing.detail == NotifyVirtual))
341             return TRUE;
342         break;
343     }
344     return FALSE;
345 }
346
347 static void event_process(XEvent *e)
348 {
349     Window window;
350     Client *client;
351     Menu *menu = NULL;
352
353     window = event_get_window(e);
354     if (!(client = g_hash_table_lookup(client_map, &window)))
355         menu = g_hash_table_lookup(menu_map, &window);
356     event_set_lasttime(e);
357     event_hack_mods(e);
358     if (event_ignore(e, client))
359         return;
360
361     /* deal with it in the kernel */
362     if (menu) {
363         event_handle_menu(menu, e);
364         return;
365     } else if (client)
366         event_handle_client(client, e);
367     else if (window == ob_root)
368         event_handle_root(e);
369     else if (e->type == MapRequest)
370         client_manage(window);
371     else if (e->type == ConfigureRequest) {
372         /* unhandled configure requests must be used to configure the
373            window directly */
374         XWindowChanges xwc;
375                
376         xwc.x = e->xconfigurerequest.x;
377         xwc.y = e->xconfigurerequest.y;
378         xwc.width = e->xconfigurerequest.width;
379         xwc.height = e->xconfigurerequest.height;
380         xwc.border_width = e->xconfigurerequest.border_width;
381         xwc.sibling = e->xconfigurerequest.above;
382         xwc.stack_mode = e->xconfigurerequest.detail;
383        
384         /* we are not to be held responsible if someone sends us an
385            invalid request! */
386         xerror_set_ignore(TRUE);
387         XConfigureWindow(ob_display, window,
388                          e->xconfigurerequest.value_mask, &xwc);
389         xerror_set_ignore(FALSE);
390     }
391
392     if (moveresize_in_progress)
393         if (e->type == MotionNotify || e->type == ButtonRelease ||
394             e->type == ButtonPress ||
395             e->type == KeyPress || e->type == KeyRelease) {
396             moveresize_event(e);
397             return; /* no dispatch! */
398         }
399
400     /* user input (action-bound) events */
401     /*
402     if (e->type == ButtonPress || e->type == ButtonRelease ||
403         e->type == MotionNotify)
404         mouse_event(e, client);
405     else if (e->type == KeyPress || e->type == KeyRelease)
406         ;
407     */
408
409     /* dispatch the event to registered handlers */
410     dispatch_x(e, client);
411 }
412
413 static void event_handle_root(XEvent *e)
414 {
415     Atom msgtype;
416      
417     switch(e->type) {
418     case ClientMessage:
419         if (e->xclient.format != 32) break;
420
421         msgtype = e->xclient.message_type;
422         if (msgtype == prop_atoms.net_current_desktop) {
423             unsigned int d = e->xclient.data.l[0];
424             if (d < screen_num_desktops)
425                 screen_set_desktop(d);
426         } else if (msgtype == prop_atoms.net_number_of_desktops) {
427             unsigned int d = e->xclient.data.l[0];
428             if (d > 0)
429                 screen_set_num_desktops(d);
430         } else if (msgtype == prop_atoms.net_showing_desktop) {
431             screen_show_desktop(e->xclient.data.l[0] != 0);
432         }
433         break;
434     case PropertyNotify:
435         if (e->xproperty.atom == prop_atoms.net_desktop_names)
436             screen_update_desktop_names();
437         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
438             screen_update_layout();
439         break;
440     }
441 }
442
443 static void event_handle_client(Client *client, XEvent *e)
444 {
445     XEvent ce;
446     Atom msgtype;
447     int i=0;
448      
449     switch (e->type) {
450     case ButtonPress:
451     case ButtonRelease:
452         switch (frame_context(client, e->xbutton.window)) {
453         case Context_Maximize:
454             client->frame->max_press = (e->type == ButtonPress);
455             framerender_frame(client->frame);
456             break;
457         case Context_Close:
458             client->frame->close_press = (e->type == ButtonPress);
459             framerender_frame(client->frame);
460             break;
461         case Context_Iconify:
462             client->frame->iconify_press = (e->type == ButtonPress);
463             framerender_frame(client->frame);
464             break;
465         case Context_AllDesktops:
466             client->frame->desk_press = (e->type == ButtonPress);
467             framerender_frame(client->frame);
468             break; 
469         case Context_Shade:
470             client->frame->shade_press = (e->type == ButtonPress);
471             framerender_frame(client->frame);
472             break;
473         default:
474             /* nothing changes with clicks for any other contexts */
475             break;
476         }
477         break;
478     case FocusIn:
479         focus_set_client(client);
480     case FocusOut:
481 #ifdef DEBUG_FOCUS
482         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
483                   client->window);
484 #endif
485         /* focus state can affect the stacking layer */
486         client_calc_layer(client);
487         frame_adjust_focus(client->frame);
488         break;
489     case EnterNotify:
490         if (client_normal(client)) {
491             if (ob_state == State_Starting) {
492                 /* move it to the top of the focus order */
493                 guint desktop = client->desktop;
494                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
495                 focus_order[desktop] = g_list_remove(focus_order[desktop],
496                                                      client);
497                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
498                                                       client);
499             } else if (config_focus_follow) {
500 #ifdef DEBUG_FOCUS
501                 g_message("EnterNotify on %lx, focusing window",
502                           client->window);
503 #endif
504                 client_focus(client);
505             }
506         }
507         break;
508     case ConfigureRequest:
509         /* compress these */
510         while (XCheckTypedWindowEvent(ob_display, client->window,
511                                       ConfigureRequest, &ce)) {
512             ++i;
513             /* XXX if this causes bad things.. we can compress config req's
514                with the same mask. */
515             e->xconfigurerequest.value_mask |=
516                 ce.xconfigurerequest.value_mask;
517             if (ce.xconfigurerequest.value_mask & CWX)
518                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
519             if (ce.xconfigurerequest.value_mask & CWY)
520                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
521             if (ce.xconfigurerequest.value_mask & CWWidth)
522                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
523             if (ce.xconfigurerequest.value_mask & CWHeight)
524                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
525             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
526                 e->xconfigurerequest.border_width =
527                     ce.xconfigurerequest.border_width;
528             if (ce.xconfigurerequest.value_mask & CWStackMode)
529                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
530         }
531
532         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
533         if (client->iconic || client->shaded) return;
534
535         if (e->xconfigurerequest.value_mask & CWBorderWidth)
536             client->border_width = e->xconfigurerequest.border_width;
537
538         /* resize, then move, as specified in the EWMH section 7.7 */
539         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
540                                                CWX | CWY)) {
541             int x, y, w, h;
542             Corner corner;
543                
544             x = (e->xconfigurerequest.value_mask & CWX) ?
545                 e->xconfigurerequest.x : client->area.x;
546             y = (e->xconfigurerequest.value_mask & CWY) ?
547                 e->xconfigurerequest.y : client->area.y;
548             w = (e->xconfigurerequest.value_mask & CWWidth) ?
549                 e->xconfigurerequest.width : client->area.width;
550             h = (e->xconfigurerequest.value_mask & CWHeight) ?
551                 e->xconfigurerequest.height : client->area.height;
552                
553             switch (client->gravity) {
554             case NorthEastGravity:
555             case EastGravity:
556                 corner = Corner_TopRight;
557                 break;
558             case SouthWestGravity:
559             case SouthGravity:
560                 corner = Corner_BottomLeft;
561                 break;
562             case SouthEastGravity:
563                 corner = Corner_BottomRight;
564                 break;
565             default:     /* NorthWest, Static, etc */
566                 corner = Corner_TopLeft;
567             }
568
569             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
570         }
571
572         if (e->xconfigurerequest.value_mask & CWStackMode) {
573             switch (e->xconfigurerequest.detail) {
574             case Below:
575             case BottomIf:
576                 stacking_lower(client);
577                 break;
578
579             case Above:
580             case TopIf:
581             default:
582                 stacking_raise(client);
583                 break;
584             }
585         }
586         break;
587     case UnmapNotify:
588         if (client->ignore_unmaps) {
589             client->ignore_unmaps--;
590             break;
591         }
592         client_unmanage(client);
593         break;
594     case DestroyNotify:
595         client_unmanage(client);
596         break;
597     case ReparentNotify:
598         /* this is when the client is first taken captive in the frame */
599         if (e->xreparent.parent == client->frame->plate) break;
600
601         /*
602           This event is quite rare and is usually handled in unmapHandler.
603           However, if the window is unmapped when the reparent event occurs,
604           the window manager never sees it because an unmap event is not sent
605           to an already unmapped window.
606         */
607
608         /* we don't want the reparent event, put it back on the stack for the
609            X server to deal with after we unmanage the window */
610         XPutBackEvent(ob_display, e);
611      
612         client_unmanage(client);
613         break;
614     case MapRequest:
615         g_message("MapRequest for 0x%lx", client->window);
616         if (!client->iconic) break; /* this normally doesn't happen, but if it
617                                        does, we don't want it! */
618         if (screen_showing_desktop)
619             screen_show_desktop(FALSE);
620         client_iconify(client, FALSE, TRUE);
621         if (!client->frame->visible)
622             /* if its not visible still, then don't mess with it */
623             break;
624         if (client->shaded)
625             client_shade(client, FALSE);
626         client_focus(client);
627         stacking_raise(client);
628         break;
629     case ClientMessage:
630         /* validate cuz we query stuff off the client here */
631         if (!client_validate(client)) break;
632   
633         if (e->xclient.format != 32) return;
634
635         msgtype = e->xclient.message_type;
636         if (msgtype == prop_atoms.wm_change_state) {
637             /* compress changes into a single change */
638             while (XCheckTypedWindowEvent(ob_display, e->type,
639                                           client->window, &ce)) {
640                 /* XXX: it would be nice to compress ALL messages of a
641                    type, not just messages in a row without other
642                    message types between. */
643                 if (ce.xclient.message_type != msgtype) {
644                     XPutBackEvent(ob_display, &ce);
645                     break;
646                 }
647                 e->xclient = ce.xclient;
648             }
649             client_set_wm_state(client, e->xclient.data.l[0]);
650         } else if (msgtype == prop_atoms.net_wm_desktop) {
651             /* compress changes into a single change */
652             while (XCheckTypedWindowEvent(ob_display, e->type,
653                                           client->window, &ce)) {
654                 /* XXX: it would be nice to compress ALL messages of a
655                    type, not just messages in a row without other
656                    message types between. */
657                 if (ce.xclient.message_type != msgtype) {
658                     XPutBackEvent(ob_display, &ce);
659                     break;
660                 }
661                 e->xclient = ce.xclient;
662             }
663             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
664                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
665                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
666                                    FALSE);
667         } else if (msgtype == prop_atoms.net_wm_state) {
668             /* can't compress these */
669             g_message("net_wm_state %s %ld %ld for 0x%lx",
670                       (e->xclient.data.l[0] == 0 ? "Remove" :
671                        e->xclient.data.l[0] == 1 ? "Add" :
672                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
673                       e->xclient.data.l[1], e->xclient.data.l[2],
674                       client->window);
675             client_set_state(client, e->xclient.data.l[0],
676                              e->xclient.data.l[1], e->xclient.data.l[2]);
677         } else if (msgtype == prop_atoms.net_close_window) {
678             g_message("net_close_window for 0x%lx", client->window);
679             client_close(client);
680         } else if (msgtype == prop_atoms.net_active_window) {
681             g_message("net_active_window for 0x%lx", client->window);
682             if (screen_showing_desktop)
683                 screen_show_desktop(FALSE);
684             if (client->iconic)
685                 client_iconify(client, FALSE, TRUE);
686             else if (!client->frame->visible)
687                 /* if its not visible for other reasons, then don't mess
688                    with it */
689                 break;
690             if (client->shaded)
691                 client_shade(client, FALSE);
692             client_focus(client);
693             stacking_raise(client);
694         } else if (msgtype == prop_atoms.net_wm_moveresize) {
695             g_message("net_wm_moveresize for 0x%lx", client->window);
696             if ((Atom)e->xclient.data.l[2] ==
697                 prop_atoms.net_wm_moveresize_size_topleft ||
698                 (Atom)e->xclient.data.l[2] ==
699                 prop_atoms.net_wm_moveresize_size_top ||
700                 (Atom)e->xclient.data.l[2] ==
701                 prop_atoms.net_wm_moveresize_size_topright ||
702                 (Atom)e->xclient.data.l[2] ==
703                 prop_atoms.net_wm_moveresize_size_right ||
704                 (Atom)e->xclient.data.l[2] ==
705                 prop_atoms.net_wm_moveresize_size_right ||
706                 (Atom)e->xclient.data.l[2] ==
707                 prop_atoms.net_wm_moveresize_size_bottomright ||
708                 (Atom)e->xclient.data.l[2] ==
709                 prop_atoms.net_wm_moveresize_size_bottom ||
710                 (Atom)e->xclient.data.l[2] ==
711                 prop_atoms.net_wm_moveresize_size_bottomleft ||
712                 (Atom)e->xclient.data.l[2] ==
713                 prop_atoms.net_wm_moveresize_size_left ||
714                 (Atom)e->xclient.data.l[2] ==
715                 prop_atoms.net_wm_moveresize_move ||
716                 (Atom)e->xclient.data.l[2] ==
717                 prop_atoms.net_wm_moveresize_size_keyboard ||
718                 (Atom)e->xclient.data.l[2] ==
719                 prop_atoms.net_wm_moveresize_move_keyboard) {
720
721                 moveresize_start(client, e->xclient.data.l[0],
722                                  e->xclient.data.l[1], e->xclient.data.l[3],
723                                  e->xclient.data.l[2]);
724             }
725         } else if (msgtype == prop_atoms.net_moveresize_window) {
726             int oldg = client->gravity;
727             int tmpg, x, y, w, h;
728
729             if (e->xclient.data.l[0] & 0xff)
730                 tmpg = e->xclient.data.l[0] & 0xff;
731             else
732                 tmpg = oldg;
733
734             if (e->xclient.data.l[0] & 1 << 8)
735                 x = e->xclient.data.l[1];
736             else
737                 x = client->area.x;
738             if (e->xclient.data.l[0] & 1 << 9)
739                 y = e->xclient.data.l[2];
740             else
741                 y = client->area.y;
742             if (e->xclient.data.l[0] & 1 << 10)
743                 w = e->xclient.data.l[3];
744             else
745                 w = client->area.y;
746             if (e->xclient.data.l[0] & 1 << 11)
747                 h = e->xclient.data.l[4];
748             else
749                 h = client->area.y;
750             client->gravity = tmpg;
751             client_configure(client, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
752             client->gravity = oldg;
753         }
754         break;
755     case PropertyNotify:
756         /* validate cuz we query stuff off the client here */
757         if (!client_validate(client)) break;
758   
759         /* compress changes to a single property into a single change */
760         while (XCheckTypedWindowEvent(ob_display, e->type,
761                                       client->window, &ce)) {
762             /* XXX: it would be nice to compress ALL changes to a property,
763                not just changes in a row without other props between. */
764             if (ce.xproperty.atom != e->xproperty.atom) {
765                 XPutBackEvent(ob_display, &ce);
766                 break;
767             }
768         }
769
770         msgtype = e->xproperty.atom;
771         if (msgtype == XA_WM_NORMAL_HINTS) {
772             client_update_normal_hints(client);
773             /* normal hints can make a window non-resizable */
774             client_setup_decor_and_functions(client);
775         }
776         else if (msgtype == XA_WM_HINTS)
777             client_update_wmhints(client);
778         else if (msgtype == XA_WM_TRANSIENT_FOR) {
779             client_update_transient_for(client);
780             client_get_type(client);
781             /* type may have changed, so update the layer */
782             client_calc_layer(client);
783             client_setup_decor_and_functions(client);
784         }
785         else if (msgtype == prop_atoms.net_wm_name ||
786                  msgtype == prop_atoms.wm_name)
787             client_update_title(client);
788         else if (msgtype == prop_atoms.net_wm_icon_name ||
789                  msgtype == prop_atoms.wm_icon_name)
790             client_update_icon_title(client);
791         else if (msgtype == prop_atoms.wm_class)
792             client_update_class(client);
793         else if (msgtype == prop_atoms.wm_protocols) {
794             client_update_protocols(client);
795             client_setup_decor_and_functions(client);
796         }
797         else if (msgtype == prop_atoms.net_wm_strut)
798             client_update_strut(client);
799         else if (msgtype == prop_atoms.net_wm_icon)
800             client_update_icons(client);
801         else if (msgtype == prop_atoms.kwm_win_icon)
802             client_update_kwm_icon(client);
803     default:
804         ;
805 #ifdef SHAPE
806         if (extensions_shape && e->type == extensions_shape_event_basep) {
807             client->shaped = ((XShapeEvent*)e)->shaped;
808             frame_adjust_shape(client->frame);
809         }
810 #endif
811     }
812 }
813
814 static void event_handle_menu(Menu *menu, XEvent *e)
815 {
816     MenuEntry *entry;
817
818     g_message("EVENT %d", e->type);
819     switch (e->type) {
820     case ButtonPress:
821         if (e->xbutton.button == 3)
822             menu_hide(menu);
823         break;
824     case ButtonRelease:
825         if (!menu->shown) break;
826
827 /*        grab_pointer_window(FALSE, None, menu->frame);*/
828
829         entry = menu_find_entry(menu, e->xbutton.window);
830         if (entry) {
831             int junk;
832             Window wjunk;
833             guint ujunk, b, w, h;
834             XGetGeometry(ob_display, e->xbutton.window,
835                          &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
836             if (e->xbutton.x >= (signed)-b &&
837                 e->xbutton.y >= (signed)-b &&
838                 e->xbutton.x < (signed)(w+b) &&
839                 e->xbutton.y < (signed)(h+b)) {
840                 menu_entry_fire(entry);
841             }
842         }
843         break;
844     case EnterNotify:
845     case LeaveNotify:
846         g_message("enter/leave");
847         entry = menu_find_entry(menu, e->xcrossing.window);
848         if (entry) {
849             entry->hilite = e->type == EnterNotify;
850             menu_entry_render(entry);
851         }
852         break;
853     }
854 }