]> icculus.org git repositories - dana/openbox.git/blob - openbox/event.c
no more interactive mouse actions, they are evil etc. actions now "feel" much more...
[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\n");
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\n");
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\n");
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\n");
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\n");
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\n",
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\n",
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 {
491             if (!keyboard_process_interactive_grab(e)) {
492                 if (moveresize_in_progress)
493                     moveresize_event(e);
494
495                 if (e->type == ButtonPress || e->type == ButtonRelease ||
496                     e->type == MotionNotify)
497                     mouse_event(client, e);
498                 else if (e->type == KeyPress)
499                     /* when in the middle of a focus cycling action, this
500                        causes the window which appears to be focused to be
501                        the one on which the actions will be executed */
502                     keyboard_event((focus_cycle_target ?
503                                     focus_cycle_target : client), e);
504             }
505         }
506     }
507 }
508
509 static void event_handle_root(XEvent *e)
510 {
511     Atom msgtype;
512      
513     switch(e->type) {
514     case SelectionClear:
515         ob_debug("Another WM has requested to replace us. Exiting.\n");
516         ob_exit();
517         break;
518
519     case ClientMessage:
520         if (e->xclient.format != 32) break;
521
522         msgtype = e->xclient.message_type;
523         if (msgtype == prop_atoms.net_current_desktop) {
524             unsigned int d = e->xclient.data.l[0];
525             if (d < screen_num_desktops)
526                 screen_set_desktop(d);
527         } else if (msgtype == prop_atoms.net_number_of_desktops) {
528             unsigned int d = e->xclient.data.l[0];
529             if (d > 0)
530                 screen_set_num_desktops(d);
531         } else if (msgtype == prop_atoms.net_showing_desktop) {
532             screen_show_desktop(e->xclient.data.l[0] != 0);
533         }
534         break;
535     case PropertyNotify:
536         if (e->xproperty.atom == prop_atoms.net_desktop_names)
537             screen_update_desktop_names();
538         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
539             screen_update_layout();
540         break;
541     case ConfigureNotify:
542 #ifdef XRANDR
543         XRRUpdateConfiguration(e);
544 #endif
545         screen_resize();
546         break;
547     default:
548         ;
549 #ifdef VIDMODE
550         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
551             ob_debug("VIDMODE EVENT\n");
552         }
553 #endif
554     }
555 }
556
557 static void event_handle_client(ObClient *client, XEvent *e)
558 {
559     XEvent ce;
560     Atom msgtype;
561     int i=0;
562     ObFrameContext con;
563      
564     switch (e->type) {
565     case VisibilityNotify:
566         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
567         break;
568     case ButtonPress:
569     case ButtonRelease:
570         /* Wheel buttons don't draw because they are an instant click, so it
571            is a waste of resources to go drawing it. */
572         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
573             switch (frame_context(client, e->xbutton.window)) {
574             case OB_FRAME_CONTEXT_MAXIMIZE:
575                 client->frame->max_press = (e->type == ButtonPress);
576                 framerender_frame(client->frame);
577                 break;
578             case OB_FRAME_CONTEXT_CLOSE:
579                 client->frame->close_press = (e->type == ButtonPress);
580                 framerender_frame(client->frame);
581                 break;
582             case OB_FRAME_CONTEXT_ICONIFY:
583                 client->frame->iconify_press = (e->type == ButtonPress);
584                 framerender_frame(client->frame);
585                 break;
586             case OB_FRAME_CONTEXT_ALLDESKTOPS:
587                 client->frame->desk_press = (e->type == ButtonPress);
588                 framerender_frame(client->frame);
589                 break; 
590             case OB_FRAME_CONTEXT_SHADE:
591                 client->frame->shade_press = (e->type == ButtonPress);
592                 framerender_frame(client->frame);
593                 break;
594             default:
595                 /* nothing changes with clicks for any other contexts */
596                 break;
597             }
598         }
599         break;
600     case FocusIn:
601 #ifdef DEBUG_FOCUS
602         ob_debug("FocusIn on client for %lx\n", client->window);
603 #endif
604         if (client != focus_client) {
605             focus_set_client(client);
606             frame_adjust_focus(client->frame, TRUE);
607         }
608         break;
609     case FocusOut:
610 #ifdef DEBUG_FOCUS
611         ob_debug("FocusOut on client for %lx\n", client->window);
612 #endif
613         /* are we a fullscreen window or a transient of one? (checks layer)
614            if we are then we need to be iconified since we are losing focus
615          */
616         if (client->layer == OB_STACKING_LAYER_FULLSCREEN && !client->iconic &&
617             !client_search_focus_tree_full(client))
618             /* iconify fullscreen windows when they and their transients
619                aren't focused */
620             client_iconify(client, TRUE, TRUE);
621         frame_adjust_focus(client->frame, FALSE);
622         break;
623     case LeaveNotify:
624         con = frame_context(client, e->xcrossing.window);
625         switch (con) {
626         case OB_FRAME_CONTEXT_MAXIMIZE:
627             client->frame->max_hover = FALSE;
628             frame_adjust_state(client->frame);
629             break;
630         case OB_FRAME_CONTEXT_ALLDESKTOPS:
631             client->frame->desk_hover = FALSE;
632             frame_adjust_state(client->frame);
633             break;
634         case OB_FRAME_CONTEXT_SHADE:
635             client->frame->shade_hover = FALSE;
636             frame_adjust_state(client->frame);
637             break;
638         case OB_FRAME_CONTEXT_ICONIFY:
639             client->frame->iconify_hover = FALSE;
640             frame_adjust_state(client->frame);
641             break;
642         case OB_FRAME_CONTEXT_CLOSE:
643             client->frame->close_hover = FALSE;
644             frame_adjust_state(client->frame);
645             break;
646         case OB_FRAME_CONTEXT_FRAME:
647             /* XXX if doing a 'reconfigure' make sure you kill this timer,
648                maybe all timers.. */
649             if (config_focus_delay && client == focus_delay_client) {
650                 ob_main_loop_timeout_remove_data(ob_main_loop,
651                                                  focus_delay_func,
652                                                  focus_delay_client);
653                 focus_delay_client = NULL;
654             }
655         default:
656             break;
657         }
658         break;
659     case EnterNotify:
660         con = frame_context(client, e->xcrossing.window);
661         switch (con) {
662         case OB_FRAME_CONTEXT_MAXIMIZE:
663             client->frame->max_hover = TRUE;
664             frame_adjust_state(client->frame);
665             break;
666         case OB_FRAME_CONTEXT_ALLDESKTOPS:
667             client->frame->desk_hover = TRUE;
668             frame_adjust_state(client->frame);
669             break;
670         case OB_FRAME_CONTEXT_SHADE:
671             client->frame->shade_hover = TRUE;
672             frame_adjust_state(client->frame);
673             break;
674         case OB_FRAME_CONTEXT_ICONIFY:
675             client->frame->iconify_hover = TRUE;
676             frame_adjust_state(client->frame);
677             break;
678         case OB_FRAME_CONTEXT_CLOSE:
679             client->frame->close_hover = TRUE;
680             frame_adjust_state(client->frame);
681             break;
682         case OB_FRAME_CONTEXT_FRAME:
683             if (client_normal(client)) {
684                 if (ob_state() == OB_STATE_STARTING) {
685                     /* move it to the top of the focus order */
686                     guint desktop = client->desktop;
687                     if (desktop == DESKTOP_ALL) desktop = screen_desktop;
688                     focus_order[desktop] = g_list_remove(focus_order[desktop],
689                                                          client);
690                     focus_order[desktop] = g_list_prepend(focus_order[desktop],
691                                                           client);
692                 } else if (config_focus_follow) {
693 #ifdef DEBUG_FOCUS
694                     ob_debug("EnterNotify on %lx, focusing window\n",
695                              client->window);
696 #endif
697                     if (config_focus_delay) {
698                         ob_main_loop_timeout_add(ob_main_loop,
699                                                  config_focus_delay,
700                                                  focus_delay_func,
701                                                  client, NULL);
702                         focus_delay_client = client;
703                     } else
704                         client_focus(client);
705                 }
706             }
707             break;
708         default:
709             break;
710         }
711         break;
712     case ConfigureRequest:
713         /* compress these */
714         while (XCheckTypedWindowEvent(ob_display, client->window,
715                                       ConfigureRequest, &ce)) {
716             ++i;
717             /* XXX if this causes bad things.. we can compress config req's
718                with the same mask. */
719             e->xconfigurerequest.value_mask |=
720                 ce.xconfigurerequest.value_mask;
721             if (ce.xconfigurerequest.value_mask & CWX)
722                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
723             if (ce.xconfigurerequest.value_mask & CWY)
724                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
725             if (ce.xconfigurerequest.value_mask & CWWidth)
726                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
727             if (ce.xconfigurerequest.value_mask & CWHeight)
728                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
729             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
730                 e->xconfigurerequest.border_width =
731                     ce.xconfigurerequest.border_width;
732             if (ce.xconfigurerequest.value_mask & CWStackMode)
733                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
734         }
735
736         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
737         if (client->iconic || client->shaded) return;
738
739         /* resize, then move, as specified in the EWMH section 7.7 */
740         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
741                                                CWX | CWY |
742                                                CWBorderWidth)) {
743             int x, y, w, h;
744             ObCorner corner;
745
746             if (e->xconfigurerequest.value_mask & CWBorderWidth)
747                 client->border_width = e->xconfigurerequest.border_width;
748
749             x = (e->xconfigurerequest.value_mask & CWX) ?
750                 e->xconfigurerequest.x : client->area.x;
751             y = (e->xconfigurerequest.value_mask & CWY) ?
752                 e->xconfigurerequest.y : client->area.y;
753             w = (e->xconfigurerequest.value_mask & CWWidth) ?
754                 e->xconfigurerequest.width : client->area.width;
755             h = (e->xconfigurerequest.value_mask & CWHeight) ?
756                 e->xconfigurerequest.height : client->area.height;
757
758             {
759                 int newx = x;
760                 int newy = y;
761                 int fw = w +
762                     client->frame->size.left + client->frame->size.right;
763                 int fh = h +
764                     client->frame->size.top + client->frame->size.bottom;
765                 client_find_onscreen(client, &newx, &newy, fw, fh,
766                                      client_normal(client));
767                 if (e->xconfigurerequest.value_mask & CWX)
768                     x = newx;
769                 if (e->xconfigurerequest.value_mask & CWY)
770                     y = newy;
771             }
772                
773             switch (client->gravity) {
774             case NorthEastGravity:
775             case EastGravity:
776                 corner = OB_CORNER_TOPRIGHT;
777                 break;
778             case SouthWestGravity:
779             case SouthGravity:
780                 corner = OB_CORNER_BOTTOMLEFT;
781                 break;
782             case SouthEastGravity:
783                 corner = OB_CORNER_BOTTOMRIGHT;
784                 break;
785             default:     /* NorthWest, Static, etc */
786                 corner = OB_CORNER_TOPLEFT;
787             }
788
789             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
790                                   TRUE);
791         }
792
793         if (e->xconfigurerequest.value_mask & CWStackMode) {
794             switch (e->xconfigurerequest.detail) {
795             case Below:
796             case BottomIf:
797                 stacking_lower(CLIENT_AS_WINDOW(client));
798                 break;
799
800             case Above:
801             case TopIf:
802             default:
803                 stacking_raise(CLIENT_AS_WINDOW(client));
804                 break;
805             }
806         }
807         break;
808     case UnmapNotify:
809         if (client->ignore_unmaps) {
810             client->ignore_unmaps--;
811             break;
812         }
813         client_unmanage(client);
814         break;
815     case DestroyNotify:
816         client_unmanage(client);
817         break;
818     case ReparentNotify:
819         /* this is when the client is first taken captive in the frame */
820         if (e->xreparent.parent == client->frame->plate) break;
821
822         /*
823           This event is quite rare and is usually handled in unmapHandler.
824           However, if the window is unmapped when the reparent event occurs,
825           the window manager never sees it because an unmap event is not sent
826           to an already unmapped window.
827         */
828
829         /* we don't want the reparent event, put it back on the stack for the
830            X server to deal with after we unmanage the window */
831         XPutBackEvent(ob_display, e);
832      
833         client_unmanage(client);
834         break;
835     case MapRequest:
836         ob_debug("MapRequest for 0x%lx\n", client->window);
837         if (!client->iconic) break; /* this normally doesn't happen, but if it
838                                        does, we don't want it! */
839         if (screen_showing_desktop)
840             screen_show_desktop(FALSE);
841         client_iconify(client, FALSE, TRUE);
842         if (!client->frame->visible)
843             /* if its not visible still, then don't mess with it */
844             break;
845         if (client->shaded)
846             client_shade(client, FALSE);
847         client_focus(client);
848         stacking_raise(CLIENT_AS_WINDOW(client));
849         break;
850     case ClientMessage:
851         /* validate cuz we query stuff off the client here */
852         if (!client_validate(client)) break;
853   
854         if (e->xclient.format != 32) return;
855
856         msgtype = e->xclient.message_type;
857         if (msgtype == prop_atoms.wm_change_state) {
858             /* compress changes into a single change */
859             while (XCheckTypedWindowEvent(ob_display, client->window,
860                                           e->type, &ce)) {
861                 /* XXX: it would be nice to compress ALL messages of a
862                    type, not just messages in a row without other
863                    message types between. */
864                 if (ce.xclient.message_type != msgtype) {
865                     XPutBackEvent(ob_display, &ce);
866                     break;
867                 }
868                 e->xclient = ce.xclient;
869             }
870             client_set_wm_state(client, e->xclient.data.l[0]);
871         } else if (msgtype == prop_atoms.net_wm_desktop) {
872             /* compress changes into a single change */
873             while (XCheckTypedWindowEvent(ob_display, client->window,
874                                           e->type, &ce)) {
875                 /* XXX: it would be nice to compress ALL messages of a
876                    type, not just messages in a row without other
877                    message types between. */
878                 if (ce.xclient.message_type != msgtype) {
879                     XPutBackEvent(ob_display, &ce);
880                     break;
881                 }
882                 e->xclient = ce.xclient;
883             }
884             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
885                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
886                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
887                                    FALSE);
888         } else if (msgtype == prop_atoms.net_wm_state) {
889             /* can't compress these */
890             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
891                      (e->xclient.data.l[0] == 0 ? "Remove" :
892                       e->xclient.data.l[0] == 1 ? "Add" :
893                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
894                      e->xclient.data.l[1], e->xclient.data.l[2],
895                      client->window);
896             client_set_state(client, e->xclient.data.l[0],
897                              e->xclient.data.l[1], e->xclient.data.l[2]);
898         } else if (msgtype == prop_atoms.net_close_window) {
899             ob_debug("net_close_window for 0x%lx\n", client->window);
900             client_close(client);
901         } else if (msgtype == prop_atoms.net_active_window) {
902             ob_debug("net_active_window for 0x%lx\n", client->window);
903             client_activate(client, FALSE);
904         } else if (msgtype == prop_atoms.net_wm_moveresize) {
905             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
906             if ((Atom)e->xclient.data.l[2] ==
907                 prop_atoms.net_wm_moveresize_size_topleft ||
908                 (Atom)e->xclient.data.l[2] ==
909                 prop_atoms.net_wm_moveresize_size_top ||
910                 (Atom)e->xclient.data.l[2] ==
911                 prop_atoms.net_wm_moveresize_size_topright ||
912                 (Atom)e->xclient.data.l[2] ==
913                 prop_atoms.net_wm_moveresize_size_right ||
914                 (Atom)e->xclient.data.l[2] ==
915                 prop_atoms.net_wm_moveresize_size_right ||
916                 (Atom)e->xclient.data.l[2] ==
917                 prop_atoms.net_wm_moveresize_size_bottomright ||
918                 (Atom)e->xclient.data.l[2] ==
919                 prop_atoms.net_wm_moveresize_size_bottom ||
920                 (Atom)e->xclient.data.l[2] ==
921                 prop_atoms.net_wm_moveresize_size_bottomleft ||
922                 (Atom)e->xclient.data.l[2] ==
923                 prop_atoms.net_wm_moveresize_size_left ||
924                 (Atom)e->xclient.data.l[2] ==
925                 prop_atoms.net_wm_moveresize_move ||
926                 (Atom)e->xclient.data.l[2] ==
927                 prop_atoms.net_wm_moveresize_size_keyboard ||
928                 (Atom)e->xclient.data.l[2] ==
929                 prop_atoms.net_wm_moveresize_move_keyboard) {
930
931                 moveresize_start(client, e->xclient.data.l[0],
932                                  e->xclient.data.l[1], e->xclient.data.l[3],
933                                  e->xclient.data.l[2]);
934             }
935         } else if (msgtype == prop_atoms.net_moveresize_window) {
936             int oldg = client->gravity;
937             int tmpg, x, y, w, h;
938
939             if (e->xclient.data.l[0] & 0xff)
940                 tmpg = e->xclient.data.l[0] & 0xff;
941             else
942                 tmpg = oldg;
943
944             if (e->xclient.data.l[0] & 1 << 8)
945                 x = e->xclient.data.l[1];
946             else
947                 x = client->area.x;
948             if (e->xclient.data.l[0] & 1 << 9)
949                 y = e->xclient.data.l[2];
950             else
951                 y = client->area.y;
952             if (e->xclient.data.l[0] & 1 << 10)
953                 w = e->xclient.data.l[3];
954             else
955                 w = client->area.width;
956             if (e->xclient.data.l[0] & 1 << 11)
957                 h = e->xclient.data.l[4];
958             else
959                 h = client->area.height;
960             client->gravity = tmpg;
961
962             {
963                 int newx = x;
964                 int newy = y;
965                 int fw = w +
966                     client->frame->size.left + client->frame->size.right;
967                 int fh = h +
968                     client->frame->size.top + client->frame->size.bottom;
969                 client_find_onscreen(client, &newx, &newy, fw, fh,
970                                      client_normal(client));
971                 if (e->xclient.data.l[0] & 1 << 8)
972                     x = newx;
973                 if (e->xclient.data.l[0] & 1 << 9)
974                     y = newy;
975             }
976                
977             client_configure(client, OB_CORNER_TOPLEFT,
978                              x, y, w, h, FALSE, TRUE);
979
980             client->gravity = oldg;
981         }
982         break;
983     case PropertyNotify:
984         /* validate cuz we query stuff off the client here */
985         if (!client_validate(client)) break;
986   
987         /* compress changes to a single property into a single change */
988         while (XCheckTypedWindowEvent(ob_display, client->window,
989                                       e->type, &ce)) {
990             Atom a, b;
991
992             /* XXX: it would be nice to compress ALL changes to a property,
993                not just changes in a row without other props between. */
994
995             a = ce.xproperty.atom;
996             b = e->xproperty.atom;
997
998             if (a == b)
999                 continue;
1000             if ((a == prop_atoms.net_wm_name ||
1001                  a == prop_atoms.wm_name ||
1002                  a == prop_atoms.net_wm_icon_name ||
1003                  a == prop_atoms.wm_icon_name)
1004                 &&
1005                 (b == prop_atoms.net_wm_name ||
1006                  b == prop_atoms.wm_name ||
1007                  b == prop_atoms.net_wm_icon_name ||
1008                  b == prop_atoms.wm_icon_name)) {
1009                 continue;
1010             }
1011             if ((a == prop_atoms.net_wm_icon ||
1012                  a == prop_atoms.kwm_win_icon)
1013                 &&
1014                 (b == prop_atoms.net_wm_icon ||
1015                  b == prop_atoms.kwm_win_icon))
1016                 continue;
1017
1018             XPutBackEvent(ob_display, &ce);
1019             break;
1020         }
1021
1022         msgtype = e->xproperty.atom;
1023         if (msgtype == XA_WM_NORMAL_HINTS) {
1024             client_update_normal_hints(client);
1025             /* normal hints can make a window non-resizable */
1026             client_setup_decor_and_functions(client);
1027         } else if (msgtype == XA_WM_HINTS) {
1028             client_update_wmhints(client);
1029         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1030             client_update_transient_for(client);
1031             client_get_type(client);
1032             /* type may have changed, so update the layer */
1033             client_calc_layer(client);
1034             client_setup_decor_and_functions(client);
1035         } else if (msgtype == prop_atoms.net_wm_name ||
1036                    msgtype == prop_atoms.wm_name ||
1037                    msgtype == prop_atoms.net_wm_icon_name ||
1038                    msgtype == prop_atoms.wm_icon_name) {
1039             client_update_title(client);
1040         } else if (msgtype == prop_atoms.wm_class) {
1041             client_update_class(client);
1042         } else if (msgtype == prop_atoms.wm_protocols) {
1043             client_update_protocols(client);
1044             client_setup_decor_and_functions(client);
1045         }
1046         else if (msgtype == prop_atoms.net_wm_strut) {
1047             client_update_strut(client);
1048         }
1049         else if (msgtype == prop_atoms.net_wm_icon ||
1050                  msgtype == prop_atoms.kwm_win_icon) {
1051             client_update_icons(client);
1052         }
1053     default:
1054         ;
1055 #ifdef SHAPE
1056         if (extensions_shape && e->type == extensions_shape_event_basep) {
1057             client->shaped = ((XShapeEvent*)e)->shaped;
1058             frame_adjust_shape(client->frame);
1059         }
1060 #endif
1061     }
1062 }
1063
1064 static void event_handle_dock(ObDock *s, XEvent *e)
1065 {
1066     switch (e->type) {
1067     case ButtonPress:
1068         stacking_raise(DOCK_AS_WINDOW(s));
1069         break;
1070     case EnterNotify:
1071         dock_hide(FALSE);
1072         break;
1073     case LeaveNotify:
1074         dock_hide(TRUE);
1075         break;
1076     }
1077 }
1078
1079 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1080 {
1081     switch (e->type) {
1082     case MotionNotify:
1083         dock_app_drag(app, &e->xmotion);
1084         break;
1085     case UnmapNotify:
1086         if (app->ignore_unmaps) {
1087             app->ignore_unmaps--;
1088             break;
1089         }
1090         dock_remove(app, TRUE);
1091         break;
1092     case DestroyNotify:
1093         dock_remove(app, FALSE);
1094         break;
1095     case ReparentNotify:
1096         dock_remove(app, FALSE);
1097         break;
1098     case ConfigureNotify:
1099         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1100         break;
1101     }
1102 }
1103
1104 ObMenuFrame* find_active_menu()
1105 {
1106     GList *it;
1107     ObMenuFrame *f;
1108
1109     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1110         f = it->data;
1111         if (f->selected)
1112             break;
1113     }
1114     return it ? it->data : NULL;
1115 }
1116
1117 static void event_handle_menu(XEvent *ev)
1118 {
1119     ObMenuFrame *f;
1120     ObMenuEntryFrame *e;
1121
1122     switch (ev->type) {
1123     case ButtonRelease:
1124         if (!(f = menu_frame_under(ev->xbutton.x_root,
1125                                    ev->xbutton.y_root)))
1126             menu_frame_hide_all();
1127         else {
1128             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1129                                             ev->xbutton.y_root)))
1130                 menu_entry_frame_execute(e, ev->xbutton.state);
1131         }
1132         break;
1133     case MotionNotify:
1134         if ((f = menu_frame_under(ev->xmotion.x_root,
1135                                   ev->xmotion.y_root))) {
1136             menu_frame_move_on_screen(f);
1137             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1138                                             ev->xmotion.y_root)))
1139                 menu_frame_select(f, e);
1140         }
1141         break;
1142     case KeyPress:
1143         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1144             menu_frame_hide_all();
1145         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1146             ObMenuFrame *f;
1147             if ((f = find_active_menu()))
1148                 menu_entry_frame_execute(f->selected, ev->xkey.state);
1149         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1150             ObMenuFrame *f;
1151             if ((f = find_active_menu()) && f->parent)
1152                 menu_frame_select(f, NULL);
1153         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1154             ObMenuFrame *f;
1155             if ((f = find_active_menu()) && f->child)
1156                 menu_frame_select_next(f->child);
1157         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1158             ObMenuFrame *f;
1159             if ((f = find_active_menu()))
1160                 menu_frame_select_previous(f);
1161         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1162             ObMenuFrame *f;
1163             if ((f = find_active_menu()))
1164                 menu_frame_select_next(f);
1165         }
1166         break;
1167     }
1168 }
1169
1170 static gboolean focus_delay_func(gpointer data)
1171 {
1172     client_focus(focus_delay_client);
1173     return FALSE; /* no repeat */
1174 }
1175
1176 static void focus_delay_client_dest(gpointer data)
1177 {
1178     ObClient *c = data;
1179     if (c == focus_delay_client) {
1180         ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1181                                          focus_delay_client);
1182         focus_delay_client = NULL;
1183     }
1184 }