add a client_activate function, use it for net_wm_active mesgs and for focus cycling.
[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 #ifdef DEBUG_FOCUS
318                         g_message("focused window got an Out/In back to "
319                                   "itself IGNORED both");
320 #endif
321                         return TRUE;
322                     }
323
324                     /* once all the FocusOut's have been dealt with, if there
325                        is a FocusIn still left and it is valid, then use it */
326                     event_process(&fe);
327                     /* secret magic way of event_process telling us that no
328                        client was found for the FocusIn event. ^_^ */
329                     if (fe.xfocus.window != None) {
330                         fallback = FALSE;
331                         break;
332                     }
333                 }
334             }
335             if (fallback) {
336 #ifdef DEBUG_FOCUS
337                 g_message("no valid FocusIn and no FocusOut events found, "
338                           "falling back");
339 #endif
340                 focus_fallback(Fallback_NoFocus);
341             }
342         }
343         break;
344     case EnterNotify:
345     case LeaveNotify:
346         /* NotifyUngrab occurs when a mouse button is released and the event is
347            caused, like when lowering a window */
348         /* NotifyVirtual occurs when ungrabbing the pointer */
349         if (e->xcrossing.mode == NotifyGrab ||
350             e->xcrossing.detail == NotifyInferior ||
351             (e->xcrossing.mode == NotifyUngrab &&
352              e->xcrossing.detail == NotifyVirtual)) {
353 #ifdef DEBUG_FOCUS
354             g_message("%sNotify mode %d detail %d on %lx IGNORED",
355                       (e->type == EnterNotify ? "Enter" : "Leave"),
356                       e->xcrossing.mode,
357                       e->xcrossing.detail, client?client->window:0);
358 #endif
359             return TRUE;
360         }
361 #ifdef DEBUG_FOCUS
362         g_message("%sNotify mode %d detail %d on %lx",
363                   (e->type == EnterNotify ? "Enter" : "Leave"),
364                   e->xcrossing.mode,
365                   e->xcrossing.detail, client?client->window:0);
366 #endif
367         break;
368     }
369     return FALSE;
370 }
371
372 static void event_process(XEvent *e)
373 {
374     Window window;
375     Client *client;
376     Menu *menu = NULL;
377
378     window = event_get_window(e);
379     if (!(client = g_hash_table_lookup(client_map, &window)))
380         menu = g_hash_table_lookup(menu_map, &window);
381     event_set_lasttime(e);
382     event_hack_mods(e);
383     if (event_ignore(e, client))
384         return;
385
386     /* deal with it in the kernel */
387     if (menu) {
388         event_handle_menu(menu, e);
389         return;
390     } else if (client)
391         event_handle_client(client, e);
392     else if (window == ob_root)
393         event_handle_root(e);
394     else if (e->type == MapRequest)
395         client_manage(window);
396     else if (e->type == ConfigureRequest) {
397         /* unhandled configure requests must be used to configure the
398            window directly */
399         XWindowChanges xwc;
400                
401         xwc.x = e->xconfigurerequest.x;
402         xwc.y = e->xconfigurerequest.y;
403         xwc.width = e->xconfigurerequest.width;
404         xwc.height = e->xconfigurerequest.height;
405         xwc.border_width = e->xconfigurerequest.border_width;
406         xwc.sibling = e->xconfigurerequest.above;
407         xwc.stack_mode = e->xconfigurerequest.detail;
408        
409         /* we are not to be held responsible if someone sends us an
410            invalid request! */
411         xerror_set_ignore(TRUE);
412         XConfigureWindow(ob_display, window,
413                          e->xconfigurerequest.value_mask, &xwc);
414         xerror_set_ignore(FALSE);
415     }
416
417     if (moveresize_in_progress)
418         if (e->type == MotionNotify || e->type == ButtonRelease ||
419             e->type == ButtonPress ||
420             e->type == KeyPress || e->type == KeyRelease) {
421             moveresize_event(e);
422
423             return; /* no dispatch! */
424             
425         }
426
427     /* user input (action-bound) events */
428     /*
429     if (e->type == ButtonPress || e->type == ButtonRelease ||
430         e->type == MotionNotify)
431         mouse_event(e, client);
432     else if (e->type == KeyPress || e->type == KeyRelease)
433         ;
434     */
435
436     /* dispatch the event to registered handlers */
437     dispatch_x(e, client);
438 }
439
440 static void event_handle_root(XEvent *e)
441 {
442     Atom msgtype;
443      
444     switch(e->type) {
445     case ClientMessage:
446         if (e->xclient.format != 32) break;
447
448         msgtype = e->xclient.message_type;
449         if (msgtype == prop_atoms.net_current_desktop) {
450             unsigned int d = e->xclient.data.l[0];
451             if (d < screen_num_desktops)
452                 screen_set_desktop(d);
453         } else if (msgtype == prop_atoms.net_number_of_desktops) {
454             unsigned int d = e->xclient.data.l[0];
455             if (d > 0)
456                 screen_set_num_desktops(d);
457         } else if (msgtype == prop_atoms.net_showing_desktop) {
458             screen_show_desktop(e->xclient.data.l[0] != 0);
459         }
460         break;
461     case PropertyNotify:
462         if (e->xproperty.atom == prop_atoms.net_desktop_names)
463             screen_update_desktop_names();
464         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
465             screen_update_layout();
466         break;
467     case ConfigureNotify:
468 #ifdef XRANDR
469         XRRUpdateConfiguration(e);
470 #endif
471         if (e->xconfigure.width != screen_physical_size.width ||
472             e->xconfigure.height != screen_physical_size.height)
473             screen_resize(e->xconfigure.width, e->xconfigure.height);
474         break;
475     default:
476         ;
477 #ifdef VIDMODE
478         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
479             g_message("VIDMODE EVENT");
480         }
481 #endif
482     }
483 }
484
485 static void event_handle_client(Client *client, XEvent *e)
486 {
487     XEvent ce;
488     Atom msgtype;
489     int i=0;
490      
491     switch (e->type) {
492     case ButtonPress:
493     case ButtonRelease:
494         switch (frame_context(client, e->xbutton.window)) {
495         case Context_Maximize:
496             client->frame->max_press = (e->type == ButtonPress);
497             framerender_frame(client->frame);
498             break;
499         case Context_Close:
500             client->frame->close_press = (e->type == ButtonPress);
501             framerender_frame(client->frame);
502             break;
503         case Context_Iconify:
504             client->frame->iconify_press = (e->type == ButtonPress);
505             framerender_frame(client->frame);
506             break;
507         case Context_AllDesktops:
508             client->frame->desk_press = (e->type == ButtonPress);
509             framerender_frame(client->frame);
510             break; 
511         case Context_Shade:
512             client->frame->shade_press = (e->type == ButtonPress);
513             framerender_frame(client->frame);
514             break;
515         default:
516             /* nothing changes with clicks for any other contexts */
517             break;
518         }
519         break;
520     case FocusIn:
521 #ifdef DEBUG_FOCUS
522         g_message("FocusIn on client for %lx", client->window);
523 #endif
524         focus_set_client(client);
525         frame_adjust_focus(client->frame, TRUE);
526         break;
527     case FocusOut:
528 #ifdef DEBUG_FOCUS
529         g_message("FocusOut on client for %lx", client->window);
530 #endif
531         /* are we a fullscreen window or a transient of one? (checks layer)
532            if we are then we need to be iconified since we are losing focus
533          */
534         if (client->layer == Layer_Fullscreen && !client->iconic &&
535             !client_search_focus_tree_full(client))
536             /* iconify fullscreen windows when they and their transients
537                aren't focused */
538             client_iconify(client, TRUE, TRUE);
539         frame_adjust_focus(client->frame, FALSE);
540         break;
541     case EnterNotify:
542         if (client_normal(client)) {
543             if (ob_state == State_Starting) {
544                 /* move it to the top of the focus order */
545                 guint desktop = client->desktop;
546                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
547                 focus_order[desktop] = g_list_remove(focus_order[desktop],
548                                                      client);
549                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
550                                                       client);
551             } else if (config_focus_follow) {
552 #ifdef DEBUG_FOCUS
553                 g_message("EnterNotify on %lx, focusing window",
554                           client->window);
555 #endif
556                 client_focus(client);
557             }
558         }
559         break;
560     case ConfigureRequest:
561         /* compress these */
562         while (XCheckTypedWindowEvent(ob_display, client->window,
563                                       ConfigureRequest, &ce)) {
564             ++i;
565             /* XXX if this causes bad things.. we can compress config req's
566                with the same mask. */
567             e->xconfigurerequest.value_mask |=
568                 ce.xconfigurerequest.value_mask;
569             if (ce.xconfigurerequest.value_mask & CWX)
570                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
571             if (ce.xconfigurerequest.value_mask & CWY)
572                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
573             if (ce.xconfigurerequest.value_mask & CWWidth)
574                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
575             if (ce.xconfigurerequest.value_mask & CWHeight)
576                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
577             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
578                 e->xconfigurerequest.border_width =
579                     ce.xconfigurerequest.border_width;
580             if (ce.xconfigurerequest.value_mask & CWStackMode)
581                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
582         }
583
584         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
585         if (client->iconic || client->shaded) return;
586
587         if (e->xconfigurerequest.value_mask & CWBorderWidth)
588             client->border_width = e->xconfigurerequest.border_width;
589
590         /* resize, then move, as specified in the EWMH section 7.7 */
591         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
592                                                CWX | CWY)) {
593             int x, y, w, h;
594             Corner corner;
595                
596             x = (e->xconfigurerequest.value_mask & CWX) ?
597                 e->xconfigurerequest.x : client->area.x;
598             y = (e->xconfigurerequest.value_mask & CWY) ?
599                 e->xconfigurerequest.y : client->area.y;
600             w = (e->xconfigurerequest.value_mask & CWWidth) ?
601                 e->xconfigurerequest.width : client->area.width;
602             h = (e->xconfigurerequest.value_mask & CWHeight) ?
603                 e->xconfigurerequest.height : client->area.height;
604                
605             switch (client->gravity) {
606             case NorthEastGravity:
607             case EastGravity:
608                 corner = Corner_TopRight;
609                 break;
610             case SouthWestGravity:
611             case SouthGravity:
612                 corner = Corner_BottomLeft;
613                 break;
614             case SouthEastGravity:
615                 corner = Corner_BottomRight;
616                 break;
617             default:     /* NorthWest, Static, etc */
618                 corner = Corner_TopLeft;
619             }
620
621             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
622         }
623
624         if (e->xconfigurerequest.value_mask & CWStackMode) {
625             switch (e->xconfigurerequest.detail) {
626             case Below:
627             case BottomIf:
628                 stacking_lower(client);
629                 break;
630
631             case Above:
632             case TopIf:
633             default:
634                 stacking_raise(client);
635                 break;
636             }
637         }
638         break;
639     case UnmapNotify:
640         if (client->ignore_unmaps) {
641             client->ignore_unmaps--;
642             break;
643         }
644         client_unmanage(client);
645         break;
646     case DestroyNotify:
647         client_unmanage(client);
648         break;
649     case ReparentNotify:
650         /* this is when the client is first taken captive in the frame */
651         if (e->xreparent.parent == client->frame->plate) break;
652
653         /*
654           This event is quite rare and is usually handled in unmapHandler.
655           However, if the window is unmapped when the reparent event occurs,
656           the window manager never sees it because an unmap event is not sent
657           to an already unmapped window.
658         */
659
660         /* we don't want the reparent event, put it back on the stack for the
661            X server to deal with after we unmanage the window */
662         XPutBackEvent(ob_display, e);
663      
664         client_unmanage(client);
665         break;
666     case MapRequest:
667         g_message("MapRequest for 0x%lx", client->window);
668         if (!client->iconic) break; /* this normally doesn't happen, but if it
669                                        does, we don't want it! */
670         if (screen_showing_desktop)
671             screen_show_desktop(FALSE);
672         client_iconify(client, FALSE, TRUE);
673         if (!client->frame->visible)
674             /* if its not visible still, then don't mess with it */
675             break;
676         if (client->shaded)
677             client_shade(client, FALSE);
678         client_focus(client);
679         stacking_raise(client);
680         break;
681     case ClientMessage:
682         /* validate cuz we query stuff off the client here */
683         if (!client_validate(client)) break;
684   
685         if (e->xclient.format != 32) return;
686
687         msgtype = e->xclient.message_type;
688         if (msgtype == prop_atoms.wm_change_state) {
689             /* compress changes into a single change */
690             while (XCheckTypedWindowEvent(ob_display, e->type,
691                                           client->window, &ce)) {
692                 /* XXX: it would be nice to compress ALL messages of a
693                    type, not just messages in a row without other
694                    message types between. */
695                 if (ce.xclient.message_type != msgtype) {
696                     XPutBackEvent(ob_display, &ce);
697                     break;
698                 }
699                 e->xclient = ce.xclient;
700             }
701             client_set_wm_state(client, e->xclient.data.l[0]);
702         } else if (msgtype == prop_atoms.net_wm_desktop) {
703             /* compress changes into a single change */
704             while (XCheckTypedWindowEvent(ob_display, e->type,
705                                           client->window, &ce)) {
706                 /* XXX: it would be nice to compress ALL messages of a
707                    type, not just messages in a row without other
708                    message types between. */
709                 if (ce.xclient.message_type != msgtype) {
710                     XPutBackEvent(ob_display, &ce);
711                     break;
712                 }
713                 e->xclient = ce.xclient;
714             }
715             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
716                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
717                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
718                                    FALSE);
719         } else if (msgtype == prop_atoms.net_wm_state) {
720             /* can't compress these */
721             g_message("net_wm_state %s %ld %ld for 0x%lx",
722                       (e->xclient.data.l[0] == 0 ? "Remove" :
723                        e->xclient.data.l[0] == 1 ? "Add" :
724                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
725                       e->xclient.data.l[1], e->xclient.data.l[2],
726                       client->window);
727             client_set_state(client, e->xclient.data.l[0],
728                              e->xclient.data.l[1], e->xclient.data.l[2]);
729         } else if (msgtype == prop_atoms.net_close_window) {
730             g_message("net_close_window for 0x%lx", client->window);
731             client_close(client);
732         } else if (msgtype == prop_atoms.net_active_window) {
733             g_message("net_active_window for 0x%lx", client->window);
734             client_activate(client);
735         } else if (msgtype == prop_atoms.net_wm_moveresize) {
736             g_message("net_wm_moveresize for 0x%lx", client->window);
737             if ((Atom)e->xclient.data.l[2] ==
738                 prop_atoms.net_wm_moveresize_size_topleft ||
739                 (Atom)e->xclient.data.l[2] ==
740                 prop_atoms.net_wm_moveresize_size_top ||
741                 (Atom)e->xclient.data.l[2] ==
742                 prop_atoms.net_wm_moveresize_size_topright ||
743                 (Atom)e->xclient.data.l[2] ==
744                 prop_atoms.net_wm_moveresize_size_right ||
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_bottomright ||
749                 (Atom)e->xclient.data.l[2] ==
750                 prop_atoms.net_wm_moveresize_size_bottom ||
751                 (Atom)e->xclient.data.l[2] ==
752                 prop_atoms.net_wm_moveresize_size_bottomleft ||
753                 (Atom)e->xclient.data.l[2] ==
754                 prop_atoms.net_wm_moveresize_size_left ||
755                 (Atom)e->xclient.data.l[2] ==
756                 prop_atoms.net_wm_moveresize_move ||
757                 (Atom)e->xclient.data.l[2] ==
758                 prop_atoms.net_wm_moveresize_size_keyboard ||
759                 (Atom)e->xclient.data.l[2] ==
760                 prop_atoms.net_wm_moveresize_move_keyboard) {
761
762                 moveresize_start(client, e->xclient.data.l[0],
763                                  e->xclient.data.l[1], e->xclient.data.l[3],
764                                  e->xclient.data.l[2]);
765             }
766         } else if (msgtype == prop_atoms.net_moveresize_window) {
767             int oldg = client->gravity;
768             int tmpg, x, y, w, h;
769
770             if (e->xclient.data.l[0] & 0xff)
771                 tmpg = e->xclient.data.l[0] & 0xff;
772             else
773                 tmpg = oldg;
774
775             if (e->xclient.data.l[0] & 1 << 8)
776                 x = e->xclient.data.l[1];
777             else
778                 x = client->area.x;
779             if (e->xclient.data.l[0] & 1 << 9)
780                 y = e->xclient.data.l[2];
781             else
782                 y = client->area.y;
783             if (e->xclient.data.l[0] & 1 << 10)
784                 w = e->xclient.data.l[3];
785             else
786                 w = client->area.y;
787             if (e->xclient.data.l[0] & 1 << 11)
788                 h = e->xclient.data.l[4];
789             else
790                 h = client->area.y;
791             client->gravity = tmpg;
792             client_configure(client, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
793             client->gravity = oldg;
794         }
795         break;
796     case PropertyNotify:
797         /* validate cuz we query stuff off the client here */
798         if (!client_validate(client)) break;
799   
800         /* compress changes to a single property into a single change */
801         while (XCheckTypedWindowEvent(ob_display, e->type,
802                                       client->window, &ce)) {
803             /* XXX: it would be nice to compress ALL changes to a property,
804                not just changes in a row without other props between. */
805             if (ce.xproperty.atom != e->xproperty.atom) {
806                 XPutBackEvent(ob_display, &ce);
807                 break;
808             }
809         }
810
811         msgtype = e->xproperty.atom;
812         if (msgtype == XA_WM_NORMAL_HINTS) {
813             client_update_normal_hints(client);
814             /* normal hints can make a window non-resizable */
815             client_setup_decor_and_functions(client);
816         }
817         else if (msgtype == XA_WM_HINTS)
818             client_update_wmhints(client);
819         else if (msgtype == XA_WM_TRANSIENT_FOR) {
820             client_update_transient_for(client);
821             client_get_type(client);
822             /* type may have changed, so update the layer */
823             client_calc_layer(client);
824             client_setup_decor_and_functions(client);
825         }
826         else if (msgtype == prop_atoms.net_wm_name ||
827                  msgtype == prop_atoms.wm_name ||
828                  msgtype == prop_atoms.net_wm_icon_name ||
829                  msgtype == prop_atoms.wm_icon_name)
830             client_update_title(client);
831         else if (msgtype == prop_atoms.wm_class)
832             client_update_class(client);
833         else if (msgtype == prop_atoms.wm_protocols) {
834             client_update_protocols(client);
835             client_setup_decor_and_functions(client);
836         }
837         else if (msgtype == prop_atoms.net_wm_strut)
838             client_update_strut(client);
839         else if (msgtype == prop_atoms.net_wm_icon)
840             client_update_icons(client);
841         else if (msgtype == prop_atoms.kwm_win_icon)
842             client_update_kwm_icon(client);
843     default:
844         ;
845 #ifdef SHAPE
846         if (extensions_shape && e->type == extensions_shape_event_basep) {
847             client->shaped = ((XShapeEvent*)e)->shaped;
848             frame_adjust_shape(client->frame);
849         }
850 #endif
851     }
852 }
853
854 static void event_handle_menu(Menu *menu, XEvent *e)
855 {
856     MenuEntry *entry;
857
858     g_message("EVENT %d", e->type);
859     switch (e->type) {
860     case ButtonPress:
861         if (e->xbutton.button == 3)
862             menu_hide(menu);
863         break;
864     case ButtonRelease:
865         if (!menu->shown) break;
866
867 /*        grab_pointer_window(FALSE, None, menu->frame);*/
868
869         entry = menu_find_entry(menu, e->xbutton.window);
870         if (entry) {
871             int junk;
872             Window wjunk;
873             guint ujunk, b, w, h;
874             XGetGeometry(ob_display, e->xbutton.window,
875                          &wjunk, &junk, &junk, &w, &h, &b, &ujunk);
876             if (e->xbutton.x >= (signed)-b &&
877                 e->xbutton.y >= (signed)-b &&
878                 e->xbutton.x < (signed)(w+b) &&
879                 e->xbutton.y < (signed)(h+b)) {
880                 menu_entry_fire(entry);
881             }
882         }
883         break;
884     case EnterNotify:
885     case LeaveNotify:
886         g_message("enter/leave");
887         entry = menu_find_entry(menu, e->xcrossing.window);
888         if (entry) {
889             entry->hilite = e->type == EnterNotify;
890             menu_entry_render(entry);
891         }
892         break;
893     }
894 }