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