1d19a284598be92ab4ccdf097a4a1cef6eebcca4
[mikachu/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     if (client == focus_hilite)
325         focus_hilite = NULL;
326 }
327
328 static void event_done(gpointer data)
329 {
330     static ObClient *last = NULL;
331
332     /* sometimes focus_hilite can be on an unfocused window, this make sure
333        it loses its focus hilite when focus moves */
334     if (focus_hilite &&
335         (focus_in && focus_hilite != focus_in) &&
336         (focus_out && focus_hilite != focus_out))
337     {
338         frame_adjust_focus(focus_hilite->frame, FALSE);
339     }
340
341     if (focus_in) {
342         if (focus_in != focus_client) {
343             focus_set_client(focus_in);
344             frame_adjust_focus(focus_in->frame, TRUE);
345             client_calc_layer(focus_in);
346         }
347
348         focus_hilite = focus_in;
349     } 
350     if (focus_out) {
351         if (focus_out == focus_client)
352             focus_set_client(NULL);
353         frame_adjust_focus(focus_out->frame, FALSE);
354         client_calc_layer(focus_out);
355
356         if (!focus_in)
357             focus_hilite = NULL;
358     }
359
360     if (focus_client != last) {
361         if (!focus_client) {
362             Window w;
363             int r;
364
365             /* is focus anywhere valid? */
366             XGetInputFocus(ob_display, &w, &r);
367             if (!w || w == RootWindow(ob_display, ob_screen))
368                 focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS);
369         }
370         last = focus_client;
371     }
372
373     focus_in = focus_out = NULL;
374 }
375
376 static void event_process(const XEvent *ec, gpointer data)
377 {
378     Window window;
379     ObGroup *group = NULL;
380     ObClient *client = NULL;
381     ObDock *dock = NULL;
382     ObDockApp *dockapp = NULL;
383     ObWindow *obwin = NULL;
384     XEvent ee, *e;
385
386     /* make a copy we can mangle */
387     ee = *ec;
388     e = &ee;
389
390     window = event_get_window(e);
391     if (!(e->type == PropertyNotify &&
392           (group = g_hash_table_lookup(group_map, &window))))
393         if ((obwin = g_hash_table_lookup(window_map, &window))) {
394             switch (obwin->type) {
395             case Window_Dock:
396                 dock = WINDOW_AS_DOCK(obwin);
397                 break;
398             case Window_DockApp:
399                 dockapp = WINDOW_AS_DOCKAPP(obwin);
400                 break;
401             case Window_Client:
402                 client = WINDOW_AS_CLIENT(obwin);
403                 break;
404             case Window_Menu:
405             case Window_Internal:
406                 /* not to be used for events */
407                 g_assert_not_reached();
408                 break;
409             }
410         }
411
412     event_set_lasttime(e);
413     event_hack_mods(e);
414     if (event_ignore(e, client))
415         return;
416
417     /* deal with it in the kernel */
418     if (group)
419         event_handle_group(group, e);
420     else if (client)
421         event_handle_client(client, e);
422     else if (dockapp)
423         event_handle_dockapp(dockapp, e);
424     else if (dock)
425         event_handle_dock(dock, e);
426     else if (window == RootWindow(ob_display, ob_screen))
427         event_handle_root(e);
428     else if (e->type == MapRequest)
429         client_manage(window);
430     else if (e->type == ConfigureRequest) {
431         /* unhandled configure requests must be used to configure the
432            window directly */
433         XWindowChanges xwc;
434                
435         xwc.x = e->xconfigurerequest.x;
436         xwc.y = e->xconfigurerequest.y;
437         xwc.width = e->xconfigurerequest.width;
438         xwc.height = e->xconfigurerequest.height;
439         xwc.border_width = e->xconfigurerequest.border_width;
440         xwc.sibling = e->xconfigurerequest.above;
441         xwc.stack_mode = e->xconfigurerequest.detail;
442        
443         /* we are not to be held responsible if someone sends us an
444            invalid request! */
445         xerror_set_ignore(TRUE);
446         XConfigureWindow(ob_display, window,
447                          e->xconfigurerequest.value_mask, &xwc);
448         xerror_set_ignore(FALSE);
449     }
450
451     /* user input (action-bound) events */
452     if (e->type == ButtonPress || e->type == ButtonRelease ||
453         e->type == MotionNotify || e->type == KeyPress ||
454         e->type == KeyRelease)
455     {
456         if (menu_frame_visible)
457             event_handle_menu(e);
458         else {
459             if (!keyboard_process_interactive_grab(e, &client)) {
460                 if (moveresize_in_progress) {
461                     moveresize_event(e);
462
463                     /* make further actions work on the client being
464                        moved/resized */
465                     client = moveresize_client;
466                 }
467
468                 menu_can_hide = FALSE;
469                 ob_main_loop_timeout_add(ob_main_loop,
470                                          G_USEC_PER_SEC / 4,
471                                          menu_hide_delay_func,
472                                          NULL, NULL);
473
474                 if (e->type == ButtonPress || e->type == ButtonRelease ||
475                     e->type == MotionNotify)
476                     mouse_event(client, e);
477                 else if (e->type == KeyPress)
478                     keyboard_event((focus_cycle_target ? focus_cycle_target :
479                                     (focus_hilite ? focus_hilite : client)),
480                                    e);
481             }
482         }
483     }
484 }
485
486 static void event_handle_root(XEvent *e)
487 {
488     Atom msgtype;
489      
490     switch(e->type) {
491     case SelectionClear:
492         ob_debug("Another WM has requested to replace us. Exiting.\n");
493         ob_exit(0);
494         break;
495
496     case ClientMessage:
497         if (e->xclient.format != 32) break;
498
499         msgtype = e->xclient.message_type;
500         if (msgtype == prop_atoms.net_current_desktop) {
501             unsigned int d = e->xclient.data.l[0];
502             if (d < screen_num_desktops)
503                 screen_set_desktop(d);
504         } else if (msgtype == prop_atoms.net_number_of_desktops) {
505             unsigned int d = e->xclient.data.l[0];
506             if (d > 0)
507                 screen_set_num_desktops(d);
508         } else if (msgtype == prop_atoms.net_showing_desktop) {
509             screen_show_desktop(e->xclient.data.l[0] != 0);
510         }
511         break;
512     case PropertyNotify:
513         if (e->xproperty.atom == prop_atoms.net_desktop_names)
514             screen_update_desktop_names();
515         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
516             screen_update_layout();
517         break;
518     case ConfigureNotify:
519 #ifdef XRANDR
520         XRRUpdateConfiguration(e);
521 #endif
522         screen_resize();
523         break;
524     default:
525         ;
526 #ifdef VIDMODE
527         if (extensions_vidmode && e->type == extensions_vidmode_event_basep) {
528             ob_debug("VIDMODE EVENT\n");
529         }
530 #endif
531     }
532 }
533
534 static void event_handle_group(ObGroup *group, XEvent *e)
535 {
536     GSList *it;
537
538     g_assert(e->type == PropertyNotify);
539
540     for (it = group->members; it; it = g_slist_next(it))
541         event_handle_client(it->data, e);
542 }
543
544 void event_enter_client(ObClient *client)
545 {
546     g_assert(config_focus_follow);
547
548     if (client_normal(client) && client_can_focus(client)) {
549         if (config_focus_delay) {
550             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
551             ob_main_loop_timeout_add(ob_main_loop,
552                                      config_focus_delay,
553                                      focus_delay_func,
554                                      client, NULL);
555         } else
556             focus_delay_func(client);
557     }
558 }
559
560 static void event_handle_client(ObClient *client, XEvent *e)
561 {
562     XEvent ce;
563     Atom msgtype;
564     int i=0;
565     ObFrameContext con;
566      
567     switch (e->type) {
568     case VisibilityNotify:
569         client->frame->obscured = e->xvisibility.state != VisibilityUnobscured;
570         break;
571     case ButtonPress:
572     case ButtonRelease:
573         /* Wheel buttons don't draw because they are an instant click, so it
574            is a waste of resources to go drawing it. */
575         if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) {
576             con = frame_context(client, e->xbutton.window);
577             con = mouse_button_frame_context(con, e->xbutton.button);
578             switch (con) {
579             case OB_FRAME_CONTEXT_MAXIMIZE:
580                 client->frame->max_press = (e->type == ButtonPress);
581                 framerender_frame(client->frame);
582                 break;
583             case OB_FRAME_CONTEXT_CLOSE:
584                 client->frame->close_press = (e->type == ButtonPress);
585                 framerender_frame(client->frame);
586                 break;
587             case OB_FRAME_CONTEXT_ICONIFY:
588                 client->frame->iconify_press = (e->type == ButtonPress);
589                 framerender_frame(client->frame);
590                 break;
591             case OB_FRAME_CONTEXT_ALLDESKTOPS:
592                 client->frame->desk_press = (e->type == ButtonPress);
593                 framerender_frame(client->frame);
594                 break; 
595             case OB_FRAME_CONTEXT_SHADE:
596                 client->frame->shade_press = (e->type == ButtonPress);
597                 framerender_frame(client->frame);
598                 break;
599             default:
600                 /* nothing changes with clicks for any other contexts */
601                 break;
602             }
603         }
604         break;
605     case FocusIn:
606 #ifdef DEBUG_FOCUS
607         ob_debug("FocusIn on client for %lx (client %lx) mode %d detail %d\n",
608                  e->xfocus.window, client->window,
609                  e->xfocus.mode, e->xfocus.detail);
610 #endif
611         focus_in = client;
612         if (focus_out == client)
613             focus_out = NULL;
614         break;
615     case FocusOut:
616 #ifdef DEBUG_FOCUS
617         ob_debug("FocusOut on client for %lx (client %lx) mode %d detail %d\n",
618                  e->xfocus.window, client->window,
619                  e->xfocus.mode, e->xfocus.detail);
620 #endif
621         if (focus_hilite == client || focus_client == client)
622             focus_out = client;
623         if (focus_in == client)
624             focus_in = NULL;
625         break;
626     case LeaveNotify:
627         con = frame_context(client, e->xcrossing.window);
628         switch (con) {
629         case OB_FRAME_CONTEXT_MAXIMIZE:
630             client->frame->max_hover = FALSE;
631             frame_adjust_state(client->frame);
632             break;
633         case OB_FRAME_CONTEXT_ALLDESKTOPS:
634             client->frame->desk_hover = FALSE;
635             frame_adjust_state(client->frame);
636             break;
637         case OB_FRAME_CONTEXT_SHADE:
638             client->frame->shade_hover = FALSE;
639             frame_adjust_state(client->frame);
640             break;
641         case OB_FRAME_CONTEXT_ICONIFY:
642             client->frame->iconify_hover = FALSE;
643             frame_adjust_state(client->frame);
644             break;
645         case OB_FRAME_CONTEXT_CLOSE:
646             client->frame->close_hover = FALSE;
647             frame_adjust_state(client->frame);
648             break;
649         case OB_FRAME_CONTEXT_FRAME:
650             if (config_focus_follow && config_focus_delay)
651                 ob_main_loop_timeout_remove_data(ob_main_loop,
652                                                  focus_delay_func,
653                                                  client);
654             break;
655         default:
656             break;
657         }
658         break;
659     case EnterNotify:
660     {
661         gboolean nofocus = FALSE;
662
663         if (ignore_enter_focus) {
664             ignore_enter_focus--;
665             nofocus = TRUE;
666         }
667
668         con = frame_context(client, e->xcrossing.window);
669         switch (con) {
670         case OB_FRAME_CONTEXT_MAXIMIZE:
671             client->frame->max_hover = TRUE;
672             frame_adjust_state(client->frame);
673             break;
674         case OB_FRAME_CONTEXT_ALLDESKTOPS:
675             client->frame->desk_hover = TRUE;
676             frame_adjust_state(client->frame);
677             break;
678         case OB_FRAME_CONTEXT_SHADE:
679             client->frame->shade_hover = TRUE;
680             frame_adjust_state(client->frame);
681             break;
682         case OB_FRAME_CONTEXT_ICONIFY:
683             client->frame->iconify_hover = TRUE;
684             frame_adjust_state(client->frame);
685             break;
686         case OB_FRAME_CONTEXT_CLOSE:
687             client->frame->close_hover = TRUE;
688             frame_adjust_state(client->frame);
689             break;
690         case OB_FRAME_CONTEXT_FRAME:
691             if (e->xcrossing.mode == NotifyGrab ||
692                 e->xcrossing.mode == NotifyUngrab)
693             {
694 #ifdef DEBUG_FOCUS
695                 ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n",
696                          (e->type == EnterNotify ? "Enter" : "Leave"),
697                          e->xcrossing.mode,
698                          e->xcrossing.detail, client?client->window:0);
699 #endif
700             } else {
701 #ifdef DEBUG_FOCUS
702                 ob_debug("%sNotify mode %d detail %d on %lx, "
703                          "focusing window: %d\n",
704                          (e->type == EnterNotify ? "Enter" : "Leave"),
705                          e->xcrossing.mode,
706                          e->xcrossing.detail, (client?client->window:0),
707                          !nofocus);
708 #endif
709                 if (!nofocus && config_focus_follow)
710                     event_enter_client(client);
711             }
712             break;
713         default:
714             break;
715         }
716         break;
717     }
718     case ConfigureRequest:
719         /* compress these */
720         while (XCheckTypedWindowEvent(ob_display, client->window,
721                                       ConfigureRequest, &ce)) {
722             ++i;
723             /* XXX if this causes bad things.. we can compress config req's
724                with the same mask. */
725             e->xconfigurerequest.value_mask |=
726                 ce.xconfigurerequest.value_mask;
727             if (ce.xconfigurerequest.value_mask & CWX)
728                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
729             if (ce.xconfigurerequest.value_mask & CWY)
730                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
731             if (ce.xconfigurerequest.value_mask & CWWidth)
732                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
733             if (ce.xconfigurerequest.value_mask & CWHeight)
734                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
735             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
736                 e->xconfigurerequest.border_width =
737                     ce.xconfigurerequest.border_width;
738             if (ce.xconfigurerequest.value_mask & CWStackMode)
739                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
740         }
741
742         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
743         if (client->iconic || client->shaded) return;
744
745         /* resize, then move, as specified in the EWMH section 7.7 */
746         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
747                                                CWX | CWY |
748                                                CWBorderWidth)) {
749             int x, y, w, h;
750             ObCorner corner;
751
752             if (e->xconfigurerequest.value_mask & CWBorderWidth)
753                 client->border_width = e->xconfigurerequest.border_width;
754
755             x = (e->xconfigurerequest.value_mask & CWX) ?
756                 e->xconfigurerequest.x : client->area.x;
757             y = (e->xconfigurerequest.value_mask & CWY) ?
758                 e->xconfigurerequest.y : client->area.y;
759             w = (e->xconfigurerequest.value_mask & CWWidth) ?
760                 e->xconfigurerequest.width : client->area.width;
761             h = (e->xconfigurerequest.value_mask & CWHeight) ?
762                 e->xconfigurerequest.height : client->area.height;
763
764             {
765                 int newx = x;
766                 int newy = y;
767                 int fw = w +
768                     client->frame->size.left + client->frame->size.right;
769                 int fh = h +
770                     client->frame->size.top + client->frame->size.bottom;
771                 client_find_onscreen(client, &newx, &newy, fw, fh,
772                                      client_normal(client));
773                 if (e->xconfigurerequest.value_mask & CWX)
774                     x = newx;
775                 if (e->xconfigurerequest.value_mask & CWY)
776                     y = newy;
777             }
778                
779             switch (client->gravity) {
780             case NorthEastGravity:
781             case EastGravity:
782                 corner = OB_CORNER_TOPRIGHT;
783                 break;
784             case SouthWestGravity:
785             case SouthGravity:
786                 corner = OB_CORNER_BOTTOMLEFT;
787                 break;
788             case SouthEastGravity:
789                 corner = OB_CORNER_BOTTOMRIGHT;
790                 break;
791             default:     /* NorthWest, Static, etc */
792                 corner = OB_CORNER_TOPLEFT;
793             }
794
795             client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
796                                   TRUE);
797         }
798
799         if (e->xconfigurerequest.value_mask & CWStackMode) {
800             switch (e->xconfigurerequest.detail) {
801             case Below:
802             case BottomIf:
803                 client_lower(client);
804                 break;
805
806             case Above:
807             case TopIf:
808             default:
809                 client_raise(client);
810                 break;
811             }
812         }
813         break;
814     case UnmapNotify:
815         if (client->ignore_unmaps) {
816             client->ignore_unmaps--;
817             break;
818         }
819         client_unmanage(client);
820         break;
821     case DestroyNotify:
822         client_unmanage(client);
823         break;
824     case ReparentNotify:
825         /* this is when the client is first taken captive in the frame */
826         if (e->xreparent.parent == client->frame->plate) break;
827
828         /*
829           This event is quite rare and is usually handled in unmapHandler.
830           However, if the window is unmapped when the reparent event occurs,
831           the window manager never sees it because an unmap event is not sent
832           to an already unmapped window.
833         */
834
835         /* we don't want the reparent event, put it back on the stack for the
836            X server to deal with after we unmanage the window */
837         XPutBackEvent(ob_display, e);
838      
839         client_unmanage(client);
840         break;
841     case MapRequest:
842         ob_debug("MapRequest for 0x%lx\n", client->window);
843         if (!client->iconic) break; /* this normally doesn't happen, but if it
844                                        does, we don't want it! */
845         client_activate(client, FALSE);
846         break;
847     case ClientMessage:
848         /* validate cuz we query stuff off the client here */
849         if (!client_validate(client)) break;
850
851         if (e->xclient.format != 32) return;
852
853         msgtype = e->xclient.message_type;
854         if (msgtype == prop_atoms.wm_change_state) {
855             /* compress changes into a single change */
856             while (XCheckTypedWindowEvent(ob_display, client->window,
857                                           e->type, &ce)) {
858                 /* XXX: it would be nice to compress ALL messages of a
859                    type, not just messages in a row without other
860                    message types between. */
861                 if (ce.xclient.message_type != msgtype) {
862                     XPutBackEvent(ob_display, &ce);
863                     break;
864                 }
865                 e->xclient = ce.xclient;
866             }
867             client_set_wm_state(client, e->xclient.data.l[0]);
868         } else if (msgtype == prop_atoms.net_wm_desktop) {
869             /* compress changes into a single change */
870             while (XCheckTypedWindowEvent(ob_display, client->window,
871                                           e->type, &ce)) {
872                 /* XXX: it would be nice to compress ALL messages of a
873                    type, not just messages in a row without other
874                    message types between. */
875                 if (ce.xclient.message_type != msgtype) {
876                     XPutBackEvent(ob_display, &ce);
877                     break;
878                 }
879                 e->xclient = ce.xclient;
880             }
881             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
882                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
883                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
884                                    FALSE);
885         } else if (msgtype == prop_atoms.net_wm_state) {
886             /* can't compress these */
887             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
888                      (e->xclient.data.l[0] == 0 ? "Remove" :
889                       e->xclient.data.l[0] == 1 ? "Add" :
890                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
891                      e->xclient.data.l[1], e->xclient.data.l[2],
892                      client->window);
893             client_set_state(client, e->xclient.data.l[0],
894                              e->xclient.data.l[1], e->xclient.data.l[2]);
895         } else if (msgtype == prop_atoms.net_close_window) {
896             ob_debug("net_close_window for 0x%lx\n", client->window);
897             client_close(client);
898         } else if (msgtype == prop_atoms.net_active_window) {
899             ob_debug("net_active_window for 0x%lx\n", client->window);
900             client_activate(client, FALSE);
901         } else if (msgtype == prop_atoms.net_wm_moveresize) {
902             ob_debug("net_wm_moveresize for 0x%lx\n", client->window);
903             if ((Atom)e->xclient.data.l[2] ==
904                 prop_atoms.net_wm_moveresize_size_topleft ||
905                 (Atom)e->xclient.data.l[2] ==
906                 prop_atoms.net_wm_moveresize_size_top ||
907                 (Atom)e->xclient.data.l[2] ==
908                 prop_atoms.net_wm_moveresize_size_topright ||
909                 (Atom)e->xclient.data.l[2] ==
910                 prop_atoms.net_wm_moveresize_size_right ||
911                 (Atom)e->xclient.data.l[2] ==
912                 prop_atoms.net_wm_moveresize_size_right ||
913                 (Atom)e->xclient.data.l[2] ==
914                 prop_atoms.net_wm_moveresize_size_bottomright ||
915                 (Atom)e->xclient.data.l[2] ==
916                 prop_atoms.net_wm_moveresize_size_bottom ||
917                 (Atom)e->xclient.data.l[2] ==
918                 prop_atoms.net_wm_moveresize_size_bottomleft ||
919                 (Atom)e->xclient.data.l[2] ==
920                 prop_atoms.net_wm_moveresize_size_left ||
921                 (Atom)e->xclient.data.l[2] ==
922                 prop_atoms.net_wm_moveresize_move ||
923                 (Atom)e->xclient.data.l[2] ==
924                 prop_atoms.net_wm_moveresize_size_keyboard ||
925                 (Atom)e->xclient.data.l[2] ==
926                 prop_atoms.net_wm_moveresize_move_keyboard) {
927
928                 moveresize_start(client, e->xclient.data.l[0],
929                                  e->xclient.data.l[1], e->xclient.data.l[3],
930                                  e->xclient.data.l[2]);
931             }
932         } else if (msgtype == prop_atoms.net_moveresize_window) {
933             int oldg = client->gravity;
934             int tmpg, x, y, w, h;
935
936             if (e->xclient.data.l[0] & 0xff)
937                 tmpg = e->xclient.data.l[0] & 0xff;
938             else
939                 tmpg = oldg;
940
941             if (e->xclient.data.l[0] & 1 << 8)
942                 x = e->xclient.data.l[1];
943             else
944                 x = client->area.x;
945             if (e->xclient.data.l[0] & 1 << 9)
946                 y = e->xclient.data.l[2];
947             else
948                 y = client->area.y;
949             if (e->xclient.data.l[0] & 1 << 10)
950                 w = e->xclient.data.l[3];
951             else
952                 w = client->area.width;
953             if (e->xclient.data.l[0] & 1 << 11)
954                 h = e->xclient.data.l[4];
955             else
956                 h = client->area.height;
957             client->gravity = tmpg;
958
959             {
960                 int newx = x;
961                 int newy = y;
962                 int fw = w +
963                     client->frame->size.left + client->frame->size.right;
964                 int fh = h +
965                     client->frame->size.top + client->frame->size.bottom;
966                 client_find_onscreen(client, &newx, &newy, fw, fh,
967                                      client_normal(client));
968                 if (e->xclient.data.l[0] & 1 << 8)
969                     x = newx;
970                 if (e->xclient.data.l[0] & 1 << 9)
971                     y = newy;
972             }
973                
974             client_configure(client, OB_CORNER_TOPLEFT,
975                              x, y, w, h, FALSE, TRUE);
976
977             client->gravity = oldg;
978         }
979         break;
980     case PropertyNotify:
981         /* validate cuz we query stuff off the client here */
982         if (!client_validate(client)) break;
983   
984         /* compress changes to a single property into a single change */
985         while (XCheckTypedWindowEvent(ob_display, client->window,
986                                       e->type, &ce)) {
987             Atom a, b;
988
989             /* XXX: it would be nice to compress ALL changes to a property,
990                not just changes in a row without other props between. */
991
992             a = ce.xproperty.atom;
993             b = e->xproperty.atom;
994
995             if (a == b)
996                 continue;
997             if ((a == prop_atoms.net_wm_name ||
998                  a == prop_atoms.wm_name ||
999                  a == prop_atoms.net_wm_icon_name ||
1000                  a == prop_atoms.wm_icon_name)
1001                 &&
1002                 (b == prop_atoms.net_wm_name ||
1003                  b == prop_atoms.wm_name ||
1004                  b == prop_atoms.net_wm_icon_name ||
1005                  b == prop_atoms.wm_icon_name)) {
1006                 continue;
1007             }
1008             if ((a == prop_atoms.net_wm_icon ||
1009                  a == prop_atoms.kwm_win_icon)
1010                 &&
1011                 (b == prop_atoms.net_wm_icon ||
1012                  b == prop_atoms.kwm_win_icon))
1013                 continue;
1014
1015             XPutBackEvent(ob_display, &ce);
1016             break;
1017         }
1018
1019         msgtype = e->xproperty.atom;
1020         if (msgtype == XA_WM_NORMAL_HINTS) {
1021             client_update_normal_hints(client);
1022             /* normal hints can make a window non-resizable */
1023             client_setup_decor_and_functions(client);
1024         } else if (msgtype == XA_WM_HINTS) {
1025             client_update_wmhints(client);
1026         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1027             client_update_transient_for(client);
1028             client_get_type(client);
1029             /* type may have changed, so update the layer */
1030             client_calc_layer(client);
1031             client_setup_decor_and_functions(client);
1032         } else if (msgtype == prop_atoms.net_wm_name ||
1033                    msgtype == prop_atoms.wm_name ||
1034                    msgtype == prop_atoms.net_wm_icon_name ||
1035                    msgtype == prop_atoms.wm_icon_name) {
1036             client_update_title(client);
1037         } else if (msgtype == prop_atoms.wm_class) {
1038             client_update_class(client);
1039         } else if (msgtype == prop_atoms.wm_protocols) {
1040             client_update_protocols(client);
1041             client_setup_decor_and_functions(client);
1042         }
1043         else if (msgtype == prop_atoms.net_wm_strut) {
1044             client_update_strut(client);
1045         }
1046         else if (msgtype == prop_atoms.net_wm_icon ||
1047                  msgtype == prop_atoms.kwm_win_icon) {
1048             client_update_icons(client);
1049         }
1050         else if (msgtype == prop_atoms.sm_client_id) {
1051             client_update_sm_client_id(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 *ret = NULL;
1108
1109     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1110         ret = it->data;
1111         if (ret->selected)
1112             break;
1113         ret = NULL;
1114     }
1115     return ret;
1116 }
1117
1118 ObMenuFrame* find_active_or_last_menu()
1119 {
1120     ObMenuFrame *ret = NULL;
1121
1122     ret = find_active_menu();
1123     if (!ret && menu_frame_visible)
1124         ret = menu_frame_visible->data;
1125     return ret;
1126 }
1127
1128 static void event_handle_menu(XEvent *ev)
1129 {
1130     ObMenuFrame *f;
1131     ObMenuEntryFrame *e;
1132
1133     switch (ev->type) {
1134     case ButtonRelease:
1135         if (menu_can_hide) {
1136             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1137                                             ev->xbutton.y_root)))
1138                 menu_entry_frame_execute(e, ev->xbutton.state);
1139             else
1140                 menu_frame_hide_all();
1141         }
1142         break;
1143     case MotionNotify:
1144         if ((f = menu_frame_under(ev->xmotion.x_root,
1145                                   ev->xmotion.y_root))) {
1146             menu_frame_move_on_screen(f);
1147             if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1148                                             ev->xmotion.y_root)))
1149                 menu_frame_select(f, e);
1150         }
1151         {
1152             ObMenuFrame *a;
1153
1154             a = find_active_menu();
1155             if (a && a != f &&
1156                 a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1157             {
1158                 menu_frame_select(a, NULL);
1159             }
1160         }
1161         break;
1162     case KeyPress:
1163         if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
1164             menu_frame_hide_all();
1165         else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) {
1166             ObMenuFrame *f;
1167             if ((f = find_active_menu()))
1168                 menu_entry_frame_execute(f->selected, ev->xkey.state);
1169         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) {
1170             ObMenuFrame *f;
1171             if ((f = find_active_or_last_menu()) && f->parent)
1172                 menu_frame_select(f, NULL);
1173         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
1174             ObMenuFrame *f;
1175             if ((f = find_active_or_last_menu()) && f->child)
1176                 menu_frame_select_next(f->child);
1177         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) {
1178             ObMenuFrame *f;
1179             if ((f = find_active_or_last_menu()))
1180                 menu_frame_select_previous(f);
1181         } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
1182             ObMenuFrame *f;
1183             if ((f = find_active_or_last_menu()))
1184                 menu_frame_select_next(f);
1185         }
1186         break;
1187     }
1188 }
1189
1190 static gboolean menu_hide_delay_func(gpointer data)
1191 {
1192     menu_can_hide = TRUE;
1193     return FALSE; /* no repeat */
1194 }
1195
1196 static gboolean focus_delay_func(gpointer data)
1197 {
1198     ObClient *c = data;
1199
1200     if (focus_client != c) {
1201         client_focus(c);
1202         if (config_focus_raise)
1203             client_raise(c);
1204     }
1205     return FALSE; /* no repeat */
1206 }
1207
1208 static void focus_delay_client_dest(ObClient *client, gpointer data)
1209 {
1210     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func, client);
1211 }
1212
1213 void event_halt_focus_delay()
1214 {
1215     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1216 }
1217
1218 void event_ignore_queued_enters()
1219 {
1220     GSList *saved = NULL, *it;
1221     XEvent *e;
1222                 
1223     XSync(ob_display, FALSE);
1224
1225     /* count the events */
1226     while (TRUE) {
1227         e = g_new(XEvent, 1);
1228         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1229             ObWindow *win;
1230             
1231             win = g_hash_table_lookup(window_map, &e->xany.window);
1232             if (win && WINDOW_IS_CLIENT(win))
1233                 ++ignore_enter_focus;
1234             
1235             saved = g_slist_append(saved, e);
1236         } else {
1237             g_free(e);
1238             break;
1239         }
1240     }
1241     /* put the events back */
1242     for (it = saved; it; it = g_slist_next(it)) {
1243         XPutBackEvent(ob_display, it->data);
1244         g_free(it->data);
1245     }
1246     g_slist_free(saved);
1247 }