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