compress events properly
[mikachu/openbox.git] / openbox / event.c
1 #include "debug.h"
2 #include "openbox.h"
3 #include "dock.h"
4 #include "client.h"
5 #include "xerror.h"
6 #include "prop.h"
7 #include "config.h"
8 #include "screen.h"
9 #include "frame.h"
10 #include "menu.h"
11 #include "menuframe.h"
12 #include "keyboard.h"
13 #include "mouse.h"
14 #include "mainloop.h"
15 #include "framerender.h"
16 #include "focus.h"
17 #include "moveresize.h"
18 #include "stacking.h"
19 #include "extensions.h"
20 #include "event.h"
21
22 #include <X11/Xlib.h>
23 #include <X11/keysym.h>
24 #include <X11/Xatom.h>
25 #include <glib.h>
26
27 #ifdef USE_LIBSN
28 #  include <libsn/sn.h>
29 #endif
30
31 #ifdef HAVE_SYS_SELECT_H
32 #  include <sys/select.h>
33 #endif
34 #ifdef HAVE_SIGNAL_H
35 #  include <signal.h>
36 #endif
37
38 #ifdef USE_SM
39 #include <X11/ICE/ICElib.h>
40 #endif
41
42 static void event_process(const XEvent *e, gpointer data);
43 static void event_handle_root(XEvent *e);
44 static void event_handle_menu(XEvent *e);
45 static void event_handle_dock(ObDock *s, XEvent *e);
46 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
47 static void event_handle_client(ObClient *c, XEvent *e);
48
49 #define INVALID_FOCUSIN(e) ((e)->xfocus.detail == NotifyInferior || \
50                             (e)->xfocus.detail == NotifyAncestor || \
51                             (e)->xfocus.detail > NotifyNonlinearVirtual)
52 #define INVALID_FOCUSOUT(e) ((e)->xfocus.mode == NotifyGrab || \
53                              (e)->xfocus.detail == NotifyInferior || \
54                              (e)->xfocus.detail == NotifyAncestor || \
55                              (e)->xfocus.detail > NotifyNonlinearVirtual)
56
57 Time event_lasttime = 0;
58
59 /*! The value of the mask for the NumLock modifier */
60 unsigned int NumLockMask;
61 /*! The value of the mask for the ScrollLock modifier */
62 unsigned int ScrollLockMask;
63 /*! The key codes for the modifier keys */
64 static XModifierKeymap *modmap;
65 /*! Table of the constant modifier masks */
66 static const int mask_table[] = {
67     ShiftMask, LockMask, ControlMask, Mod1Mask,
68     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
69 };
70 static int mask_table_size;
71
72 #ifdef USE_SM
73 static void ice_handler(int fd, gpointer conn)
74 {
75     Bool b;
76     IceProcessMessages(conn, NULL, &b);
77 }
78
79 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
80                       IcePointer *watch_data)
81 {
82     static gint fd = -1;
83
84     if (opening) {
85         fd = IceConnectionNumber(conn);
86         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
87     } else {
88         ob_main_loop_fd_remove(ob_main_loop, fd);
89         fd = -1;
90     }
91 }
92 #endif
93
94 #ifdef USE_LIBSN
95 static void sn_handler(const XEvent *e, gpointer display)
96 {
97     XEvent ec;
98     ec = *e;
99     sn_display_process_event(display, &ec);
100 }
101 #endif
102
103
104 void event_startup()
105 {
106     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
107      
108     /* get lock masks that are defined by the display (not constant) */
109     modmap = XGetModifierMapping(ob_display);
110     g_assert(modmap);
111     if (modmap && modmap->max_keypermod > 0) {
112         size_t cnt;
113         const size_t size = mask_table_size * modmap->max_keypermod;
114         /* get the values of the keyboard lock modifiers
115            Note: Caps lock is not retrieved the same way as Scroll and Num
116            lock since it doesn't need to be. */
117         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
118         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
119                                                      XK_Scroll_Lock);
120           
121         for (cnt = 0; cnt < size; ++cnt) {
122             if (! modmap->modifiermap[cnt]) continue;
123                
124             if (num_lock == modmap->modifiermap[cnt])
125                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
126             if (scroll_lock == modmap->modifiermap[cnt])
127                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
128         }
129     }
130
131     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
132
133 #ifdef USE_SM
134     IceAddConnectionWatch(ice_watch, NULL);
135 #endif
136
137 #ifdef USE_LIBSN
138     ob_main_loop_x_add(ob_main_loop, sn_handler, ob_sn_display, NULL);
139 #endif
140 }
141
142 void event_shutdown()
143 {
144     XFreeModifiermap(modmap);
145 }
146
147 static Window event_get_window(XEvent *e)
148 {
149     Window window;
150
151     /* pick a window */
152     switch (e->type) {
153     case SelectionClear:
154         window = RootWindow(ob_display, ob_screen);
155         break;
156     case MapRequest:
157         window = e->xmap.window;
158         break;
159     case UnmapNotify:
160         window = e->xunmap.window;
161         break;
162     case DestroyNotify:
163         window = e->xdestroywindow.window;
164         break;
165     case ConfigureRequest:
166         window = e->xconfigurerequest.window;
167         break;
168     case ConfigureNotify:
169         window = e->xconfigure.window;
170         break;
171     default:
172 #ifdef XKB
173         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
174             switch (((XkbAnyEvent*)e)->xkb_type) {
175             case XkbBellNotify:
176                 window = ((XkbBellNotifyEvent*)e)->window;
177             default:
178                 window = None;
179             }
180         } else
181 #endif
182             window = e->xany.window;
183     }
184     return window;
185 }
186
187 static void event_set_lasttime(XEvent *e)
188 {
189     Time t = 0;
190
191     /* grab the lasttime and hack up the state */
192     switch (e->type) {
193     case ButtonPress:
194     case ButtonRelease:
195         t = e->xbutton.time;
196         break;
197     case KeyPress:
198         t = e->xkey.time;
199         break;
200     case KeyRelease:
201         t = e->xkey.time;
202         break;
203     case MotionNotify:
204         t = e->xmotion.time;
205         break;
206     case PropertyNotify:
207         t = e->xproperty.time;
208         break;
209     case EnterNotify:
210     case LeaveNotify:
211         t = e->xcrossing.time;
212         break;
213     default:
214         /* if more event types are anticipated, get their timestamp
215            explicitly */
216         break;
217     }
218
219     if (t > event_lasttime)
220         event_lasttime = t;
221 }
222
223 #define STRIP_MODS(s) \
224         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
225         /* kill off the Button1Mask etc, only want the modifiers */ \
226         s &= (ControlMask | ShiftMask | Mod1Mask | \
227               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
228
229 static void event_hack_mods(XEvent *e)
230 {
231     KeyCode *kp;
232     int i, k;
233
234     switch (e->type) {
235     case ButtonPress:
236     case ButtonRelease:
237         STRIP_MODS(e->xbutton.state);
238         break;
239     case KeyPress:
240         STRIP_MODS(e->xkey.state);
241         break;
242     case KeyRelease:
243         STRIP_MODS(e->xkey.state);
244         /* remove from the state the mask of the modifier being released, if
245            it is a modifier key being released (this is a little ugly..) */
246         kp = modmap->modifiermap;
247         for (i = 0; i < mask_table_size; ++i) {
248             for (k = 0; k < modmap->max_keypermod; ++k) {
249                 if (*kp == e->xkey.keycode) { /* found the keycode */
250                     /* remove the mask for it */
251                     e->xkey.state &= ~mask_table[i];
252                     /* cause the first loop to break; */
253                     i = mask_table_size;
254                     break; /* get outta here! */
255                 }
256                 ++kp;
257             }
258         }
259         break;
260     case MotionNotify:
261         STRIP_MODS(e->xmotion.state);
262         /* compress events */
263         {
264             XEvent ce;
265             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
266                                           e->type, &ce)) {
267                 e->xmotion.x_root = ce.xmotion.x_root;
268                 e->xmotion.y_root = ce.xmotion.y_root;
269             }
270         }
271         break;
272     }
273 }
274
275 static gboolean event_ignore(XEvent *e, ObClient *client)
276 {
277     switch(e->type) {
278     case FocusIn:
279         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
280            because of RevertToPointerRoot. If the focus ends up reverting to
281            pointer root on a workspace change, then the FocusIn event that we
282            want will be of type NotifyAncestor. This situation does not occur
283            for FocusOut, so it is safely ignored there.
284         */
285         if (INVALID_FOCUSIN(e) ||
286             client == NULL) {
287 #ifdef DEBUG_FOCUS
288         ob_debug("FocusIn on %lx mode %d detail %d IGNORED\n",
289                  e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
290 #endif
291             /* says a client was not found for the event (or a valid FocusIn
292                event was not found.
293             */
294             e->xfocus.window = None;
295             return TRUE;
296         }
297
298 #ifdef DEBUG_FOCUS
299         ob_debug("FocusIn on %lx mode %d detail %d\n", e->xfocus.window,
300                  e->xfocus.mode, e->xfocus.detail);
301 #endif
302         break;
303     case FocusOut:
304         if (INVALID_FOCUSOUT(e)) {
305 #ifdef DEBUG_FOCUS
306         ob_debug("FocusOut on %lx mode %d detail %d IGNORED\n",
307                  e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
308 #endif
309             return TRUE;
310         }
311
312 #ifdef DEBUG_FOCUS
313         ob_debug("FocusOut on %lx mode %d detail %d\n",
314                  e->xfocus.window, e->xfocus.mode, e->xfocus.detail);
315 #endif
316
317         {
318             XEvent fe;
319             gboolean fallback = TRUE;
320
321             while (TRUE) {
322                 if (!XCheckTypedWindowEvent(ob_display, e->xfocus.window,
323                                             FocusOut, &fe))
324                     if (!XCheckTypedEvent(ob_display, FocusIn, &fe))
325                         break;
326                 if (fe.type == FocusOut) {
327 #ifdef DEBUG_FOCUS
328                     ob_debug("found pending FocusOut");
329 #endif
330                     if (!INVALID_FOCUSOUT(&fe)) {
331                         /* if there is a VALID FocusOut still coming, don't
332                            fallback focus yet, we'll deal with it then */
333                         XPutBackEvent(ob_display, &fe);
334                         fallback = FALSE;
335                         break;
336                     }
337                 } else {
338 #ifdef DEBUG_FOCUS
339                     ob_debug("found pending FocusIn");
340 #endif
341                     /* is the focused window getting a FocusOut/In back to
342                        itself?
343                     */
344                     if (fe.xfocus.window == e->xfocus.window &&
345                         !event_ignore(&fe, client)) {
346                         /*
347                           if focus_client is not set, then we can't do
348                           this. we need the FocusIn. This happens in the
349                           case when the set_focus_client(NULL) in the
350                           focus_fallback function fires and then
351                           focus_fallback picks the currently focused
352                           window (such as on a SendToDesktop-esque action.
353                         */
354                         if (focus_client) {
355 #ifdef DEBUG_FOCUS
356                             ob_debug("focused window got an Out/In back to "
357                                      "itself IGNORED both");
358 #endif
359                             return TRUE;
360                         } else {
361                             event_process(&fe, NULL);
362 #ifdef DEBUG_FOCUS
363                             ob_debug("focused window got an Out/In back to "
364                                      "itself but focus_client was null "
365                                      "IGNORED just the Out");
366 #endif
367                             return TRUE;
368                         }
369                     }
370
371                     /* once all the FocusOut's have been dealt with, if there
372                        is a FocusIn still left and it is valid, then use it */
373                     event_process(&fe, NULL);
374                     /* secret magic way of event_process telling us that no
375                        client was found for the FocusIn event. ^_^ */
376                     if (fe.xfocus.window != None) {
377                         fallback = FALSE;
378                         break;
379                     }
380                 }
381             }
382             if (fallback) {
383 #ifdef DEBUG_FOCUS
384                 ob_debug("no valid FocusIn and no FocusOut events found, "
385                          "falling back");
386 #endif
387                 focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS);
388             }
389         }
390         break;
391     case EnterNotify:
392     case LeaveNotify:
393         /* NotifyUngrab occurs when a mouse button is released and the event is
394            caused, like when lowering a window */
395         /* NotifyVirtual occurs when ungrabbing the pointer */
396         if (e->xcrossing.mode == NotifyGrab ||
397             e->xcrossing.detail == NotifyInferior ||
398             (e->xcrossing.mode == NotifyUngrab &&
399              e->xcrossing.detail == NotifyVirtual)) {
400 #ifdef DEBUG_FOCUS
401             ob_debug("%sNotify mode %d detail %d on %lx IGNORED",
402                      (e->type == EnterNotify ? "Enter" : "Leave"),
403                      e->xcrossing.mode,
404                      e->xcrossing.detail, client?client->window:0);
405 #endif
406             return TRUE;
407         }
408 #ifdef DEBUG_FOCUS
409         ob_debug("%sNotify mode %d detail %d on %lx",
410                  (e->type == EnterNotify ? "Enter" : "Leave"),
411                  e->xcrossing.mode,
412                  e->xcrossing.detail, client?client->window:0);
413 #endif
414         break;
415     }
416     return FALSE;
417 }
418
419 static void event_process(const XEvent *ec, gpointer data)
420 {
421     Window window;
422     ObClient *client = NULL;
423     ObDock *dock = NULL;
424     ObDockApp *dockapp = NULL;
425     ObWindow *obwin = NULL;
426     XEvent ee, *e;
427
428     /* make a copy we can mangle */
429     ee = *ec;
430     e = &ee;
431
432     g_message("Event %d", e->type);
433
434     window = event_get_window(e);
435     if ((obwin = g_hash_table_lookup(window_map, &window))) {
436         switch (obwin->type) {
437         case Window_Dock:
438             dock = WINDOW_AS_DOCK(obwin);
439             break;
440         case Window_DockApp:
441             dockapp = WINDOW_AS_DOCKAPP(obwin);
442             break;
443         case Window_Client:
444             client = WINDOW_AS_CLIENT(obwin);
445             break;
446         case Window_Menu:
447         case Window_Internal:
448             /* not to be used for events */
449             g_assert_not_reached();
450             break;
451         }
452     }
453
454     event_set_lasttime(e);
455     event_hack_mods(e);
456     if (event_ignore(e, client))
457         return;
458
459     /* deal with it in the kernel */
460     if (client)
461         event_handle_client(client, e);
462     else if (dockapp)
463         event_handle_dockapp(dockapp, e);
464     else if (dock)
465         event_handle_dock(dock, e);
466     else if (window == RootWindow(ob_display, ob_screen))
467         event_handle_root(e);
468     else if (e->type == MapRequest)
469         client_manage(window);
470     else if (e->type == ConfigureRequest) {
471         /* unhandled configure requests must be used to configure the
472            window directly */
473         XWindowChanges xwc;
474                
475         xwc.x = e->xconfigurerequest.x;
476         xwc.y = e->xconfigurerequest.y;
477         xwc.width = e->xconfigurerequest.width;
478         xwc.height = e->xconfigurerequest.height;
479         xwc.border_width = e->xconfigurerequest.border_width;
480         xwc.sibling = e->xconfigurerequest.above;
481         xwc.stack_mode = e->xconfigurerequest.detail;
482        
483         /* we are not to be held responsible if someone sends us an
484            invalid request! */
485         xerror_set_ignore(TRUE);
486         XConfigureWindow(ob_display, window,
487                          e->xconfigurerequest.value_mask, &xwc);
488         xerror_set_ignore(FALSE);
489     }
490
491     /* user input (action-bound) events */
492     if (e->type == ButtonPress || e->type == ButtonRelease ||
493         e->type == MotionNotify || e->type == KeyPress ||
494         e->type == KeyRelease)
495     {
496         if (menu_frame_visible)
497             event_handle_menu(e);
498         else if (moveresize_in_progress)
499             moveresize_event(e);
500         else {
501             ObFrameContext context;
502
503             context = frame_context(client, e->xany.window);
504
505             if (!keyboard_process_interactive_grab(e, &client, &context)) {
506                 if (e->type == ButtonPress || e->type == ButtonRelease ||
507                     e->type == MotionNotify)
508                     mouse_event(client, context, e);
509                 else if (e->type == KeyPress)
510                     keyboard_event(client, e);
511             }
512         }
513     }
514 }
515
516 static void event_handle_root(XEvent *e)
517 {
518     Atom msgtype;
519      
520     switch(e->type) {
521     case SelectionClear:
522         ob_debug("Another WM has requested to replace us. Exiting.\n");
523         ob_exit();
524         break;
525
526     case ClientMessage:
527         if (e->xclient.format != 32) break;
528
529         msgtype = e->xclient.message_type;
530         if (msgtype == prop_atoms.net_current_desktop) {
531             unsigned int d = e->xclient.data.l[0];
532             if (d < screen_num_desktops)
533                 screen_set_desktop(d);
534         } else if (msgtype == prop_atoms.net_number_of_desktops) {
535             unsigned int d = e->xclient.data.l[0];
536             if (d > 0)
537                 screen_set_num_desktops(d);
538         } else if (msgtype == prop_atoms.net_showing_desktop) {
539             screen_show_desktop(e->xclient.data.l[0] != 0);
540         }
541         break;
542     case PropertyNotify:
543         if (e->xproperty.atom == prop_atoms.net_desktop_names)
544             screen_update_desktop_names();
545         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
546             screen_update_layout();
547         break;
548     case ConfigureNotify:
549 #ifdef XRANDR
550         XRRUpdateConfiguration(e);
551 #endif
552         screen_resize();
553         break;
554     default:
555         ;
556 #ifdef VIDMODE
557         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
558             ob_debug("VIDMODE EVENT\n");
559         }
560 #endif
561     }
562 }
563
564 static void event_handle_client(ObClient *client, XEvent *e)
565 {
566     XEvent ce;
567     Atom msgtype;
568     int i=0;
569     ObFrameContext con;
570      
571     switch (e->type) {
572     case VisibilityNotify:
573         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
574         break;
575     case ButtonPress:
576     case ButtonRelease:
577         /* Wheel buttons don't draw because they are an instant click, so it
578            is a waste of resources to go drawing it. */
579         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
580             switch (frame_context(client, e->xbutton.window)) {
581             case OB_FRAME_CONTEXT_MAXIMIZE:
582                 client->frame->max_press = (e->type == ButtonPress);
583                 framerender_frame(client->frame);
584                 break;
585             case OB_FRAME_CONTEXT_CLOSE:
586                 client->frame->close_press = (e->type == ButtonPress);
587                 framerender_frame(client->frame);
588                 break;
589             case OB_FRAME_CONTEXT_ICONIFY:
590                 client->frame->iconify_press = (e->type == ButtonPress);
591                 framerender_frame(client->frame);
592                 break;
593             case OB_FRAME_CONTEXT_ALLDESKTOPS:
594                 client->frame->desk_press = (e->type == ButtonPress);
595                 framerender_frame(client->frame);
596                 break; 
597             case OB_FRAME_CONTEXT_SHADE:
598                 client->frame->shade_press = (e->type == ButtonPress);
599                 framerender_frame(client->frame);
600                 break;
601             default:
602                 /* nothing changes with clicks for any other contexts */
603                 break;
604             }
605         }
606         break;
607     case FocusIn:
608 #ifdef DEBUG_FOCUS
609         ob_debug("FocusIn on client for %lx\n", client->window);
610 #endif
611         if (client != focus_client) {
612             focus_set_client(client);
613             frame_adjust_focus(client->frame, TRUE);
614         }
615         break;
616     case FocusOut:
617 #ifdef DEBUG_FOCUS
618         ob_debug("FocusOut on client for %lx\n", client->window);
619 #endif
620         /* are we a fullscreen window or a transient of one? (checks layer)
621            if we are then we need to be iconified since we are losing focus
622          */
623         if (client->layer == OB_STACKING_LAYER_FULLSCREEN && !client->iconic &&
624             !client_search_focus_tree_full(client))
625             /* iconify fullscreen windows when they and their transients
626                aren't focused */
627             client_iconify(client, TRUE, TRUE);
628         frame_adjust_focus(client->frame, FALSE);
629         break;
630     case LeaveNotify:
631         con = frame_context(client, e->xcrossing.window);
632         switch (con) {
633         case OB_FRAME_CONTEXT_MAXIMIZE:
634             client->frame->max_hover = FALSE;
635             frame_adjust_state(client->frame);
636             break;
637         case OB_FRAME_CONTEXT_ALLDESKTOPS:
638             client->frame->desk_hover = FALSE;
639             frame_adjust_state(client->frame);
640             break;
641         case OB_FRAME_CONTEXT_SHADE:
642             client->frame->shade_hover = FALSE;
643             frame_adjust_state(client->frame);
644             break;
645         case OB_FRAME_CONTEXT_ICONIFY:
646             client->frame->iconify_hover = FALSE;
647             frame_adjust_state(client->frame);
648             break;
649         case OB_FRAME_CONTEXT_CLOSE:
650             client->frame->close_hover = FALSE;
651             frame_adjust_state(client->frame);
652             break;
653         default:
654             break;
655         }
656         break;
657     case EnterNotify:
658         con = frame_context(client, e->xcrossing.window);
659         switch (con) {
660         case OB_FRAME_CONTEXT_MAXIMIZE:
661             client->frame->max_hover = TRUE;
662             frame_adjust_state(client->frame);
663             break;
664         case OB_FRAME_CONTEXT_ALLDESKTOPS:
665             client->frame->desk_hover = TRUE;
666             frame_adjust_state(client->frame);
667             break;
668         case OB_FRAME_CONTEXT_SHADE:
669             client->frame->shade_hover = TRUE;
670             frame_adjust_state(client->frame);
671             break;
672         case OB_FRAME_CONTEXT_ICONIFY:
673             client->frame->iconify_hover = TRUE;
674             frame_adjust_state(client->frame);
675             break;
676         case OB_FRAME_CONTEXT_CLOSE:
677             client->frame->close_hover = TRUE;
678             frame_adjust_state(client->frame);
679             break;
680         case OB_FRAME_CONTEXT_FRAME:
681             if (client_normal(client)) {
682                 if (ob_state() == OB_STATE_STARTING) {
683                     /* move it to the top of the focus order */
684                     guint desktop = client->desktop;
685                     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
686                     focus_order[desktop] = g_list_remove(focus_order[desktop],
687                                                          client);
688                     focus_order[desktop] = g_list_prepend(focus_order[desktop],
689                                                           client);
690                 } else if (config_focus_follow) {
691 #ifdef DEBUG_FOCUS
692                     ob_debug("EnterNotify on %lx, focusing window\n",
693                              client->window);
694 #endif
695                     client_focus(client);
696                 }
697             }
698             break;
699         default:
700             break;
701         }
702         break;
703     case ConfigureRequest:
704         /* compress these */
705         while (XCheckTypedWindowEvent(ob_display, client->window,
706                                       ConfigureRequest, &ce)) {
707             ++i;
708             /* XXX if this causes bad things.. we can compress config req's
709                with the same mask. */
710             e->xconfigurerequest.value_mask |=
711                 ce.xconfigurerequest.value_mask;
712             if (ce.xconfigurerequest.value_mask & CWX)
713                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
714             if (ce.xconfigurerequest.value_mask & CWY)
715                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
716             if (ce.xconfigurerequest.value_mask & CWWidth)
717                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
718             if (ce.xconfigurerequest.value_mask & CWHeight)
719                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
720             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
721                 e->xconfigurerequest.border_width =
722                     ce.xconfigurerequest.border_width;
723             if (ce.xconfigurerequest.value_mask & CWStackMode)
724                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
725         }
726
727         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
728         if (client->iconic || client->shaded) return;
729
730         /* resize, then move, as specified in the EWMH section 7.7 */
731         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
732                                                CWX | CWY |
733                                                CWBorderWidth)) {
734             int x, y, w, h;
735             ObCorner corner;
736
737             if (e->xconfigurerequest.value_mask & CWBorderWidth)
738                 client->border_width = e->xconfigurerequest.border_width;
739
740             x = (e->xconfigurerequest.value_mask & CWX) ?
741                 e->xconfigurerequest.x : client->area.x;
742             y = (e->xconfigurerequest.value_mask & CWY) ?
743                 e->xconfigurerequest.y : client->area.y;
744             w = (e->xconfigurerequest.value_mask & CWWidth) ?
745                 e->xconfigurerequest.width : client->area.width;
746             h = (e->xconfigurerequest.value_mask & CWHeight) ?
747                 e->xconfigurerequest.height : client->area.height;
748
749             {
750                 int newx = x;
751                 int newy = y;
752                 int fw = w +
753                     client->frame->size.left + client->frame->size.right;
754                 int fh = h +
755                     client->frame->size.top + client->frame->size.bottom;
756                 client_find_onscreen(client, &newx, &newy, fw, fh,
757                                      client_normal(client));
758                 if (e->xconfigurerequest.value_mask & CWX)
759                     x = newx;
760                 if (e->xconfigurerequest.value_mask & CWY)
761                     y = newy;
762             }
763                
764             switch (client->gravity) {
765             case NorthEastGravity:
766             case EastGravity:
767                 corner = OB_CORNER_TOPRIGHT;
768                 break;
769             case SouthWestGravity:
770             case SouthGravity:
771                 corner = OB_CORNER_BOTTOMLEFT;
772                 break;
773             case SouthEastGravity:
774                 corner = OB_CORNER_BOTTOMRIGHT;
775                 break;
776             default:     /* NorthWest, Static, etc */
777                 corner = OB_CORNER_TOPLEFT;
778             }
779
780             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
781                                   TRUE);
782         }
783
784         if (e->xconfigurerequest.value_mask & CWStackMode) {
785             switch (e->xconfigurerequest.detail) {
786             case Below:
787             case BottomIf:
788                 stacking_lower(CLIENT_AS_WINDOW(client));
789                 break;
790
791             case Above:
792             case TopIf:
793             default:
794                 stacking_raise(CLIENT_AS_WINDOW(client));
795                 break;
796             }
797         }
798         break;
799     case UnmapNotify:
800         if (client->ignore_unmaps) {
801             client->ignore_unmaps--;
802             break;
803         }
804         client_unmanage(client);
805         break;
806     case DestroyNotify:
807         client_unmanage(client);
808         break;
809     case ReparentNotify:
810         /* this is when the client is first taken captive in the frame */
811         if (e->xreparent.parent == client->frame->plate) break;
812
813         /*
814           This event is quite rare and is usually handled in unmapHandler.
815           However, if the window is unmapped when the reparent event occurs,
816           the window manager never sees it because an unmap event is not sent
817           to an already unmapped window.
818         */
819
820         /* we don't want the reparent event, put it back on the stack for the
821            X server to deal with after we unmanage the window */
822         XPutBackEvent(ob_display, e);
823      
824         client_unmanage(client);
825         break;
826     case MapRequest:
827         ob_debug("MapRequest for 0x%lx\n", client->window);
828         if (!client->iconic) break; /* this normally doesn't happen, but if it
829                                        does, we don't want it! */
830         if (screen_showing_desktop)
831             screen_show_desktop(FALSE);
832         client_iconify(client, FALSE, TRUE);
833         if (!client->frame->visible)
834             /* if its not visible still, then don't mess with it */
835             break;
836         if (client->shaded)
837             client_shade(client, FALSE);
838         client_focus(client);
839         stacking_raise(CLIENT_AS_WINDOW(client));
840         break;
841     case ClientMessage:
842         /* validate cuz we query stuff off the client here */
843         if (!client_validate(client)) break;
844   
845         if (e->xclient.format != 32) return;
846
847         msgtype = e->xclient.message_type;
848         if (msgtype == prop_atoms.wm_change_state) {
849             /* compress changes into a single change */
850             while (XCheckTypedWindowEvent(ob_display, client->window,
851                                           e->type, &ce)) {
852                 /* XXX: it would be nice to compress ALL messages of a
853                    type, not just messages in a row without other
854                    message types between. */
855                 if (ce.xclient.message_type != msgtype) {
856                     XPutBackEvent(ob_display, &ce);
857                     break;
858                 }
859                 e->xclient = ce.xclient;
860             }
861             client_set_wm_state(client, e->xclient.data.l[0]);
862         } else if (msgtype == prop_atoms.net_wm_desktop) {
863             /* compress changes into a single change */
864             while (XCheckTypedWindowEvent(ob_display, client->window,
865                                           e->type, &ce)) {
866                 /* XXX: it would be nice to compress ALL messages of a
867                    type, not just messages in a row without other
868                    message types between. */
869                 if (ce.xclient.message_type != msgtype) {
870                     XPutBackEvent(ob_display, &ce);
871                     break;
872                 }
873                 e->xclient = ce.xclient;
874             }
875             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
876                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
877                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
878                                    FALSE);
879         } else if (msgtype == prop_atoms.net_wm_state) {
880             /* can't compress these */
881             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
882                      (e->xclient.data.l[0] == 0 ? "Remove" :
883                       e->xclient.data.l[0] == 1 ? "Add" :
884                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
885                      e->xclient.data.l[1], e->xclient.data.l[2],
886                      client->window);
887             client_set_state(client, e->xclient.data.l[0],
888                              e->xclient.data.l[1], e->xclient.data.l[2]);
889         } else if (msgtype == prop_atoms.net_close_window) {
890             ob_debug("net_close_window for 0x%lx\n", client->window);
891             client_close(client);
892         } else if (msgtype == prop_atoms.net_active_window) {
893             ob_debug("net_active_window for 0x%lx\n", client->window);
894             client_activate(client, FALSE);
895         } else if (msgtype == prop_atoms.net_wm_moveresize) {
896             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
897             if ((Atom)e->xclient.data.l[2] ==
898                 prop_atoms.net_wm_moveresize_size_topleft ||
899                 (Atom)e->xclient.data.l[2] ==
900                 prop_atoms.net_wm_moveresize_size_top ||
901                 (Atom)e->xclient.data.l[2] ==
902                 prop_atoms.net_wm_moveresize_size_topright ||
903                 (Atom)e->xclient.data.l[2] ==
904                 prop_atoms.net_wm_moveresize_size_right ||
905                 (Atom)e->xclient.data.l[2] ==
906                 prop_atoms.net_wm_moveresize_size_right ||
907                 (Atom)e->xclient.data.l[2] ==
908                 prop_atoms.net_wm_moveresize_size_bottomright ||
909                 (Atom)e->xclient.data.l[2] ==
910                 prop_atoms.net_wm_moveresize_size_bottom ||
911                 (Atom)e->xclient.data.l[2] ==
912                 prop_atoms.net_wm_moveresize_size_bottomleft ||
913                 (Atom)e->xclient.data.l[2] ==
914                 prop_atoms.net_wm_moveresize_size_left ||
915                 (Atom)e->xclient.data.l[2] ==
916                 prop_atoms.net_wm_moveresize_move ||
917                 (Atom)e->xclient.data.l[2] ==
918                 prop_atoms.net_wm_moveresize_size_keyboard ||
919                 (Atom)e->xclient.data.l[2] ==
920                 prop_atoms.net_wm_moveresize_move_keyboard) {
921
922                 moveresize_start(client, e->xclient.data.l[0],
923                                  e->xclient.data.l[1], e->xclient.data.l[3],
924                                  e->xclient.data.l[2]);
925             }
926         } else if (msgtype == prop_atoms.net_moveresize_window) {
927             int oldg = client->gravity;
928             int tmpg, x, y, w, h;
929
930             if (e->xclient.data.l[0] & 0xff)
931                 tmpg = e->xclient.data.l[0] & 0xff;
932             else
933                 tmpg = oldg;
934
935             if (e->xclient.data.l[0] & 1 << 8)
936                 x = e->xclient.data.l[1];
937             else
938                 x = client->area.x;
939             if (e->xclient.data.l[0] & 1 << 9)
940                 y = e->xclient.data.l[2];
941             else
942                 y = client->area.y;
943             if (e->xclient.data.l[0] & 1 << 10)
944                 w = e->xclient.data.l[3];
945             else
946                 w = client->area.width;
947             if (e->xclient.data.l[0] & 1 << 11)
948                 h = e->xclient.data.l[4];
949             else
950                 h = client->area.height;
951             client->gravity = tmpg;
952
953             {
954                 int newx = x;
955                 int newy = y;
956                 int fw = w +
957                     client->frame->size.left + client->frame->size.right;
958                 int fh = h +
959                     client->frame->size.top + client->frame->size.bottom;
960                 client_find_onscreen(client, &newx, &newy, fw, fh,
961                                      client_normal(client));
962                 if (e->xclient.data.l[0] & 1 << 8)
963                     x = newx;
964                 if (e->xclient.data.l[0] & 1 << 9)
965                     y = newy;
966             }
967                
968             client_configure(client, OB_CORNER_TOPLEFT,
969                              x, y, w, h, FALSE, TRUE);
970
971             client->gravity = oldg;
972         }
973         break;
974     case PropertyNotify:
975         /* validate cuz we query stuff off the client here */
976         if (!client_validate(client)) break;
977   
978         /* compress changes to a single property into a single change */
979         while (XCheckTypedWindowEvent(ob_display, client->window,
980                                       e->type, &ce)) {
981             Atom a, b;
982
983             /* XXX: it would be nice to compress ALL changes to a property,
984                not just changes in a row without other props between. */
985
986             a = ce.xproperty.atom;
987             b = e->xproperty.atom;
988
989             if (a == b)
990                 continue;
991             if ((a == prop_atoms.net_wm_name ||
992                  a == prop_atoms.wm_name ||
993                  a == prop_atoms.net_wm_icon_name ||
994                  a == prop_atoms.wm_icon_name)
995                 &&
996                 (b == prop_atoms.net_wm_name ||
997                  b == prop_atoms.wm_name ||
998                  b == prop_atoms.net_wm_icon_name ||
999                  b == prop_atoms.wm_icon_name)) {
1000                 continue;
1001             }
1002             if ((a == prop_atoms.net_wm_icon ||
1003                  a == prop_atoms.kwm_win_icon)
1004                 &&
1005                 (b == prop_atoms.net_wm_icon ||
1006                  b == prop_atoms.kwm_win_icon))
1007                 continue;
1008
1009             XPutBackEvent(ob_display, &ce);
1010             break;
1011         }
1012
1013         msgtype = e->xproperty.atom;
1014         if (msgtype == XA_WM_NORMAL_HINTS) {
1015             client_update_normal_hints(client);
1016             /* normal hints can make a window non-resizable */
1017             client_setup_decor_and_functions(client);
1018         } else if (msgtype == XA_WM_HINTS) {
1019             client_update_wmhints(client);
1020         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1021             client_update_transient_for(client);
1022             client_get_type(client);
1023             /* type may have changed, so update the layer */
1024             client_calc_layer(client);
1025             client_setup_decor_and_functions(client);
1026         } else if (msgtype == prop_atoms.net_wm_name ||
1027                    msgtype == prop_atoms.wm_name ||
1028                    msgtype == prop_atoms.net_wm_icon_name ||
1029                    msgtype == prop_atoms.wm_icon_name) {
1030             client_update_title(client);
1031         } else if (msgtype == prop_atoms.wm_class) {
1032             client_update_class(client);
1033         } else if (msgtype == prop_atoms.wm_protocols) {
1034             client_update_protocols(client);
1035             client_setup_decor_and_functions(client);
1036         }
1037         else if (msgtype == prop_atoms.net_wm_strut) {
1038             client_update_strut(client);
1039         }
1040         else if (msgtype == prop_atoms.net_wm_icon ||
1041                  msgtype == prop_atoms.kwm_win_icon) {
1042             client_update_icons(client);
1043         }
1044     default:
1045         ;
1046 #ifdef SHAPE
1047         if (extensions_shape && e->type == extensions_shape_event_basep) {
1048             client->shaped = ((XShapeEvent*)e)->shaped;
1049             frame_adjust_shape(client->frame);
1050         }
1051 #endif
1052     }
1053 }
1054
1055 static void event_handle_dock(ObDock *s, XEvent *e)
1056 {
1057     switch (e->type) {
1058     case ButtonPress:
1059         stacking_raise(DOCK_AS_WINDOW(s));
1060         break;
1061     case EnterNotify:
1062         dock_hide(FALSE);
1063         break;
1064     case LeaveNotify:
1065         dock_hide(TRUE);
1066         break;
1067     }
1068 }
1069
1070 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1071 {
1072     switch (e->type) {
1073     case MotionNotify:
1074         dock_app_drag(app, &e->xmotion);
1075         break;
1076     case UnmapNotify:
1077         if (app->ignore_unmaps) {
1078             app->ignore_unmaps--;
1079             break;
1080         }
1081         dock_remove(app, TRUE);
1082         break;
1083     case DestroyNotify:
1084         dock_remove(app, FALSE);
1085         break;
1086     case ReparentNotify:
1087         dock_remove(app, FALSE);
1088         break;
1089     case ConfigureNotify:
1090         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1091         break;
1092     }
1093 }
1094
1095 ObMenuFrame* find_active_menu()
1096 {
1097     GList *it;
1098     ObMenuFrame *f;
1099
1100     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1101         f = it->data;
1102         if (f->selected)
1103             break;
1104     }
1105     return it ? it->data : NULL;
1106 }
1107
1108 static void event_handle_menu(XEvent *ev)
1109 {
1110     ObMenuFrame *f;
1111     ObMenuEntryFrame *e;
1112
1113     switch (ev->type) {
1114     case ButtonRelease:
1115         if (!(f = menu_frame_under(ev->xbutton.x_root,
1116                                    ev->xbutton.y_root)))
1117             menu_frame_hide_all();
1118         else {
1119             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1120                                             ev->xbutton.y_root)))
1121                 menu_entry_frame_execute(e,
1122                                          !(ev->xbutton.state & ControlMask));
1123         }
1124         break;
1125     case MotionNotify:
1126         if ((f = menu_frame_under(ev->xmotion.x_root,
1127                                   ev->xmotion.y_root))) {
1128             menu_frame_move_on_screen(f);
1129             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1130                                             ev->xmotion.y_root)))
1131                 menu_frame_select(f, e);
1132         }
1133         break;
1134     case KeyPress:
1135         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1136             menu_frame_hide_all();
1137         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1138             ObMenuFrame *f;
1139             if ((f = find_active_menu()))
1140                 menu_entry_frame_execute(f->selected,
1141                                          !(ev->xkey.state & ControlMask));
1142         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1143             ObMenuFrame *f;
1144             if ((f = find_active_menu()) && f->parent)
1145                 menu_frame_select(f, NULL);
1146         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1147             ObMenuFrame *f;
1148             if ((f = find_active_menu()) && f->child)
1149                 menu_frame_select_next(f->child);
1150         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1151             ObMenuFrame *f;
1152             if ((f = find_active_menu()))
1153                 menu_frame_select_previous(f);
1154         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1155             ObMenuFrame *f;
1156             if ((f = find_active_menu()))
1157                 menu_frame_select_next(f);
1158         }
1159         break;
1160     }
1161 }