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