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