]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
focus fallback
[mikachu/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003        Ben Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "mouse.h"
35 #include "mainloop.h"
36 #include "framerender.h"
37 #include "focus.h"
38 #include "moveresize.h"
39 #include "group.h"
40 #include "stacking.h"
41 #include "extensions.h"
42
43 #include <X11/Xlib.h>
44 #include <X11/keysym.h>
45 #include <X11/Xatom.h>
46 #include <glib.h>
47
48 #ifdef HAVE_SYS_SELECT_H
49 #  include <sys/select.h>
50 #endif
51 #ifdef HAVE_SIGNAL_H
52 #  include <signal.h>
53 #endif
54 #ifdef XKB
55 #  include <X11/XKBlib.h>
56 #endif
57
58 #ifdef USE_SM
59 #include <X11/ICE/ICElib.h>
60 #endif
61
62 typedef struct
63 {
64     gboolean ignored;
65 } ObEventData;
66
67 static void event_process(const XEvent *e, gpointer data);
68 static void event_client_dest(ObClient *client, gpointer data);
69 static void event_handle_root(XEvent *e);
70 static void event_handle_menu(XEvent *e);
71 static void event_handle_dock(ObDock *s, XEvent *e);
72 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
73 static void event_handle_client(ObClient *c, XEvent *e);
74 static void event_handle_group(ObGroup *g, XEvent *e);
75
76 static gboolean focus_delay_func(gpointer data);
77 static void focus_delay_client_dest(ObClient *client, gpointer data);
78
79 static gboolean menu_hide_delay_func(gpointer data);
80
81 /* The time for the current event being processed */
82 Time event_curtime = CurrentTime;
83
84 /*! The value of the mask for the NumLock modifier */
85 guint NumLockMask;
86 /*! The value of the mask for the ScrollLock modifier */
87 guint ScrollLockMask;
88 /*! The key codes for the modifier keys */
89 static XModifierKeymap *modmap;
90 /*! Table of the constant modifier masks */
91 static const gint mask_table[] = {
92     ShiftMask, LockMask, ControlMask, Mod1Mask,
93     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
94 };
95 static gint mask_table_size;
96
97 static guint ignore_enter_focus = 0;
98
99 static gboolean menu_can_hide;
100
101 #ifdef USE_SM
102 static void ice_handler(gint fd, gpointer conn)
103 {
104     Bool b;
105     IceProcessMessages(conn, NULL, &b);
106 }
107
108 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
109                       IcePointer *watch_data)
110 {
111     static gint fd = -1;
112
113     if (opening) {
114         fd = IceConnectionNumber(conn);
115         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
116     } else {
117         ob_main_loop_fd_remove(ob_main_loop, fd);
118         fd = -1;
119     }
120 }
121 #endif
122
123 void event_startup(gboolean reconfig)
124 {
125     if (reconfig) return;
126
127     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
128      
129     /* get lock masks that are defined by the display (not constant) */
130     modmap = XGetModifierMapping(ob_display);
131     g_assert(modmap);
132     if (modmap && modmap->max_keypermod > 0) {
133         size_t cnt;
134         const size_t size = mask_table_size * modmap->max_keypermod;
135         /* get the values of the keyboard lock modifiers
136            Note: Caps lock is not retrieved the same way as Scroll and Num
137            lock since it doesn't need to be. */
138         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
139         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
140                                                      XK_Scroll_Lock);
141
142         for (cnt = 0; cnt < size; ++cnt) {
143             if (! modmap->modifiermap[cnt]) continue;
144
145             if (num_lock == modmap->modifiermap[cnt])
146                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
147             if (scroll_lock == modmap->modifiermap[cnt])
148                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
149         }
150     }
151
152     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
153
154 #ifdef USE_SM
155     IceAddConnectionWatch(ice_watch, NULL);
156 #endif
157
158     client_add_destructor(focus_delay_client_dest, NULL);
159     client_add_destructor(event_client_dest, NULL);
160 }
161
162 void event_shutdown(gboolean reconfig)
163 {
164     if (reconfig) return;
165
166 #ifdef USE_SM
167     IceRemoveConnectionWatch(ice_watch, NULL);
168 #endif
169
170     client_remove_destructor(focus_delay_client_dest);
171     client_remove_destructor(event_client_dest);
172     XFreeModifiermap(modmap);
173 }
174
175 static Window event_get_window(XEvent *e)
176 {
177     Window window;
178
179     /* pick a window */
180     switch (e->type) {
181     case SelectionClear:
182         window = RootWindow(ob_display, ob_screen);
183         break;
184     case MapRequest:
185         window = e->xmap.window;
186         break;
187     case UnmapNotify:
188         window = e->xunmap.window;
189         break;
190     case DestroyNotify:
191         window = e->xdestroywindow.window;
192         break;
193     case ConfigureRequest:
194         window = e->xconfigurerequest.window;
195         break;
196     case ConfigureNotify:
197         window = e->xconfigure.window;
198         break;
199     default:
200 #ifdef XKB
201         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
202             switch (((XkbAnyEvent*)e)->xkb_type) {
203             case XkbBellNotify:
204                 window = ((XkbBellNotifyEvent*)e)->window;
205             default:
206                 window = None;
207             }
208         } else
209 #endif
210             window = e->xany.window;
211     }
212     return window;
213 }
214
215 static void event_set_curtime(XEvent *e)
216 {
217     Time t = CurrentTime;
218
219     /* grab the lasttime and hack up the state */
220     switch (e->type) {
221     case ButtonPress:
222     case ButtonRelease:
223         t = e->xbutton.time;
224         break;
225     case KeyPress:
226         t = e->xkey.time;
227         break;
228     case KeyRelease:
229         t = e->xkey.time;
230         break;
231     case MotionNotify:
232         t = e->xmotion.time;
233         break;
234     case PropertyNotify:
235         t = e->xproperty.time;
236         break;
237     case EnterNotify:
238     case LeaveNotify:
239         t = e->xcrossing.time;
240         break;
241     default:
242         /* if more event types are anticipated, get their timestamp
243            explicitly */
244         break;
245     }
246
247     event_curtime = t;
248 }
249
250 #define STRIP_MODS(s) \
251         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
252         /* kill off the Button1Mask etc, only want the modifiers */ \
253         s &= (ControlMask | ShiftMask | Mod1Mask | \
254               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
255
256 static void event_hack_mods(XEvent *e)
257 {
258 #ifdef XKB
259     XkbStateRec xkb_state;
260 #endif
261     KeyCode *kp;
262     gint i, k;
263
264     switch (e->type) {
265     case ButtonPress:
266     case ButtonRelease:
267         STRIP_MODS(e->xbutton.state);
268         break;
269     case KeyPress:
270         STRIP_MODS(e->xkey.state);
271         break;
272     case KeyRelease:
273         STRIP_MODS(e->xkey.state);
274         /* remove from the state the mask of the modifier being released, if
275            it is a modifier key being released (this is a little ugly..) */
276 #ifdef XKB
277         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
278             e->xkey.state = xkb_state.compat_state;
279             break;
280         }
281 #endif
282         kp = modmap->modifiermap;
283         for (i = 0; i < mask_table_size; ++i) {
284             for (k = 0; k < modmap->max_keypermod; ++k) {
285                 if (*kp == e->xkey.keycode) { /* found the keycode */
286                     /* remove the mask for it */
287                     e->xkey.state &= ~mask_table[i];
288                     /* cause the first loop to break; */
289                     i = mask_table_size;
290                     break; /* get outta here! */
291                 }
292                 ++kp;
293             }
294         }
295         break;
296     case MotionNotify:
297         STRIP_MODS(e->xmotion.state);
298         /* compress events */
299         {
300             XEvent ce;
301             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
302                                           e->type, &ce)) {
303                 e->xmotion.x_root = ce.xmotion.x_root;
304                 e->xmotion.y_root = ce.xmotion.y_root;
305             }
306         }
307         break;
308     }
309 }
310
311 static gboolean wanted_focusevent(XEvent *e)
312 {
313     gint mode = e->xfocus.mode;
314     gint detail = e->xfocus.detail;
315
316     if (e->type == FocusIn) {
317
318         /* These are ones we never want.. */
319
320         /* This means focus was given by a keyboard/mouse grab. */
321         if (mode == NotifyGrab)
322             return FALSE;
323         /* This means focus was given back from a keyboard/mouse grab. */
324         if (mode == NotifyUngrab)
325             return FALSE;
326
327         /* These are the ones we want.. */
328
329         /* This means focus moved from the root window to a client */
330         if (detail == NotifyVirtual)
331             return TRUE;
332         /* This means focus moved from one client to another */
333         if (detail == NotifyNonlinearVirtual)
334             return TRUE;
335
336         /* Otherwise.. */
337         return FALSE;
338     } else {
339         g_assert(e->type == FocusOut);
340
341
342         /* These are ones we never want.. */
343
344         /* This means focus was taken by a keyboard/mouse grab. */
345         if (mode == NotifyGrab)
346             return FALSE;
347
348         /* These are the ones we want.. */
349
350         /* This means focus moved from a client to the root window */
351         if (detail == NotifyVirtual)
352             return TRUE;
353         /* This means focus moved from one client to another */
354         if (detail == NotifyNonlinearVirtual)
355             return TRUE;
356
357         /* Otherwise.. */
358         return FALSE;
359     }
360 }
361
362 static Bool look_for_focusin(Display *d, XEvent *e, XPointer arg)
363 {
364     return e->type == FocusIn && wanted_focusevent(e);
365 }
366
367 static gboolean event_ignore(XEvent *e, ObClient *client)
368 {
369     switch(e->type) {
370     case EnterNotify:
371     case LeaveNotify:
372         if (e->xcrossing.detail == NotifyInferior)
373             return TRUE;
374         break;
375     case FocusIn:
376     case FocusOut:
377         /* I don't think this should ever happen with our event masks, but
378            if it does, we don't want it. */
379         if (client == NULL)
380             return TRUE;
381         if (!wanted_focusevent(e))
382             return TRUE;
383         break;
384     }
385     return FALSE;
386 }
387
388 static void event_process(const XEvent *ec, gpointer data)
389 {
390     Window window;
391     ObGroup *group = NULL;
392     ObClient *client = NULL;
393     ObDock *dock = NULL;
394     ObDockApp *dockapp = NULL;
395     ObWindow *obwin = NULL;
396     XEvent ee, *e;
397     ObEventData *ed = data;
398
399     /* make a copy we can mangle */
400     ee = *ec;
401     e = &ee;
402
403     window = event_get_window(e);
404     if (!(e->type == PropertyNotify &&
405           (group = g_hash_table_lookup(group_map, &window))))
406         if ((obwin = g_hash_table_lookup(window_map, &window))) {
407             switch (obwin->type) {
408             case Window_Dock:
409                 dock = WINDOW_AS_DOCK(obwin);
410                 break;
411             case Window_DockApp:
412                 dockapp = WINDOW_AS_DOCKAPP(obwin);
413                 break;
414             case Window_Client:
415                 client = WINDOW_AS_CLIENT(obwin);
416                 break;
417             case Window_Menu:
418             case Window_Internal:
419                 /* not to be used for events */
420                 g_assert_not_reached();
421                 break;
422             }
423         }
424
425 #if 0 /* focus debugging stuff */
426     if (e->type == FocusIn || e->type == FocusOut) {
427         gint mode = e->xfocus.mode;
428         gint detail = e->xfocus.detail;
429         Window window = e->xfocus.window;
430         if (detail == NotifyVirtual) {
431             ob_debug("FOCUS %s NOTIFY VIRTUAL window 0x%x\n",
432                      (e->type == FocusIn ? "IN" : "OUT"), window);
433         }
434
435         else if (detail == NotifyNonlinearVirtual) {
436             ob_debug("FOCUS %s NOTIFY NONLINVIRTUAL window 0x%x\n",
437                      (e->type == FocusIn ? "IN" : "OUT"), window);
438         }
439
440         else
441             ob_debug("UNKNOWN FOCUS %s (d %d, m %d) window 0x%x\n",
442                      (e->type == FocusIn ? "IN" : "OUT"),
443                      detail, mode, window);
444     }
445 #endif
446
447     event_set_curtime(e);
448     event_hack_mods(e);
449     if (event_ignore(e, client)) {
450         if (ed)
451             ed->ignored = TRUE;
452         return;
453     } else if (ed)
454             ed->ignored = FALSE;
455
456     /* deal with it in the kernel */
457     if (group)
458         event_handle_group(group, e);
459     else if (client)
460         event_handle_client(client, e);
461     else if (dockapp)
462         event_handle_dockapp(dockapp, e);
463     else if (dock)
464         event_handle_dock(dock, e);
465     else if (window == RootWindow(ob_display, ob_screen))
466         event_handle_root(e);
467     else if (e->type == MapRequest)
468         client_manage(window);
469     else if (e->type == ConfigureRequest) {
470         /* unhandled configure requests must be used to configure the
471            window directly */
472         XWindowChanges xwc;
473
474         xwc.x = e->xconfigurerequest.x;
475         xwc.y = e->xconfigurerequest.y;
476         xwc.width = e->xconfigurerequest.width;
477         xwc.height = e->xconfigurerequest.height;
478         xwc.border_width = e->xconfigurerequest.border_width;
479         xwc.sibling = e->xconfigurerequest.above;
480         xwc.stack_mode = e->xconfigurerequest.detail;
481        
482         /* we are not to be held responsible if someone sends us an
483            invalid request! */
484         xerror_set_ignore(TRUE);
485         XConfigureWindow(ob_display, window,
486                          e->xconfigurerequest.value_mask, &xwc);
487         xerror_set_ignore(FALSE);
488     }
489
490     /* user input (action-bound) events */
491     if (e->type == ButtonPress || e->type == ButtonRelease ||
492         e->type == MotionNotify || e->type == KeyPress ||
493         e->type == KeyRelease)
494     {
495         if (menu_frame_visible)
496             event_handle_menu(e);
497         else {
498             if (!keyboard_process_interactive_grab(e, &client)) {
499                 if (moveresize_in_progress) {
500                     moveresize_event(e);
501
502                     /* make further actions work on the client being
503                        moved/resized */
504                     client = moveresize_client;
505                 }
506
507                 menu_can_hide = FALSE;
508                 ob_main_loop_timeout_add(ob_main_loop,
509                                          config_menu_hide_delay * 1000,
510                                          menu_hide_delay_func,
511                                          NULL, NULL);
512
513                 if (e->type == ButtonPress || e->type == ButtonRelease ||
514                     e->type == MotionNotify)
515                     mouse_event(client, e);
516                 else if (e->type == KeyPress) {
517                     keyboard_event((focus_cycle_target ? focus_cycle_target :
518                                     (focus_hilite ? focus_hilite : client)),
519                                    e);
520                 }
521             }
522         }
523     }
524     /* if something happens and it's not from an XEvent, then we don't know
525        the time */
526     event_curtime = CurrentTime;
527 }
528
529 static void event_handle_root(XEvent *e)
530 {
531     Atom msgtype;
532      
533     switch(e->type) {
534     case SelectionClear:
535         ob_debug("Another WM has requested to replace us. Exiting.\n");
536         ob_exit_replace();
537         break;
538
539     case ClientMessage:
540         if (e->xclient.format != 32) break;
541
542         msgtype = e->xclient.message_type;
543         if (msgtype == prop_atoms.net_current_desktop) {
544             guint d = e->xclient.data.l[0];
545             if (d < screen_num_desktops)
546                 screen_set_desktop(d);
547         } else if (msgtype == prop_atoms.net_number_of_desktops) {
548             guint d = e->xclient.data.l[0];
549             if (d > 0)
550                 screen_set_num_desktops(d);
551         } else if (msgtype == prop_atoms.net_showing_desktop) {
552             screen_show_desktop(e->xclient.data.l[0] != 0);
553         } else if (msgtype == prop_atoms.ob_control) {
554             if (e->xclient.data.l[0] == 1)
555                 ob_reconfigure();
556             else if (e->xclient.data.l[0] == 2)
557                 ob_restart();
558         }
559         break;
560     case PropertyNotify:
561         if (e->xproperty.atom == prop_atoms.net_desktop_names)
562             screen_update_desktop_names();
563         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
564             screen_update_layout();
565         break;
566     case ConfigureNotify:
567 #ifdef XRANDR
568         XRRUpdateConfiguration(e);
569 #endif
570         screen_resize();
571         break;
572     default:
573         ;
574     }
575 }
576
577 static void event_handle_group(ObGroup *group, XEvent *e)
578 {
579     GSList *it;
580
581     g_assert(e->type == PropertyNotify);
582
583     for (it = group->members; it; it = g_slist_next(it))
584         event_handle_client(it->data, e);
585 }
586
587 void event_enter_client(ObClient *client)
588 {
589     g_assert(config_focus_follow);
590
591     if (client_normal(client) && client_can_focus(client)) {
592         if (config_focus_delay) {
593             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
594             ob_main_loop_timeout_add(ob_main_loop,
595                                      config_focus_delay,
596                                      focus_delay_func,
597                                      client, NULL);
598         } else
599             focus_delay_func(client);
600     }
601 }
602
603 static void event_handle_client(ObClient *client, XEvent *e)
604 {
605     XEvent ce;
606     Atom msgtype;
607     gint i=0;
608     ObFrameContext con;
609      
610     switch (e->type) {
611     case VisibilityNotify:
612         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
613         break;
614     case ButtonPress:
615     case ButtonRelease:
616         /* Wheel buttons don't draw because they are an instant click, so it
617            is a waste of resources to go drawing it. */
618         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
619             con = frame_context(client, e->xbutton.window);
620             con = mouse_button_frame_context(con, e->xbutton.button);
621             switch (con) {
622             case OB_FRAME_CONTEXT_MAXIMIZE:
623                 client->frame->max_press = (e->type == ButtonPress);
624                 framerender_frame(client->frame);
625                 break;
626             case OB_FRAME_CONTEXT_CLOSE:
627                 client->frame->close_press = (e->type == ButtonPress);
628                 framerender_frame(client->frame);
629                 break;
630             case OB_FRAME_CONTEXT_ICONIFY:
631                 client->frame->iconify_press = (e->type == ButtonPress);
632                 framerender_frame(client->frame);
633                 break;
634             case OB_FRAME_CONTEXT_ALLDESKTOPS:
635                 client->frame->desk_press = (e->type == ButtonPress);
636                 framerender_frame(client->frame);
637                 break; 
638             case OB_FRAME_CONTEXT_SHADE:
639                 client->frame->shade_press = (e->type == ButtonPress);
640                 framerender_frame(client->frame);
641                 break;
642             default:
643                 /* nothing changes with clicks for any other contexts */
644                 break;
645             }
646         }
647         break;
648     case FocusIn:
649         if (client != focus_client) {
650             focus_set_client(client);
651             frame_adjust_focus(client->frame, TRUE);
652             client_calc_layer(client);
653         }
654         break;
655     case FocusOut:
656         /* Look for the followup FocusIn */
657         if (!XCheckIfEvent(ob_display, &ce, look_for_focusin, NULL)) {
658             /* There is no FocusIn, move focus where we can still hear events*/
659             focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS);
660         } else if (ce.xany.window == e->xany.window) {
661             /* If focus didn't actually move anywhere, there is nothing to do*/
662             break;
663         } else {
664             /* Focus did move, so process the FocusIn event */
665             ObEventData ed;
666             event_process(&ce, &ed);
667             if (ed.ignored) {
668                 /* The FocusIn was ignored, this means it was on a window
669                    that isn't a client. */
670                 focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS);
671             }
672         }
673
674         /* This client is no longer focused, so show that */
675         focus_hilite = NULL;
676         frame_adjust_focus(client->frame, FALSE);
677         client_calc_layer(client);
678         break;
679     case LeaveNotify:
680         con = frame_context(client, e->xcrossing.window);
681         switch (con) {
682         case OB_FRAME_CONTEXT_MAXIMIZE:
683             client->frame->max_hover = FALSE;
684             frame_adjust_state(client->frame);
685             break;
686         case OB_FRAME_CONTEXT_ALLDESKTOPS:
687             client->frame->desk_hover = FALSE;
688             frame_adjust_state(client->frame);
689             break;
690         case OB_FRAME_CONTEXT_SHADE:
691             client->frame->shade_hover = FALSE;
692             frame_adjust_state(client->frame);
693             break;
694         case OB_FRAME_CONTEXT_ICONIFY:
695             client->frame->iconify_hover = FALSE;
696             frame_adjust_state(client->frame);
697             break;
698         case OB_FRAME_CONTEXT_CLOSE:
699             client->frame->close_hover = FALSE;
700             frame_adjust_state(client->frame);
701             break;
702         case OB_FRAME_CONTEXT_FRAME:
703             if (config_focus_follow && config_focus_delay)
704                 ob_main_loop_timeout_remove_data(ob_main_loop,
705                                                  focus_delay_func,
706                                                  client, TRUE);
707             break;
708         default:
709             break;
710         }
711         break;
712     case EnterNotify:
713     {
714         gboolean nofocus = FALSE;
715
716         if (ignore_enter_focus) {
717             ignore_enter_focus--;
718             nofocus = TRUE;
719         }
720
721         con = frame_context(client, e->xcrossing.window);
722         switch (con) {
723         case OB_FRAME_CONTEXT_MAXIMIZE:
724             client->frame->max_hover = TRUE;
725             frame_adjust_state(client->frame);
726             break;
727         case OB_FRAME_CONTEXT_ALLDESKTOPS:
728             client->frame->desk_hover = TRUE;
729             frame_adjust_state(client->frame);
730             break;
731         case OB_FRAME_CONTEXT_SHADE:
732             client->frame->shade_hover = TRUE;
733             frame_adjust_state(client->frame);
734             break;
735         case OB_FRAME_CONTEXT_ICONIFY:
736             client->frame->iconify_hover = TRUE;
737             frame_adjust_state(client->frame);
738             break;
739         case OB_FRAME_CONTEXT_CLOSE:
740             client->frame->close_hover = TRUE;
741             frame_adjust_state(client->frame);
742             break;
743         case OB_FRAME_CONTEXT_FRAME:
744             if (e->xcrossing.mode == NotifyGrab ||
745                 e->xcrossing.mode == NotifyUngrab)
746             {
747 #ifdef DEBUG_FOCUS
748                 ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
749                          (e->type == EnterNotify ? "Enter" : "Leave"),
750                          e->xcrossing.mode,
751                          e->xcrossing.detail, client?client->window:0);
752 #endif
753             } else {
754 #ifdef DEBUG_FOCUS
755                 ob_debug("%sNotify mode %d detail %d on %lx, "
756                          "focusing window: %d\n",
757                          (e->type == EnterNotify ? "Enter" : "Leave"),
758                          e->xcrossing.mode,
759                          e->xcrossing.detail, (client?client->window:0),
760                          !nofocus);
761 #endif
762                 if (!nofocus && config_focus_follow)
763                     event_enter_client(client);
764             }
765             break;
766         default:
767             break;
768         }
769         break;
770     }
771     case ConfigureRequest:
772         /* compress these */
773         while (XCheckTypedWindowEvent(ob_display, client->window,
774                                       ConfigureRequest, &ce)) {
775             ++i;
776             /* XXX if this causes bad things.. we can compress config req's
777                with the same mask. */
778             e->xconfigurerequest.value_mask |=
779                 ce.xconfigurerequest.value_mask;
780             if (ce.xconfigurerequest.value_mask & CWX)
781                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
782             if (ce.xconfigurerequest.value_mask & CWY)
783                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
784             if (ce.xconfigurerequest.value_mask & CWWidth)
785                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
786             if (ce.xconfigurerequest.value_mask & CWHeight)
787                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
788             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
789                 e->xconfigurerequest.border_width =
790                     ce.xconfigurerequest.border_width;
791             if (ce.xconfigurerequest.value_mask & CWStackMode)
792                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
793         }
794
795         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
796         if (client->iconic || client->shaded) return;
797
798         /* resize, then move, as specified in the EWMH section 7.7 */
799         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
800                                                CWX | CWY |
801                                                CWBorderWidth)) {
802             gint x, y, w, h;
803             ObCorner corner;
804
805             if (e->xconfigurerequest.value_mask & CWBorderWidth)
806                 client->border_width = e->xconfigurerequest.border_width;
807
808             x = (e->xconfigurerequest.value_mask & CWX) ?
809                 e->xconfigurerequest.x : client->area.x;
810             y = (e->xconfigurerequest.value_mask & CWY) ?
811                 e->xconfigurerequest.y : client->area.y;
812             w = (e->xconfigurerequest.value_mask & CWWidth) ?
813                 e->xconfigurerequest.width : client->area.width;
814             h = (e->xconfigurerequest.value_mask & CWHeight) ?
815                 e->xconfigurerequest.height : client->area.height;
816
817             {
818                 gint newx = x;
819                 gint newy = y;
820                 gint fw = w +
821                      client->frame->size.left + client->frame->size.right;
822                 gint fh = h +
823                      client->frame->size.top + client->frame->size.bottom;
824                 /* make this rude for size-only changes but not for position
825                    changes.. */
826                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
827                                    (e->xconfigurerequest.value_mask & CWY));
828
829                 client_find_onscreen(client, &newx, &newy, fw, fh,
830                                      !moving);
831                 if (e->xconfigurerequest.value_mask & CWX)
832                     x = newx;
833                 if (e->xconfigurerequest.value_mask & CWY)
834                     y = newy;
835             }
836
837             switch (client->gravity) {
838             case NorthEastGravity:
839             case EastGravity:
840                 corner = OB_CORNER_TOPRIGHT;
841                 break;
842             case SouthWestGravity:
843             case SouthGravity:
844                 corner = OB_CORNER_BOTTOMLEFT;
845                 break;
846             case SouthEastGravity:
847                 corner = OB_CORNER_BOTTOMRIGHT;
848                 break;
849             default:     /* NorthWest, Static, etc */
850                 corner = OB_CORNER_TOPLEFT;
851             }
852
853             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
854                                   TRUE);
855         }
856
857         if (e->xconfigurerequest.value_mask & CWStackMode) {
858             switch (e->xconfigurerequest.detail) {
859             case Below:
860             case BottomIf:
861                 /* Apps are so rude. And this is totally disconnected from
862                    activation/focus. Bleh. */
863                 /*client_lower(client);*/
864                 break;
865
866             case Above:
867             case TopIf:
868             default:
869                 /* Apps are so rude. And this is totally disconnected from
870                    activation/focus. Bleh. */
871                 /*client_raise(client);*/
872                 break;
873             }
874         }
875         break;
876     case UnmapNotify:
877         if (client->ignore_unmaps) {
878             client->ignore_unmaps--;
879             break;
880         }
881         client_unmanage(client);
882         break;
883     case DestroyNotify:
884         client_unmanage(client);
885         break;
886     case ReparentNotify:
887         /* this is when the client is first taken captive in the frame */
888         if (e->xreparent.parent == client->frame->plate) break;
889
890         /*
891           This event is quite rare and is usually handled in unmapHandler.
892           However, if the window is unmapped when the reparent event occurs,
893           the window manager never sees it because an unmap event is not sent
894           to an already unmapped window.
895         */
896
897         /* we don't want the reparent event, put it back on the stack for the
898            X server to deal with after we unmanage the window */
899         XPutBackEvent(ob_display, e);
900      
901         client_unmanage(client);
902         break;
903     case MapRequest:
904         ob_debug("MapRequest for 0x%lx\n", client->window);
905         if (!client->iconic) break; /* this normally doesn't happen, but if it
906                                        does, we don't want it!
907                                        it can happen now when the window is on
908                                        another desktop, but we still don't
909                                        want it! */
910         client_activate(client, FALSE, TRUE, CurrentTime);
911         break;
912     case ClientMessage:
913         /* validate cuz we query stuff off the client here */
914         if (!client_validate(client)) break;
915
916         if (e->xclient.format != 32) return;
917
918         msgtype = e->xclient.message_type;
919         if (msgtype == prop_atoms.wm_change_state) {
920             /* compress changes into a single change */
921             while (XCheckTypedWindowEvent(ob_display, client->window,
922                                           e->type, &ce)) {
923                 /* XXX: it would be nice to compress ALL messages of a
924                    type, not just messages in a row without other
925                    message types between. */
926                 if (ce.xclient.message_type != msgtype) {
927                     XPutBackEvent(ob_display, &ce);
928                     break;
929                 }
930                 e->xclient = ce.xclient;
931             }
932             client_set_wm_state(client, e->xclient.data.l[0]);
933         } else if (msgtype == prop_atoms.net_wm_desktop) {
934             /* compress changes into a single change */
935             while (XCheckTypedWindowEvent(ob_display, client->window,
936                                           e->type, &ce)) {
937                 /* XXX: it would be nice to compress ALL messages of a
938                    type, not just messages in a row without other
939                    message types between. */
940                 if (ce.xclient.message_type != msgtype) {
941                     XPutBackEvent(ob_display, &ce);
942                     break;
943                 }
944                 e->xclient = ce.xclient;
945             }
946             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
947                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
948                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
949                                    FALSE);
950         } else if (msgtype == prop_atoms.net_wm_state) {
951             /* can't compress these */
952             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
953                      (e->xclient.data.l[0] == 0 ? "Remove" :
954                       e->xclient.data.l[0] == 1 ? "Add" :
955                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
956                      e->xclient.data.l[1], e->xclient.data.l[2],
957                      client->window);
958             client_set_state(client, e->xclient.data.l[0],
959                              e->xclient.data.l[1], e->xclient.data.l[2]);
960         } else if (msgtype == prop_atoms.net_close_window) {
961             ob_debug("net_close_window for 0x%lx\n", client->window);
962             client_close(client);
963         } else if (msgtype == prop_atoms.net_active_window) {
964             ob_debug("net_active_window for 0x%lx source=%s\n",
965                      client->window,
966                      (e->xclient.data.l[0] == 0 ? "unknown" :
967                       (e->xclient.data.l[0] == 1 ? "application" :
968                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
969             /* XXX make use of data.l[1] and [2] ! */
970             client_activate(client, FALSE,
971                             (e->xclient.data.l[0] == 0 ||
972                              e->xclient.data.l[0] == 2),
973                             e->xclient.data.l[1]);
974         } else if (msgtype == prop_atoms.net_wm_moveresize) {
975             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
976                      client->window, e->xclient.data.l[2]);
977             if ((Atom)e->xclient.data.l[2] ==
978                 prop_atoms.net_wm_moveresize_size_topleft ||
979                 (Atom)e->xclient.data.l[2] ==
980                 prop_atoms.net_wm_moveresize_size_top ||
981                 (Atom)e->xclient.data.l[2] ==
982                 prop_atoms.net_wm_moveresize_size_topright ||
983                 (Atom)e->xclient.data.l[2] ==
984                 prop_atoms.net_wm_moveresize_size_right ||
985                 (Atom)e->xclient.data.l[2] ==
986                 prop_atoms.net_wm_moveresize_size_right ||
987                 (Atom)e->xclient.data.l[2] ==
988                 prop_atoms.net_wm_moveresize_size_bottomright ||
989                 (Atom)e->xclient.data.l[2] ==
990                 prop_atoms.net_wm_moveresize_size_bottom ||
991                 (Atom)e->xclient.data.l[2] ==
992                 prop_atoms.net_wm_moveresize_size_bottomleft ||
993                 (Atom)e->xclient.data.l[2] ==
994                 prop_atoms.net_wm_moveresize_size_left ||
995                 (Atom)e->xclient.data.l[2] ==
996                 prop_atoms.net_wm_moveresize_move ||
997                 (Atom)e->xclient.data.l[2] ==
998                 prop_atoms.net_wm_moveresize_size_keyboard ||
999                 (Atom)e->xclient.data.l[2] ==
1000                 prop_atoms.net_wm_moveresize_move_keyboard) {
1001
1002                 moveresize_start(client, e->xclient.data.l[0],
1003                                  e->xclient.data.l[1], e->xclient.data.l[3],
1004                                  e->xclient.data.l[2]);
1005             }
1006             else if ((Atom)e->xclient.data.l[2] ==
1007                      prop_atoms.net_wm_moveresize_cancel)
1008                 moveresize_end(TRUE);
1009         } else if (msgtype == prop_atoms.net_moveresize_window) {
1010             gint oldg = client->gravity;
1011             gint tmpg, x, y, w, h;
1012
1013             if (e->xclient.data.l[0] & 0xff)
1014                 tmpg = e->xclient.data.l[0] & 0xff;
1015             else
1016                 tmpg = oldg;
1017
1018             if (e->xclient.data.l[0] & 1 << 8)
1019                 x = e->xclient.data.l[1];
1020             else
1021                 x = client->area.x;
1022             if (e->xclient.data.l[0] & 1 << 9)
1023                 y = e->xclient.data.l[2];
1024             else
1025                 y = client->area.y;
1026             if (e->xclient.data.l[0] & 1 << 10)
1027                 w = e->xclient.data.l[3];
1028             else
1029                 w = client->area.width;
1030             if (e->xclient.data.l[0] & 1 << 11)
1031                 h = e->xclient.data.l[4];
1032             else
1033                 h = client->area.height;
1034             client->gravity = tmpg;
1035
1036             {
1037                 gint newx = x;
1038                 gint newy = y;
1039                 gint fw = w +
1040                      client->frame->size.left + client->frame->size.right;
1041                 gint fh = h +
1042                      client->frame->size.top + client->frame->size.bottom;
1043                 client_find_onscreen(client, &newx, &newy, fw, fh,
1044                                      client_normal(client));
1045                 if (e->xclient.data.l[0] & 1 << 8)
1046                     x = newx;
1047                 if (e->xclient.data.l[0] & 1 << 9)
1048                     y = newy;
1049             }
1050
1051             client_configure(client, OB_CORNER_TOPLEFT,
1052                              x, y, w, h, FALSE, TRUE);
1053
1054             client->gravity = oldg;
1055         }
1056         break;
1057     case PropertyNotify:
1058         /* validate cuz we query stuff off the client here */
1059         if (!client_validate(client)) break;
1060   
1061         /* compress changes to a single property into a single change */
1062         while (XCheckTypedWindowEvent(ob_display, client->window,
1063                                       e->type, &ce)) {
1064             Atom a, b;
1065
1066             /* XXX: it would be nice to compress ALL changes to a property,
1067                not just changes in a row without other props between. */
1068
1069             a = ce.xproperty.atom;
1070             b = e->xproperty.atom;
1071
1072             if (a == b)
1073                 continue;
1074             if ((a == prop_atoms.net_wm_name ||
1075                  a == prop_atoms.wm_name ||
1076                  a == prop_atoms.net_wm_icon_name ||
1077                  a == prop_atoms.wm_icon_name)
1078                 &&
1079                 (b == prop_atoms.net_wm_name ||
1080                  b == prop_atoms.wm_name ||
1081                  b == prop_atoms.net_wm_icon_name ||
1082                  b == prop_atoms.wm_icon_name)) {
1083                 continue;
1084             }
1085             if (a == prop_atoms.net_wm_icon &&
1086                 b == prop_atoms.net_wm_icon)
1087                 continue;
1088
1089             XPutBackEvent(ob_display, &ce);
1090             break;
1091         }
1092
1093         msgtype = e->xproperty.atom;
1094         if (msgtype == XA_WM_NORMAL_HINTS) {
1095             client_update_normal_hints(client);
1096             /* normal hints can make a window non-resizable */
1097             client_setup_decor_and_functions(client);
1098         } else if (msgtype == XA_WM_HINTS) {
1099             client_update_wmhints(client);
1100         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1101             client_update_transient_for(client);
1102             client_get_type(client);
1103             /* type may have changed, so update the layer */
1104             client_calc_layer(client);
1105             client_setup_decor_and_functions(client);
1106         } else if (msgtype == prop_atoms.net_wm_name ||
1107                    msgtype == prop_atoms.wm_name ||
1108                    msgtype == prop_atoms.net_wm_icon_name ||
1109                    msgtype == prop_atoms.wm_icon_name) {
1110             client_update_title(client);
1111         } else if (msgtype == prop_atoms.wm_class) {
1112             client_update_class(client);
1113         } else if (msgtype == prop_atoms.wm_protocols) {
1114             client_update_protocols(client);
1115             client_setup_decor_and_functions(client);
1116         }
1117         else if (msgtype == prop_atoms.net_wm_strut) {
1118             client_update_strut(client);
1119         }
1120         else if (msgtype == prop_atoms.net_wm_icon) {
1121             client_update_icons(client);
1122         }
1123         else if (msgtype == prop_atoms.net_wm_user_time) {
1124             client_update_user_time(client, TRUE);
1125         }
1126         else if (msgtype == prop_atoms.sm_client_id) {
1127             client_update_sm_client_id(client);
1128         }
1129     default:
1130         ;
1131 #ifdef SHAPE
1132         if (extensions_shape && e->type == extensions_shape_event_basep) {
1133             client->shaped = ((XShapeEvent*)e)->shaped;
1134             frame_adjust_shape(client->frame);
1135         }
1136 #endif
1137     }
1138 }
1139
1140 static void event_handle_dock(ObDock *s, XEvent *e)
1141 {
1142     switch (e->type) {
1143     case ButtonPress:
1144         if (e->xbutton.button == 1)
1145             stacking_raise(DOCK_AS_WINDOW(s));
1146         else if (e->xbutton.button == 2)
1147             stacking_lower(DOCK_AS_WINDOW(s));
1148         break;
1149     case EnterNotify:
1150         dock_hide(FALSE);
1151         break;
1152     case LeaveNotify:
1153         dock_hide(TRUE);
1154         break;
1155     }
1156 }
1157
1158 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1159 {
1160     switch (e->type) {
1161     case MotionNotify:
1162         dock_app_drag(app, &e->xmotion);
1163         break;
1164     case UnmapNotify:
1165         if (app->ignore_unmaps) {
1166             app->ignore_unmaps--;
1167             break;
1168         }
1169         dock_remove(app, TRUE);
1170         break;
1171     case DestroyNotify:
1172         dock_remove(app, FALSE);
1173         break;
1174     case ReparentNotify:
1175         dock_remove(app, FALSE);
1176         break;
1177     case ConfigureNotify:
1178         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1179         break;
1180     }
1181 }
1182
1183 ObMenuFrame* find_active_menu()
1184 {
1185     GList *it;
1186     ObMenuFrame *ret = NULL;
1187
1188     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1189         ret = it->data;
1190         if (ret->selected)
1191             break;
1192         ret = NULL;
1193     }
1194     return ret;
1195 }
1196
1197 ObMenuFrame* find_active_or_last_menu()
1198 {
1199     ObMenuFrame *ret = NULL;
1200
1201     ret = find_active_menu();
1202     if (!ret && menu_frame_visible)
1203         ret = menu_frame_visible->data;
1204     return ret;
1205 }
1206
1207 static void event_handle_menu(XEvent *ev)
1208 {
1209     ObMenuFrame *f;
1210     ObMenuEntryFrame *e;
1211
1212     switch (ev->type) {
1213     case ButtonRelease:
1214         if (menu_can_hide) {
1215             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1216                                             ev->xbutton.y_root)))
1217                 menu_entry_frame_execute(e, ev->xbutton.state,
1218                                          ev->xbutton.time);
1219             else
1220                 menu_frame_hide_all();
1221         }
1222         break;
1223     case MotionNotify:
1224         if ((f = menu_frame_under(ev->xmotion.x_root,
1225                                   ev->xmotion.y_root))) {
1226             menu_frame_move_on_screen(f);
1227             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1228                                             ev->xmotion.y_root)))
1229                 menu_frame_select(f, e);
1230         }
1231         {
1232             ObMenuFrame *a;
1233
1234             a = find_active_menu();
1235             if (a && a != f &&
1236                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1237             {
1238                 menu_frame_select(a, NULL);
1239             }
1240         }
1241         break;
1242     case KeyPress:
1243         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1244             menu_frame_hide_all();
1245         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1246             ObMenuFrame *f;
1247             if ((f = find_active_menu()))
1248                 menu_entry_frame_execute(f->selected, ev->xkey.state,
1249                                          ev->xkey.time);
1250         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1251             ObMenuFrame *f;
1252             if ((f = find_active_or_last_menu()) && f->parent)
1253                 menu_frame_select(f, NULL);
1254         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1255             ObMenuFrame *f;
1256             if ((f = find_active_or_last_menu()) && f->child)
1257                 menu_frame_select_next(f->child);
1258         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1259             ObMenuFrame *f;
1260             if ((f = find_active_or_last_menu()))
1261                 menu_frame_select_previous(f);
1262         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1263             ObMenuFrame *f;
1264             if ((f = find_active_or_last_menu()))
1265                 menu_frame_select_next(f);
1266         }
1267         break;
1268     }
1269 }
1270
1271 static gboolean menu_hide_delay_func(gpointer data)
1272 {
1273     menu_can_hide = TRUE;
1274     return FALSE; /* no repeat */
1275 }
1276
1277 static gboolean focus_delay_func(gpointer data)
1278 {
1279     ObClient *c = data;
1280
1281     if (focus_client != c) {
1282         if (client_validate(c)) {
1283             client_focus(c);
1284             if (config_focus_raise)
1285                 client_raise(c);
1286         }
1287     }
1288     return FALSE; /* no repeat */
1289 }
1290
1291 static void focus_delay_client_dest(ObClient *client, gpointer data)
1292 {
1293     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1294                                      client, TRUE);
1295 }
1296
1297 static void event_client_dest(ObClient *client, gpointer data)
1298 {
1299     if (client == focus_hilite)
1300         focus_hilite = NULL;
1301 }
1302
1303 void event_halt_focus_delay()
1304 {
1305     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1306 }
1307
1308 void event_ignore_queued_enters()
1309 {
1310     GSList *saved = NULL, *it;
1311     XEvent *e;
1312                 
1313     XSync(ob_display, FALSE);
1314
1315     /* count the events */
1316     while (TRUE) {
1317         e = g_new(XEvent, 1);
1318         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1319             ObWindow *win;
1320             
1321             win = g_hash_table_lookup(window_map, &e->xany.window);
1322             if (win && WINDOW_IS_CLIENT(win))
1323                 ++ignore_enter_focus;
1324             
1325             saved = g_slist_append(saved, e);
1326         } else {
1327             g_free(e);
1328             break;
1329         }
1330     }
1331     /* put the events back */
1332     for (it = saved; it; it = g_slist_next(it)) {
1333         XPutBackEvent(ob_display, it->data);
1334         g_free(it->data);
1335     }
1336     g_slist_free(saved);
1337 }