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