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