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