compress motion events
[dana/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, FocusOut,
323                                             e->xfocus.window,&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     window = event_get_window(e);
433     if ((obwin = g_hash_table_lookup(window_map, &window))) {
434         switch (obwin->type) {
435         case Window_Dock:
436             dock = WINDOW_AS_DOCK(obwin);
437             break;
438         case Window_DockApp:
439             dockapp = WINDOW_AS_DOCKAPP(obwin);
440             break;
441         case Window_Client:
442             client = WINDOW_AS_CLIENT(obwin);
443             break;
444         case Window_Menu:
445         case Window_Internal:
446             /* not to be used for events */
447             g_assert_not_reached();
448             break;
449         }
450     }
451
452     event_set_lasttime(e);
453     event_hack_mods(e);
454     if (event_ignore(e, client))
455         return;
456
457     /* deal with it in the kernel */
458     if (client)
459         event_handle_client(client, e);
460     else if (dockapp)
461         event_handle_dockapp(dockapp, e);
462     else if (dock)
463         event_handle_dock(dock, e);
464     else if (window == RootWindow(ob_display, ob_screen))
465         event_handle_root(e);
466     else if (e->type == MapRequest)
467         client_manage(window);
468     else if (e->type == ConfigureRequest) {
469         /* unhandled configure requests must be used to configure the
470            window directly */
471         XWindowChanges xwc;
472                
473         xwc.x = e->xconfigurerequest.x;
474         xwc.y = e->xconfigurerequest.y;
475         xwc.width = e->xconfigurerequest.width;
476         xwc.height = e->xconfigurerequest.height;
477         xwc.border_width = e->xconfigurerequest.border_width;
478         xwc.sibling = e->xconfigurerequest.above;
479         xwc.stack_mode = e->xconfigurerequest.detail;
480        
481         /* we are not to be held responsible if someone sends us an
482            invalid request! */
483         xerror_set_ignore(TRUE);
484         XConfigureWindow(ob_display, window,
485                          e->xconfigurerequest.value_mask, &xwc);
486         xerror_set_ignore(FALSE);
487     }
488
489     /* user input (action-bound) events */
490     if (e->type == ButtonPress || e->type == ButtonRelease ||
491         e->type == MotionNotify || e->type == KeyPress ||
492         e->type == KeyRelease)
493     {
494         if (menu_frame_visible)
495             event_handle_menu(e);
496         else if (moveresize_in_progress)
497             moveresize_event(e);
498         else {
499             ObFrameContext context;
500
501             context = frame_context(client, e->xany.window);
502
503             if (!keyboard_process_interactive_grab(e, &client, &context)) {
504                 if (e->type == ButtonPress || e->type == ButtonRelease ||
505                     e->type == MotionNotify)
506                     mouse_event(client, context, e);
507                 else if (e->type == KeyPress)
508                     keyboard_event(client, e);
509             }
510         }
511     }
512 }
513
514 static void event_handle_root(XEvent *e)
515 {
516     Atom msgtype;
517      
518     switch(e->type) {
519     case SelectionClear:
520         ob_debug("Another WM has requested to replace us. Exiting.\n");
521         ob_exit();
522         break;
523
524     case ClientMessage:
525         if (e->xclient.format != 32) break;
526
527         msgtype = e->xclient.message_type;
528         if (msgtype == prop_atoms.net_current_desktop) {
529             unsigned int d = e->xclient.data.l[0];
530             if (d < screen_num_desktops)
531                 screen_set_desktop(d);
532         } else if (msgtype == prop_atoms.net_number_of_desktops) {
533             unsigned int d = e->xclient.data.l[0];
534             if (d > 0)
535                 screen_set_num_desktops(d);
536         } else if (msgtype == prop_atoms.net_showing_desktop) {
537             screen_show_desktop(e->xclient.data.l[0] != 0);
538         }
539         break;
540     case PropertyNotify:
541         if (e->xproperty.atom == prop_atoms.net_desktop_names)
542             screen_update_desktop_names();
543         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
544             screen_update_layout();
545         break;
546     case ConfigureNotify:
547 #ifdef XRANDR
548         XRRUpdateConfiguration(e);
549 #endif
550         screen_resize();
551         break;
552     default:
553         ;
554 #ifdef VIDMODE
555         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
556             ob_debug("VIDMODE EVENT\n");
557         }
558 #endif
559     }
560 }
561
562 static void event_handle_client(ObClient *client, XEvent *e)
563 {
564     XEvent ce;
565     Atom msgtype;
566     int i=0;
567     ObFrameContext con;
568      
569     switch (e->type) {
570     case VisibilityNotify:
571         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
572         break;
573     case ButtonPress:
574     case ButtonRelease:
575         /* Wheel buttons don't draw because they are an instant click, so it
576            is a waste of resources to go drawing it. */
577         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
578             switch (frame_context(client, e->xbutton.window)) {
579             case OB_FRAME_CONTEXT_MAXIMIZE:
580                 client->frame->max_press = (e->type == ButtonPress);
581                 framerender_frame(client->frame);
582                 break;
583             case OB_FRAME_CONTEXT_CLOSE:
584                 client->frame->close_press = (e->type == ButtonPress);
585                 framerender_frame(client->frame);
586                 break;
587             case OB_FRAME_CONTEXT_ICONIFY:
588                 client->frame->iconify_press = (e->type == ButtonPress);
589                 framerender_frame(client->frame);
590                 break;
591             case OB_FRAME_CONTEXT_ALLDESKTOPS:
592                 client->frame->desk_press = (e->type == ButtonPress);
593                 framerender_frame(client->frame);
594                 break; 
595             case OB_FRAME_CONTEXT_SHADE:
596                 client->frame->shade_press = (e->type == ButtonPress);
597                 framerender_frame(client->frame);
598                 break;
599             default:
600                 /* nothing changes with clicks for any other contexts */
601                 break;
602             }
603         }
604         break;
605     case FocusIn:
606 #ifdef DEBUG_FOCUS
607         ob_debug("FocusIn on client for %lx\n", client->window);
608 #endif
609         if (client != focus_client) {
610             focus_set_client(client);
611             frame_adjust_focus(client->frame, TRUE);
612         }
613         break;
614     case FocusOut:
615 #ifdef DEBUG_FOCUS
616         ob_debug("FocusOut on client for %lx\n", client->window);
617 #endif
618         /* are we a fullscreen window or a transient of one? (checks layer)
619            if we are then we need to be iconified since we are losing focus
620          */
621         if (client->layer == OB_STACKING_LAYER_FULLSCREEN && !client->iconic &&
622             !client_search_focus_tree_full(client))
623             /* iconify fullscreen windows when they and their transients
624                aren't focused */
625             client_iconify(client, TRUE, TRUE);
626         frame_adjust_focus(client->frame, FALSE);
627         break;
628     case LeaveNotify:
629         con = frame_context(client, e->xcrossing.window);
630         switch (con) {
631         case OB_FRAME_CONTEXT_MAXIMIZE:
632             client->frame->max_hover = FALSE;
633             frame_adjust_state(client->frame);
634             break;
635         case OB_FRAME_CONTEXT_ALLDESKTOPS:
636             client->frame->desk_hover = FALSE;
637             frame_adjust_state(client->frame);
638             break;
639         case OB_FRAME_CONTEXT_SHADE:
640             client->frame->shade_hover = FALSE;
641             frame_adjust_state(client->frame);
642             break;
643         case OB_FRAME_CONTEXT_ICONIFY:
644             client->frame->iconify_hover = FALSE;
645             frame_adjust_state(client->frame);
646             break;
647         case OB_FRAME_CONTEXT_CLOSE:
648             client->frame->close_hover = FALSE;
649             frame_adjust_state(client->frame);
650             break;
651         default:
652             break;
653         }
654         break;
655     case EnterNotify:
656         con = frame_context(client, e->xcrossing.window);
657         switch (con) {
658         case OB_FRAME_CONTEXT_MAXIMIZE:
659             client->frame->max_hover = TRUE;
660             frame_adjust_state(client->frame);
661             break;
662         case OB_FRAME_CONTEXT_ALLDESKTOPS:
663             client->frame->desk_hover = TRUE;
664             frame_adjust_state(client->frame);
665             break;
666         case OB_FRAME_CONTEXT_SHADE:
667             client->frame->shade_hover = TRUE;
668             frame_adjust_state(client->frame);
669             break;
670         case OB_FRAME_CONTEXT_ICONIFY:
671             client->frame->iconify_hover = TRUE;
672             frame_adjust_state(client->frame);
673             break;
674         case OB_FRAME_CONTEXT_CLOSE:
675             client->frame->close_hover = TRUE;
676             frame_adjust_state(client->frame);
677             break;
678         case OB_FRAME_CONTEXT_FRAME:
679             if (client_normal(client)) {
680                 if (ob_state() == OB_STATE_STARTING) {
681                     /* move it to the top of the focus order */
682                     guint desktop = client->desktop;
683                     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
684                     focus_order[desktop] = g_list_remove(focus_order[desktop],
685                                                          client);
686                     focus_order[desktop] = g_list_prepend(focus_order[desktop],
687                                                           client);
688                 } else if (config_focus_follow) {
689 #ifdef DEBUG_FOCUS
690                     ob_debug("EnterNotify on %lx, focusing window\n",
691                              client->window);
692 #endif
693                     client_focus(client);
694                 }
695             }
696             break;
697         default:
698             break;
699         }
700         break;
701     case ConfigureRequest:
702         /* compress these */
703         while (XCheckTypedWindowEvent(ob_display, client->window,
704                                       ConfigureRequest, &ce)) {
705             ++i;
706             /* XXX if this causes bad things.. we can compress config req's
707                with the same mask. */
708             e->xconfigurerequest.value_mask |=
709                 ce.xconfigurerequest.value_mask;
710             if (ce.xconfigurerequest.value_mask & CWX)
711                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
712             if (ce.xconfigurerequest.value_mask & CWY)
713                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
714             if (ce.xconfigurerequest.value_mask & CWWidth)
715                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
716             if (ce.xconfigurerequest.value_mask & CWHeight)
717                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
718             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
719                 e->xconfigurerequest.border_width =
720                     ce.xconfigurerequest.border_width;
721             if (ce.xconfigurerequest.value_mask & CWStackMode)
722                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
723         }
724
725         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
726         if (client->iconic || client->shaded) return;
727
728         /* resize, then move, as specified in the EWMH section 7.7 */
729         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
730                                                CWX | CWY |
731                                                CWBorderWidth)) {
732             int x, y, w, h;
733             ObCorner corner;
734
735             if (e->xconfigurerequest.value_mask & CWBorderWidth)
736                 client->border_width = e->xconfigurerequest.border_width;
737
738             x = (e->xconfigurerequest.value_mask & CWX) ?
739                 e->xconfigurerequest.x : client->area.x;
740             y = (e->xconfigurerequest.value_mask & CWY) ?
741                 e->xconfigurerequest.y : client->area.y;
742             w = (e->xconfigurerequest.value_mask & CWWidth) ?
743                 e->xconfigurerequest.width : client->area.width;
744             h = (e->xconfigurerequest.value_mask & CWHeight) ?
745                 e->xconfigurerequest.height : client->area.height;
746
747             {
748                 int newx = x;
749                 int newy = y;
750                 int fw = w +
751                     client->frame->size.left + client->frame->size.right;
752                 int fh = h +
753                     client->frame->size.top + client->frame->size.bottom;
754                 client_find_onscreen(client, &newx, &newy, fw, fh,
755                                      client_normal(client));
756                 if (e->xconfigurerequest.value_mask & CWX)
757                     x = newx;
758                 if (e->xconfigurerequest.value_mask & CWY)
759                     y = newy;
760             }
761                
762             switch (client->gravity) {
763             case NorthEastGravity:
764             case EastGravity:
765                 corner = OB_CORNER_TOPRIGHT;
766                 break;
767             case SouthWestGravity:
768             case SouthGravity:
769                 corner = OB_CORNER_BOTTOMLEFT;
770                 break;
771             case SouthEastGravity:
772                 corner = OB_CORNER_BOTTOMRIGHT;
773                 break;
774             default:     /* NorthWest, Static, etc */
775                 corner = OB_CORNER_TOPLEFT;
776             }
777
778             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
779                                   TRUE);
780         }
781
782         if (e->xconfigurerequest.value_mask & CWStackMode) {
783             switch (e->xconfigurerequest.detail) {
784             case Below:
785             case BottomIf:
786                 stacking_lower(CLIENT_AS_WINDOW(client));
787                 break;
788
789             case Above:
790             case TopIf:
791             default:
792                 stacking_raise(CLIENT_AS_WINDOW(client));
793                 break;
794             }
795         }
796         break;
797     case UnmapNotify:
798         if (client->ignore_unmaps) {
799             client->ignore_unmaps--;
800             break;
801         }
802         client_unmanage(client);
803         break;
804     case DestroyNotify:
805         client_unmanage(client);
806         break;
807     case ReparentNotify:
808         /* this is when the client is first taken captive in the frame */
809         if (e->xreparent.parent == client->frame->plate) break;
810
811         /*
812           This event is quite rare and is usually handled in unmapHandler.
813           However, if the window is unmapped when the reparent event occurs,
814           the window manager never sees it because an unmap event is not sent
815           to an already unmapped window.
816         */
817
818         /* we don't want the reparent event, put it back on the stack for the
819            X server to deal with after we unmanage the window */
820         XPutBackEvent(ob_display, e);
821      
822         client_unmanage(client);
823         break;
824     case MapRequest:
825         ob_debug("MapRequest for 0x%lx\n", client->window);
826         if (!client->iconic) break; /* this normally doesn't happen, but if it
827                                        does, we don't want it! */
828         if (screen_showing_desktop)
829             screen_show_desktop(FALSE);
830         client_iconify(client, FALSE, TRUE);
831         if (!client->frame->visible)
832             /* if its not visible still, then don't mess with it */
833             break;
834         if (client->shaded)
835             client_shade(client, FALSE);
836         client_focus(client);
837         stacking_raise(CLIENT_AS_WINDOW(client));
838         break;
839     case ClientMessage:
840         /* validate cuz we query stuff off the client here */
841         if (!client_validate(client)) break;
842   
843         if (e->xclient.format != 32) return;
844
845         msgtype = e->xclient.message_type;
846         if (msgtype == prop_atoms.wm_change_state) {
847             /* compress changes into a single change */
848             while (XCheckTypedWindowEvent(ob_display, e->type,
849                                           client->window, &ce)) {
850                 /* XXX: it would be nice to compress ALL messages of a
851                    type, not just messages in a row without other
852                    message types between. */
853                 if (ce.xclient.message_type != msgtype) {
854                     XPutBackEvent(ob_display, &ce);
855                     break;
856                 }
857                 e->xclient = ce.xclient;
858             }
859             client_set_wm_state(client, e->xclient.data.l[0]);
860         } else if (msgtype == prop_atoms.net_wm_desktop) {
861             /* compress changes into a single change */
862             while (XCheckTypedWindowEvent(ob_display, e->type,
863                                           client->window, &ce)) {
864                 /* XXX: it would be nice to compress ALL messages of a
865                    type, not just messages in a row without other
866                    message types between. */
867                 if (ce.xclient.message_type != msgtype) {
868                     XPutBackEvent(ob_display, &ce);
869                     break;
870                 }
871                 e->xclient = ce.xclient;
872             }
873             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
874                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
875                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
876                                    FALSE);
877         } else if (msgtype == prop_atoms.net_wm_state) {
878             /* can't compress these */
879             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
880                      (e->xclient.data.l[0] == 0 ? "Remove" :
881                       e->xclient.data.l[0] == 1 ? "Add" :
882                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
883                      e->xclient.data.l[1], e->xclient.data.l[2],
884                      client->window);
885             client_set_state(client, e->xclient.data.l[0],
886                              e->xclient.data.l[1], e->xclient.data.l[2]);
887         } else if (msgtype == prop_atoms.net_close_window) {
888             ob_debug("net_close_window for 0x%lx\n", client->window);
889             client_close(client);
890         } else if (msgtype == prop_atoms.net_active_window) {
891             ob_debug("net_active_window for 0x%lx\n", client->window);
892             client_activate(client, FALSE);
893         } else if (msgtype == prop_atoms.net_wm_moveresize) {
894             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
895             if ((Atom)e->xclient.data.l[2] ==
896                 prop_atoms.net_wm_moveresize_size_topleft ||
897                 (Atom)e->xclient.data.l[2] ==
898                 prop_atoms.net_wm_moveresize_size_top ||
899                 (Atom)e->xclient.data.l[2] ==
900                 prop_atoms.net_wm_moveresize_size_topright ||
901                 (Atom)e->xclient.data.l[2] ==
902                 prop_atoms.net_wm_moveresize_size_right ||
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_bottomright ||
907                 (Atom)e->xclient.data.l[2] ==
908                 prop_atoms.net_wm_moveresize_size_bottom ||
909                 (Atom)e->xclient.data.l[2] ==
910                 prop_atoms.net_wm_moveresize_size_bottomleft ||
911                 (Atom)e->xclient.data.l[2] ==
912                 prop_atoms.net_wm_moveresize_size_left ||
913                 (Atom)e->xclient.data.l[2] ==
914                 prop_atoms.net_wm_moveresize_move ||
915                 (Atom)e->xclient.data.l[2] ==
916                 prop_atoms.net_wm_moveresize_size_keyboard ||
917                 (Atom)e->xclient.data.l[2] ==
918                 prop_atoms.net_wm_moveresize_move_keyboard) {
919
920                 moveresize_start(client, e->xclient.data.l[0],
921                                  e->xclient.data.l[1], e->xclient.data.l[3],
922                                  e->xclient.data.l[2]);
923             }
924         } else if (msgtype == prop_atoms.net_moveresize_window) {
925             int oldg = client->gravity;
926             int tmpg, x, y, w, h;
927
928             if (e->xclient.data.l[0] & 0xff)
929                 tmpg = e->xclient.data.l[0] & 0xff;
930             else
931                 tmpg = oldg;
932
933             if (e->xclient.data.l[0] & 1 << 8)
934                 x = e->xclient.data.l[1];
935             else
936                 x = client->area.x;
937             if (e->xclient.data.l[0] & 1 << 9)
938                 y = e->xclient.data.l[2];
939             else
940                 y = client->area.y;
941             if (e->xclient.data.l[0] & 1 << 10)
942                 w = e->xclient.data.l[3];
943             else
944                 w = client->area.width;
945             if (e->xclient.data.l[0] & 1 << 11)
946                 h = e->xclient.data.l[4];
947             else
948                 h = client->area.height;
949             client->gravity = tmpg;
950
951             {
952                 int newx = x;
953                 int newy = y;
954                 int fw = w +
955                     client->frame->size.left + client->frame->size.right;
956                 int fh = h +
957                     client->frame->size.top + client->frame->size.bottom;
958                 client_find_onscreen(client, &newx, &newy, fw, fh,
959                                      client_normal(client));
960                 if (e->xclient.data.l[0] & 1 << 8)
961                     x = newx;
962                 if (e->xclient.data.l[0] & 1 << 9)
963                     y = newy;
964             }
965                
966             client_configure(client, OB_CORNER_TOPLEFT,
967                              x, y, w, h, FALSE, TRUE);
968
969             client->gravity = oldg;
970         }
971         break;
972     case PropertyNotify:
973         /* validate cuz we query stuff off the client here */
974         if (!client_validate(client)) break;
975   
976         /* compress changes to a single property into a single change */
977         while (XCheckTypedWindowEvent(ob_display, e->type,
978                                       client->window, &ce)) {
979             /* XXX: it would be nice to compress ALL changes to a property,
980                not just changes in a row without other props between. */
981             if (ce.xproperty.atom != e->xproperty.atom) {
982                 XPutBackEvent(ob_display, &ce);
983                 break;
984             }
985         }
986
987         msgtype = e->xproperty.atom;
988         if (msgtype == XA_WM_NORMAL_HINTS) {
989             client_update_normal_hints(client);
990             /* normal hints can make a window non-resizable */
991             client_setup_decor_and_functions(client);
992         }
993         else if (msgtype == XA_WM_HINTS)
994             client_update_wmhints(client);
995         else if (msgtype == XA_WM_TRANSIENT_FOR) {
996             client_update_transient_for(client);
997             client_get_type(client);
998             /* type may have changed, so update the layer */
999             client_calc_layer(client);
1000             client_setup_decor_and_functions(client);
1001         }
1002         else if (msgtype == prop_atoms.net_wm_name ||
1003                  msgtype == prop_atoms.wm_name ||
1004                  msgtype == prop_atoms.net_wm_icon_name ||
1005                  msgtype == prop_atoms.wm_icon_name)
1006             client_update_title(client);
1007         else if (msgtype == prop_atoms.wm_class)
1008             client_update_class(client);
1009         else if (msgtype == prop_atoms.wm_protocols) {
1010             client_update_protocols(client);
1011             client_setup_decor_and_functions(client);
1012         }
1013         else if (msgtype == prop_atoms.net_wm_strut) {
1014             client_update_strut(client);
1015         }
1016         else if (msgtype == prop_atoms.net_wm_icon ||
1017                  msgtype == prop_atoms.kwm_win_icon)
1018             client_update_icons(client);
1019     default:
1020         ;
1021 #ifdef SHAPE
1022         if (extensions_shape && e->type == extensions_shape_event_basep) {
1023             client->shaped = ((XShapeEvent*)e)->shaped;
1024             frame_adjust_shape(client->frame);
1025         }
1026 #endif
1027     }
1028 }
1029
1030 static void event_handle_dock(ObDock *s, XEvent *e)
1031 {
1032     switch (e->type) {
1033     case ButtonPress:
1034         stacking_raise(DOCK_AS_WINDOW(s));
1035         break;
1036     case EnterNotify:
1037         dock_hide(FALSE);
1038         break;
1039     case LeaveNotify:
1040         dock_hide(TRUE);
1041         break;
1042     }
1043 }
1044
1045 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1046 {
1047     switch (e->type) {
1048     case MotionNotify:
1049         dock_app_drag(app, &e->xmotion);
1050         break;
1051     case UnmapNotify:
1052         if (app->ignore_unmaps) {
1053             app->ignore_unmaps--;
1054             break;
1055         }
1056         dock_remove(app, TRUE);
1057         break;
1058     case DestroyNotify:
1059         dock_remove(app, FALSE);
1060         break;
1061     case ReparentNotify:
1062         dock_remove(app, FALSE);
1063         break;
1064     case ConfigureNotify:
1065         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1066         break;
1067     }
1068 }
1069
1070 ObMenuFrame* find_active_menu()
1071 {
1072     GList *it;
1073     ObMenuFrame *f;
1074
1075     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1076         f = it->data;
1077         if (f->selected)
1078             break;
1079     }
1080     return it ? it->data : NULL;
1081 }
1082
1083 static void event_handle_menu(XEvent *ev)
1084 {
1085     ObMenuFrame *f;
1086     ObMenuEntryFrame *e;
1087
1088     switch (ev->type) {
1089     case ButtonRelease:
1090         if (!(f = menu_frame_under(ev->xbutton.x_root,
1091                                    ev->xbutton.y_root)))
1092             menu_frame_hide_all();
1093         else {
1094             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1095                                             ev->xbutton.y_root)))
1096                 menu_entry_frame_execute(e,
1097                                          !(ev->xbutton.state & ControlMask));
1098         }
1099         break;
1100     case MotionNotify:
1101         if ((f = menu_frame_under(ev->xmotion.x_root,
1102                                   ev->xmotion.y_root))) {
1103             menu_frame_move_on_screen(f);
1104             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1105                                             ev->xmotion.y_root)))
1106                 menu_frame_select(f, e);
1107         }
1108         break;
1109     case KeyPress:
1110         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1111             menu_frame_hide_all();
1112         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1113             ObMenuFrame *f;
1114             if ((f = find_active_menu()))
1115                 menu_entry_frame_execute(f->selected,
1116                                          !(ev->xkey.state & ControlMask));
1117         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1118             ObMenuFrame *f;
1119             if ((f = find_active_menu()) && f->parent)
1120                 menu_frame_select(f, NULL);
1121         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1122             ObMenuFrame *f;
1123             if ((f = find_active_menu()) && f->child)
1124                 menu_frame_select_next(f->child);
1125         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1126             ObMenuFrame *f;
1127             if ((f = find_active_menu()))
1128                 menu_frame_select_previous(f);
1129         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1130             ObMenuFrame *f;
1131             if ((f = find_active_menu()))
1132                 menu_frame_select_next(f);
1133         }
1134         break;
1135     }
1136 }