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