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