only refocus the omnipresent window if its allowed
[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 1 /* 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, this means focus went to a window that
659                is not being managed. most likely, this went to PointerRoot
660                or None, meaning the window is no longer around so fallback
661                focus, but not to that window */
662             ob_debug("Focus went to a black hole !\n");
663             focus_fallback(FALSE);
664         } else if (ce.xany.window == e->xany.window) {
665             /* If focus didn't actually move anywhere, there is nothing to do*/
666             break;
667         } else {
668             /* Focus did move, so process the FocusIn event */
669             ObEventData ed;
670             event_process(&ce, &ed);
671             if (ed.ignored) {
672                 /* The FocusIn was ignored, this means it was on a window
673                    that isn't a client. */
674                 ob_debug("Focus went to an unmanaged window 0x%x !\n",
675                          ce.xfocus.window);
676                 focus_fallback(TRUE);
677             }
678         }
679
680         /* This client is no longer focused, so show that */
681         focus_hilite = NULL;
682         frame_adjust_focus(client->frame, FALSE);
683         client_calc_layer(client);
684         break;
685     case LeaveNotify:
686         con = frame_context(client, e->xcrossing.window);
687         switch (con) {
688         case OB_FRAME_CONTEXT_MAXIMIZE:
689             client->frame->max_hover = FALSE;
690             frame_adjust_state(client->frame);
691             break;
692         case OB_FRAME_CONTEXT_ALLDESKTOPS:
693             client->frame->desk_hover = FALSE;
694             frame_adjust_state(client->frame);
695             break;
696         case OB_FRAME_CONTEXT_SHADE:
697             client->frame->shade_hover = FALSE;
698             frame_adjust_state(client->frame);
699             break;
700         case OB_FRAME_CONTEXT_ICONIFY:
701             client->frame->iconify_hover = FALSE;
702             frame_adjust_state(client->frame);
703             break;
704         case OB_FRAME_CONTEXT_CLOSE:
705             client->frame->close_hover = FALSE;
706             frame_adjust_state(client->frame);
707             break;
708         case OB_FRAME_CONTEXT_FRAME:
709             if (config_focus_follow && config_focus_delay)
710                 ob_main_loop_timeout_remove_data(ob_main_loop,
711                                                  focus_delay_func,
712                                                  client, TRUE);
713             break;
714         default:
715             break;
716         }
717         break;
718     case EnterNotify:
719     {
720         gboolean nofocus = FALSE;
721
722         if (ignore_enter_focus) {
723             ignore_enter_focus--;
724             nofocus = TRUE;
725         }
726
727         con = frame_context(client, e->xcrossing.window);
728         switch (con) {
729         case OB_FRAME_CONTEXT_MAXIMIZE:
730             client->frame->max_hover = TRUE;
731             frame_adjust_state(client->frame);
732             break;
733         case OB_FRAME_CONTEXT_ALLDESKTOPS:
734             client->frame->desk_hover = TRUE;
735             frame_adjust_state(client->frame);
736             break;
737         case OB_FRAME_CONTEXT_SHADE:
738             client->frame->shade_hover = TRUE;
739             frame_adjust_state(client->frame);
740             break;
741         case OB_FRAME_CONTEXT_ICONIFY:
742             client->frame->iconify_hover = TRUE;
743             frame_adjust_state(client->frame);
744             break;
745         case OB_FRAME_CONTEXT_CLOSE:
746             client->frame->close_hover = TRUE;
747             frame_adjust_state(client->frame);
748             break;
749         case OB_FRAME_CONTEXT_FRAME:
750             if (e->xcrossing.mode == NotifyGrab ||
751                 e->xcrossing.mode == NotifyUngrab)
752             {
753 #ifdef DEBUG_FOCUS
754                 ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
755                          (e->type == EnterNotify ? "Enter" : "Leave"),
756                          e->xcrossing.mode,
757                          e->xcrossing.detail, client?client->window:0);
758 #endif
759             } else {
760 #ifdef DEBUG_FOCUS
761                 ob_debug("%sNotify mode %d detail %d on %lx, "
762                          "focusing window: %d\n",
763                          (e->type == EnterNotify ? "Enter" : "Leave"),
764                          e->xcrossing.mode,
765                          e->xcrossing.detail, (client?client->window:0),
766                          !nofocus);
767 #endif
768                 if (!nofocus && config_focus_follow)
769                     event_enter_client(client);
770             }
771             break;
772         default:
773             break;
774         }
775         break;
776     }
777     case ConfigureRequest:
778         /* compress these */
779         while (XCheckTypedWindowEvent(ob_display, client->window,
780                                       ConfigureRequest, &ce)) {
781             ++i;
782             /* XXX if this causes bad things.. we can compress config req's
783                with the same mask. */
784             e->xconfigurerequest.value_mask |=
785                 ce.xconfigurerequest.value_mask;
786             if (ce.xconfigurerequest.value_mask & CWX)
787                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
788             if (ce.xconfigurerequest.value_mask & CWY)
789                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
790             if (ce.xconfigurerequest.value_mask & CWWidth)
791                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
792             if (ce.xconfigurerequest.value_mask & CWHeight)
793                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
794             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
795                 e->xconfigurerequest.border_width =
796                     ce.xconfigurerequest.border_width;
797             if (ce.xconfigurerequest.value_mask & CWStackMode)
798                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
799         }
800
801         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
802         if (client->iconic || client->shaded) return;
803
804         /* resize, then move, as specified in the EWMH section 7.7 */
805         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
806                                                CWX | CWY |
807                                                CWBorderWidth)) {
808             gint x, y, w, h;
809             ObCorner corner;
810
811             if (e->xconfigurerequest.value_mask & CWBorderWidth)
812                 client->border_width = e->xconfigurerequest.border_width;
813
814             x = (e->xconfigurerequest.value_mask & CWX) ?
815                 e->xconfigurerequest.x : client->area.x;
816             y = (e->xconfigurerequest.value_mask & CWY) ?
817                 e->xconfigurerequest.y : client->area.y;
818             w = (e->xconfigurerequest.value_mask & CWWidth) ?
819                 e->xconfigurerequest.width : client->area.width;
820             h = (e->xconfigurerequest.value_mask & CWHeight) ?
821                 e->xconfigurerequest.height : client->area.height;
822
823             {
824                 gint newx = x;
825                 gint newy = y;
826                 gint fw = w +
827                      client->frame->size.left + client->frame->size.right;
828                 gint fh = h +
829                      client->frame->size.top + client->frame->size.bottom;
830                 /* make this rude for size-only changes but not for position
831                    changes.. */
832                 gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
833                                    (e->xconfigurerequest.value_mask & CWY));
834
835                 client_find_onscreen(client, &newx, &newy, fw, fh,
836                                      !moving);
837                 if (e->xconfigurerequest.value_mask & CWX)
838                     x = newx;
839                 if (e->xconfigurerequest.value_mask & CWY)
840                     y = newy;
841             }
842
843             switch (client->gravity) {
844             case NorthEastGravity:
845             case EastGravity:
846                 corner = OB_CORNER_TOPRIGHT;
847                 break;
848             case SouthWestGravity:
849             case SouthGravity:
850                 corner = OB_CORNER_BOTTOMLEFT;
851                 break;
852             case SouthEastGravity:
853                 corner = OB_CORNER_BOTTOMRIGHT;
854                 break;
855             default:     /* NorthWest, Static, etc */
856                 corner = OB_CORNER_TOPLEFT;
857             }
858
859             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
860                                   TRUE);
861         }
862
863         if (e->xconfigurerequest.value_mask & CWStackMode) {
864             switch (e->xconfigurerequest.detail) {
865             case Below:
866             case BottomIf:
867                 /* Apps are so rude. And this is totally disconnected from
868                    activation/focus. Bleh. */
869                 /*client_lower(client);*/
870                 break;
871
872             case Above:
873             case TopIf:
874             default:
875                 /* Apps are so rude. And this is totally disconnected from
876                    activation/focus. Bleh. */
877                 /*client_raise(client);*/
878                 break;
879             }
880         }
881         break;
882     case UnmapNotify:
883         if (client->ignore_unmaps) {
884             client->ignore_unmaps--;
885             break;
886         }
887         client_unmanage(client);
888         break;
889     case DestroyNotify:
890         client_unmanage(client);
891         break;
892     case ReparentNotify:
893         /* this is when the client is first taken captive in the frame */
894         if (e->xreparent.parent == client->frame->plate) break;
895
896         /*
897           This event is quite rare and is usually handled in unmapHandler.
898           However, if the window is unmapped when the reparent event occurs,
899           the window manager never sees it because an unmap event is not sent
900           to an already unmapped window.
901         */
902
903         /* we don't want the reparent event, put it back on the stack for the
904            X server to deal with after we unmanage the window */
905         XPutBackEvent(ob_display, e);
906      
907         client_unmanage(client);
908         break;
909     case MapRequest:
910         ob_debug("MapRequest for 0x%lx\n", client->window);
911         if (!client->iconic) break; /* this normally doesn't happen, but if it
912                                        does, we don't want it!
913                                        it can happen now when the window is on
914                                        another desktop, but we still don't
915                                        want it! */
916         client_activate(client, FALSE, TRUE, CurrentTime);
917         break;
918     case ClientMessage:
919         /* validate cuz we query stuff off the client here */
920         if (!client_validate(client)) break;
921
922         if (e->xclient.format != 32) return;
923
924         msgtype = e->xclient.message_type;
925         if (msgtype == prop_atoms.wm_change_state) {
926             /* compress changes into a single change */
927             while (XCheckTypedWindowEvent(ob_display, client->window,
928                                           e->type, &ce)) {
929                 /* XXX: it would be nice to compress ALL messages of a
930                    type, not just messages in a row without other
931                    message types between. */
932                 if (ce.xclient.message_type != msgtype) {
933                     XPutBackEvent(ob_display, &ce);
934                     break;
935                 }
936                 e->xclient = ce.xclient;
937             }
938             client_set_wm_state(client, e->xclient.data.l[0]);
939         } else if (msgtype == prop_atoms.net_wm_desktop) {
940             /* compress changes into a single change */
941             while (XCheckTypedWindowEvent(ob_display, client->window,
942                                           e->type, &ce)) {
943                 /* XXX: it would be nice to compress ALL messages of a
944                    type, not just messages in a row without other
945                    message types between. */
946                 if (ce.xclient.message_type != msgtype) {
947                     XPutBackEvent(ob_display, &ce);
948                     break;
949                 }
950                 e->xclient = ce.xclient;
951             }
952             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
953                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
954                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
955                                    FALSE);
956         } else if (msgtype == prop_atoms.net_wm_state) {
957             /* can't compress these */
958             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
959                      (e->xclient.data.l[0] == 0 ? "Remove" :
960                       e->xclient.data.l[0] == 1 ? "Add" :
961                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
962                      e->xclient.data.l[1], e->xclient.data.l[2],
963                      client->window);
964             client_set_state(client, e->xclient.data.l[0],
965                              e->xclient.data.l[1], e->xclient.data.l[2]);
966         } else if (msgtype == prop_atoms.net_close_window) {
967             ob_debug("net_close_window for 0x%lx\n", client->window);
968             client_close(client);
969         } else if (msgtype == prop_atoms.net_active_window) {
970             ob_debug("net_active_window for 0x%lx source=%s\n",
971                      client->window,
972                      (e->xclient.data.l[0] == 0 ? "unknown" :
973                       (e->xclient.data.l[0] == 1 ? "application" :
974                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
975             /* XXX make use of data.l[1] and [2] ! */
976             client_activate(client, FALSE,
977                             (e->xclient.data.l[0] == 0 ||
978                              e->xclient.data.l[0] == 2),
979                             e->xclient.data.l[1]);
980         } else if (msgtype == prop_atoms.net_wm_moveresize) {
981             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
982                      client->window, e->xclient.data.l[2]);
983             if ((Atom)e->xclient.data.l[2] ==
984                 prop_atoms.net_wm_moveresize_size_topleft ||
985                 (Atom)e->xclient.data.l[2] ==
986                 prop_atoms.net_wm_moveresize_size_top ||
987                 (Atom)e->xclient.data.l[2] ==
988                 prop_atoms.net_wm_moveresize_size_topright ||
989                 (Atom)e->xclient.data.l[2] ==
990                 prop_atoms.net_wm_moveresize_size_right ||
991                 (Atom)e->xclient.data.l[2] ==
992                 prop_atoms.net_wm_moveresize_size_right ||
993                 (Atom)e->xclient.data.l[2] ==
994                 prop_atoms.net_wm_moveresize_size_bottomright ||
995                 (Atom)e->xclient.data.l[2] ==
996                 prop_atoms.net_wm_moveresize_size_bottom ||
997                 (Atom)e->xclient.data.l[2] ==
998                 prop_atoms.net_wm_moveresize_size_bottomleft ||
999                 (Atom)e->xclient.data.l[2] ==
1000                 prop_atoms.net_wm_moveresize_size_left ||
1001                 (Atom)e->xclient.data.l[2] ==
1002                 prop_atoms.net_wm_moveresize_move ||
1003                 (Atom)e->xclient.data.l[2] ==
1004                 prop_atoms.net_wm_moveresize_size_keyboard ||
1005                 (Atom)e->xclient.data.l[2] ==
1006                 prop_atoms.net_wm_moveresize_move_keyboard) {
1007
1008                 moveresize_start(client, e->xclient.data.l[0],
1009                                  e->xclient.data.l[1], e->xclient.data.l[3],
1010                                  e->xclient.data.l[2]);
1011             }
1012             else if ((Atom)e->xclient.data.l[2] ==
1013                      prop_atoms.net_wm_moveresize_cancel)
1014                 moveresize_end(TRUE);
1015         } else if (msgtype == prop_atoms.net_moveresize_window) {
1016             gint oldg = client->gravity;
1017             gint tmpg, x, y, w, h;
1018
1019             if (e->xclient.data.l[0] & 0xff)
1020                 tmpg = e->xclient.data.l[0] & 0xff;
1021             else
1022                 tmpg = oldg;
1023
1024             if (e->xclient.data.l[0] & 1 << 8)
1025                 x = e->xclient.data.l[1];
1026             else
1027                 x = client->area.x;
1028             if (e->xclient.data.l[0] & 1 << 9)
1029                 y = e->xclient.data.l[2];
1030             else
1031                 y = client->area.y;
1032             if (e->xclient.data.l[0] & 1 << 10)
1033                 w = e->xclient.data.l[3];
1034             else
1035                 w = client->area.width;
1036             if (e->xclient.data.l[0] & 1 << 11)
1037                 h = e->xclient.data.l[4];
1038             else
1039                 h = client->area.height;
1040             client->gravity = tmpg;
1041
1042             {
1043                 gint newx = x;
1044                 gint newy = y;
1045                 gint fw = w +
1046                      client->frame->size.left + client->frame->size.right;
1047                 gint fh = h +
1048                      client->frame->size.top + client->frame->size.bottom;
1049                 client_find_onscreen(client, &newx, &newy, fw, fh,
1050                                      client_normal(client));
1051                 if (e->xclient.data.l[0] & 1 << 8)
1052                     x = newx;
1053                 if (e->xclient.data.l[0] & 1 << 9)
1054                     y = newy;
1055             }
1056
1057             client_configure(client, OB_CORNER_TOPLEFT,
1058                              x, y, w, h, FALSE, TRUE);
1059
1060             client->gravity = oldg;
1061         }
1062         break;
1063     case PropertyNotify:
1064         /* validate cuz we query stuff off the client here */
1065         if (!client_validate(client)) break;
1066   
1067         /* compress changes to a single property into a single change */
1068         while (XCheckTypedWindowEvent(ob_display, client->window,
1069                                       e->type, &ce)) {
1070             Atom a, b;
1071
1072             /* XXX: it would be nice to compress ALL changes to a property,
1073                not just changes in a row without other props between. */
1074
1075             a = ce.xproperty.atom;
1076             b = e->xproperty.atom;
1077
1078             if (a == b)
1079                 continue;
1080             if ((a == prop_atoms.net_wm_name ||
1081                  a == prop_atoms.wm_name ||
1082                  a == prop_atoms.net_wm_icon_name ||
1083                  a == prop_atoms.wm_icon_name)
1084                 &&
1085                 (b == prop_atoms.net_wm_name ||
1086                  b == prop_atoms.wm_name ||
1087                  b == prop_atoms.net_wm_icon_name ||
1088                  b == prop_atoms.wm_icon_name)) {
1089                 continue;
1090             }
1091             if (a == prop_atoms.net_wm_icon &&
1092                 b == prop_atoms.net_wm_icon)
1093                 continue;
1094
1095             XPutBackEvent(ob_display, &ce);
1096             break;
1097         }
1098
1099         msgtype = e->xproperty.atom;
1100         if (msgtype == XA_WM_NORMAL_HINTS) {
1101             client_update_normal_hints(client);
1102             /* normal hints can make a window non-resizable */
1103             client_setup_decor_and_functions(client);
1104         } else if (msgtype == XA_WM_HINTS) {
1105             client_update_wmhints(client);
1106         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1107             client_update_transient_for(client);
1108             client_get_type(client);
1109             /* type may have changed, so update the layer */
1110             client_calc_layer(client);
1111             client_setup_decor_and_functions(client);
1112         } else if (msgtype == prop_atoms.net_wm_name ||
1113                    msgtype == prop_atoms.wm_name ||
1114                    msgtype == prop_atoms.net_wm_icon_name ||
1115                    msgtype == prop_atoms.wm_icon_name) {
1116             client_update_title(client);
1117         } else if (msgtype == prop_atoms.wm_class) {
1118             client_update_class(client);
1119         } else if (msgtype == prop_atoms.wm_protocols) {
1120             client_update_protocols(client);
1121             client_setup_decor_and_functions(client);
1122         }
1123         else if (msgtype == prop_atoms.net_wm_strut) {
1124             client_update_strut(client);
1125         }
1126         else if (msgtype == prop_atoms.net_wm_icon) {
1127             client_update_icons(client);
1128         }
1129         else if (msgtype == prop_atoms.net_wm_user_time) {
1130             client_update_user_time(client, TRUE);
1131         }
1132         else if (msgtype == prop_atoms.sm_client_id) {
1133             client_update_sm_client_id(client);
1134         }
1135     default:
1136         ;
1137 #ifdef SHAPE
1138         if (extensions_shape && e->type == extensions_shape_event_basep) {
1139             client->shaped = ((XShapeEvent*)e)->shaped;
1140             frame_adjust_shape(client->frame);
1141         }
1142 #endif
1143     }
1144 }
1145
1146 static void event_handle_dock(ObDock *s, XEvent *e)
1147 {
1148     switch (e->type) {
1149     case ButtonPress:
1150         if (e->xbutton.button == 1)
1151             stacking_raise(DOCK_AS_WINDOW(s));
1152         else if (e->xbutton.button == 2)
1153             stacking_lower(DOCK_AS_WINDOW(s));
1154         break;
1155     case EnterNotify:
1156         dock_hide(FALSE);
1157         break;
1158     case LeaveNotify:
1159         dock_hide(TRUE);
1160         break;
1161     }
1162 }
1163
1164 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1165 {
1166     switch (e->type) {
1167     case MotionNotify:
1168         dock_app_drag(app, &e->xmotion);
1169         break;
1170     case UnmapNotify:
1171         if (app->ignore_unmaps) {
1172             app->ignore_unmaps--;
1173             break;
1174         }
1175         dock_remove(app, TRUE);
1176         break;
1177     case DestroyNotify:
1178         dock_remove(app, FALSE);
1179         break;
1180     case ReparentNotify:
1181         dock_remove(app, FALSE);
1182         break;
1183     case ConfigureNotify:
1184         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1185         break;
1186     }
1187 }
1188
1189 ObMenuFrame* find_active_menu()
1190 {
1191     GList *it;
1192     ObMenuFrame *ret = NULL;
1193
1194     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1195         ret = it->data;
1196         if (ret->selected)
1197             break;
1198         ret = NULL;
1199     }
1200     return ret;
1201 }
1202
1203 ObMenuFrame* find_active_or_last_menu()
1204 {
1205     ObMenuFrame *ret = NULL;
1206
1207     ret = find_active_menu();
1208     if (!ret && menu_frame_visible)
1209         ret = menu_frame_visible->data;
1210     return ret;
1211 }
1212
1213 static void event_handle_menu(XEvent *ev)
1214 {
1215     ObMenuFrame *f;
1216     ObMenuEntryFrame *e;
1217
1218     switch (ev->type) {
1219     case ButtonRelease:
1220         if (menu_can_hide) {
1221             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1222                                             ev->xbutton.y_root)))
1223                 menu_entry_frame_execute(e, ev->xbutton.state,
1224                                          ev->xbutton.time);
1225             else
1226                 menu_frame_hide_all();
1227         }
1228         break;
1229     case MotionNotify:
1230         if ((f = menu_frame_under(ev->xmotion.x_root,
1231                                   ev->xmotion.y_root))) {
1232             menu_frame_move_on_screen(f);
1233             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1234                                             ev->xmotion.y_root)))
1235                 menu_frame_select(f, e);
1236         }
1237         {
1238             ObMenuFrame *a;
1239
1240             a = find_active_menu();
1241             if (a && a != f &&
1242                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1243             {
1244                 menu_frame_select(a, NULL);
1245             }
1246         }
1247         break;
1248     case KeyPress:
1249         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1250             menu_frame_hide_all();
1251         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1252             ObMenuFrame *f;
1253             if ((f = find_active_menu()))
1254                 menu_entry_frame_execute(f->selected, ev->xkey.state,
1255                                          ev->xkey.time);
1256         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1257             ObMenuFrame *f;
1258             if ((f = find_active_or_last_menu()) && f->parent)
1259                 menu_frame_select(f, NULL);
1260         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1261             ObMenuFrame *f;
1262             if ((f = find_active_or_last_menu()) && f->child)
1263                 menu_frame_select_next(f->child);
1264         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1265             ObMenuFrame *f;
1266             if ((f = find_active_or_last_menu()))
1267                 menu_frame_select_previous(f);
1268         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1269             ObMenuFrame *f;
1270             if ((f = find_active_or_last_menu()))
1271                 menu_frame_select_next(f);
1272         }
1273         break;
1274     }
1275 }
1276
1277 static gboolean menu_hide_delay_func(gpointer data)
1278 {
1279     menu_can_hide = TRUE;
1280     return FALSE; /* no repeat */
1281 }
1282
1283 static gboolean focus_delay_func(gpointer data)
1284 {
1285     ObClient *c = data;
1286
1287     if (focus_client != c) {
1288         if (client_validate(c)) {
1289             client_focus(c);
1290             if (config_focus_raise)
1291                 client_raise(c);
1292         }
1293     }
1294     return FALSE; /* no repeat */
1295 }
1296
1297 static void focus_delay_client_dest(ObClient *client, gpointer data)
1298 {
1299     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1300                                      client, TRUE);
1301 }
1302
1303 static void event_client_dest(ObClient *client, gpointer data)
1304 {
1305     if (client == focus_hilite)
1306         focus_hilite = NULL;
1307 }
1308
1309 void event_halt_focus_delay()
1310 {
1311     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1312 }
1313
1314 void event_ignore_queued_enters()
1315 {
1316     GSList *saved = NULL, *it;
1317     XEvent *e;
1318                 
1319     XSync(ob_display, FALSE);
1320
1321     /* count the events */
1322     while (TRUE) {
1323         e = g_new(XEvent, 1);
1324         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1325             ObWindow *win;
1326             
1327             win = g_hash_table_lookup(window_map, &e->xany.window);
1328             if (win && WINDOW_IS_CLIENT(win))
1329                 ++ignore_enter_focus;
1330             
1331             saved = g_slist_append(saved, e);
1332         } else {
1333             g_free(e);
1334             break;
1335         }
1336     }
1337     /* put the events back */
1338     for (it = saved; it; it = g_slist_next(it)) {
1339         XPutBackEvent(ob_display, it->data);
1340         g_free(it->data);
1341     }
1342     g_slist_free(saved);
1343 }