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