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