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