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