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