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