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