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