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