add code for interactive actions
[dana/openbox.git] / openbox / event.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    event.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "event.h"
21 #include "debug.h"
22 #include "window.h"
23 #include "openbox.h"
24 #include "dock.h"
25 #include "client.h"
26 #include "xerror.h"
27 #include "prop.h"
28 #include "config.h"
29 #include "screen.h"
30 #include "frame.h"
31 #include "grab.h"
32 #include "menu.h"
33 #include "menuframe.h"
34 #include "keyboard.h"
35 #include "modkeys.h"
36 #include "propwin.h"
37 #include "mouse.h"
38 #include "mainloop.h"
39 #include "focus.h"
40 #include "focus_cycle.h"
41 #include "moveresize.h"
42 #include "group.h"
43 #include "stacking.h"
44 #include "extensions.h"
45 #include "translate.h"
46
47 #include <X11/Xlib.h>
48 #include <X11/Xatom.h>
49 #include <glib.h>
50
51 #ifdef HAVE_SYS_SELECT_H
52 #  include <sys/select.h>
53 #endif
54 #ifdef HAVE_SIGNAL_H
55 #  include <signal.h>
56 #endif
57 #ifdef HAVE_UNISTD_H
58 #  include <unistd.h> /* for usleep() */
59 #endif
60 #ifdef XKB
61 #  include <X11/XKBlib.h>
62 #endif
63
64 #ifdef USE_SM
65 #include <X11/ICE/ICElib.h>
66 #endif
67
68 typedef struct
69 {
70     gboolean ignored;
71 } ObEventData;
72
73 typedef struct
74 {
75     ObClient *client;
76     Time time;
77 } ObFocusDelayData;
78
79 typedef struct
80 {
81     gulong start; /* inclusive */
82     gulong end;   /* inclusive */
83 } ObSerialRange;
84
85 static void event_process(const XEvent *e, gpointer data);
86 static void event_handle_root(XEvent *e);
87 static gboolean event_handle_menu_keyboard(XEvent *e);
88 static gboolean event_handle_menu(XEvent *e);
89 static void event_handle_dock(ObDock *s, XEvent *e);
90 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
91 static void event_handle_client(ObClient *c, XEvent *e);
92 static void event_handle_user_time_window_clients(GSList *l, XEvent *e);
93 static void event_handle_user_input(ObClient *client, XEvent *e);
94 static gboolean is_enter_focus_event_ignored(XEvent *e);
95
96 static void focus_delay_dest(gpointer data);
97 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
98 static gboolean focus_delay_func(gpointer data);
99 static void focus_delay_client_dest(ObClient *client, gpointer data);
100
101 /* The time for the current event being processed */
102 Time event_curtime = CurrentTime;
103
104 static gboolean focus_left_screen = FALSE;
105 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
106 static GSList *ignore_serials = NULL;
107
108 #ifdef USE_SM
109 static void ice_handler(gint fd, gpointer conn)
110 {
111     Bool b;
112     IceProcessMessages(conn, NULL, &b);
113 }
114
115 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
116                       IcePointer *watch_data)
117 {
118     static gint fd = -1;
119
120     if (opening) {
121         fd = IceConnectionNumber(conn);
122         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
123     } else {
124         ob_main_loop_fd_remove(ob_main_loop, fd);
125         fd = -1;
126     }
127 }
128 #endif
129
130 void event_startup(gboolean reconfig)
131 {
132     if (reconfig) return;
133
134     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
135
136 #ifdef USE_SM
137     IceAddConnectionWatch(ice_watch, NULL);
138 #endif
139
140     client_add_destroy_notify(focus_delay_client_dest, NULL);
141 }
142
143 void event_shutdown(gboolean reconfig)
144 {
145     if (reconfig) return;
146
147 #ifdef USE_SM
148     IceRemoveConnectionWatch(ice_watch, NULL);
149 #endif
150
151     client_remove_destroy_notify(focus_delay_client_dest);
152 }
153
154 static Window event_get_window(XEvent *e)
155 {
156     Window window;
157
158     /* pick a window */
159     switch (e->type) {
160     case SelectionClear:
161         window = RootWindow(ob_display, ob_screen);
162         break;
163     case MapRequest:
164         window = e->xmap.window;
165         break;
166     case UnmapNotify:
167         window = e->xunmap.window;
168         break;
169     case DestroyNotify:
170         window = e->xdestroywindow.window;
171         break;
172     case ConfigureRequest:
173         window = e->xconfigurerequest.window;
174         break;
175     case ConfigureNotify:
176         window = e->xconfigure.window;
177         break;
178     default:
179 #ifdef XKB
180         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
181             switch (((XkbAnyEvent*)e)->xkb_type) {
182             case XkbBellNotify:
183                 window = ((XkbBellNotifyEvent*)e)->window;
184             default:
185                 window = None;
186             }
187         } else
188 #endif
189 #ifdef SYNC
190         if (extensions_sync &&
191             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
192         {
193             window = None;
194         } else
195 #endif
196             window = e->xany.window;
197     }
198     return window;
199 }
200
201 static void event_set_curtime(XEvent *e)
202 {
203     Time t = CurrentTime;
204
205     /* grab the lasttime and hack up the state */
206     switch (e->type) {
207     case ButtonPress:
208     case ButtonRelease:
209         t = e->xbutton.time;
210         break;
211     case KeyPress:
212         t = e->xkey.time;
213         break;
214     case KeyRelease:
215         t = e->xkey.time;
216         break;
217     case MotionNotify:
218         t = e->xmotion.time;
219         break;
220     case PropertyNotify:
221         t = e->xproperty.time;
222         break;
223     case EnterNotify:
224     case LeaveNotify:
225         t = e->xcrossing.time;
226         break;
227     default:
228 #ifdef SYNC
229         if (extensions_sync &&
230             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
231         {
232             t = ((XSyncAlarmNotifyEvent*)e)->time;
233         }
234 #endif
235         /* if more event types are anticipated, get their timestamp
236            explicitly */
237         break;
238     }
239
240     event_curtime = t;
241 }
242
243 static void event_hack_mods(XEvent *e)
244 {
245 #ifdef XKB
246     XkbStateRec xkb_state;
247 #endif
248
249     switch (e->type) {
250     case ButtonPress:
251     case ButtonRelease:
252         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
253         break;
254     case KeyPress:
255         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
256         break;
257     case KeyRelease:
258         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
259 #ifdef XKB
260         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
261             e->xkey.state = xkb_state.compat_state;
262             break;
263         }
264 #endif
265         /* remove from the state the mask of the modifier key being released,
266            if it is a modifier key being released that is */
267         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
268         break;
269     case MotionNotify:
270         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
271         /* compress events */
272         {
273             XEvent ce;
274             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
275                                           e->type, &ce)) {
276                 e->xmotion.x = ce.xmotion.x;
277                 e->xmotion.y = ce.xmotion.y;
278                 e->xmotion.x_root = ce.xmotion.x_root;
279                 e->xmotion.y_root = ce.xmotion.y_root;
280             }
281         }
282         break;
283     }
284 }
285
286 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
287 {
288     gint mode = e->xfocus.mode;
289     gint detail = e->xfocus.detail;
290     Window win = e->xany.window;
291
292     if (e->type == FocusIn) {
293         /* These are ones we never want.. */
294
295         /* This means focus was given by a keyboard/mouse grab. */
296         if (mode == NotifyGrab)
297             return FALSE;
298         /* This means focus was given back from a keyboard/mouse grab. */
299         if (mode == NotifyUngrab)
300             return FALSE;
301
302         /* These are the ones we want.. */
303
304         if (win == RootWindow(ob_display, ob_screen)) {
305             /* If looking for a focus in on a client, then always return
306                FALSE for focus in's to the root window */
307             if (in_client_only)
308                 return FALSE;
309             /* This means focus reverted off of a client */
310             else if (detail == NotifyPointerRoot ||
311                      detail == NotifyDetailNone ||
312                      detail == NotifyInferior ||
313                      /* This means focus got here from another screen */
314                      detail == NotifyNonlinear)
315                 return TRUE;
316             else
317                 return FALSE;
318         }
319
320         /* It was on a client, was it a valid one?
321            It's possible to get a FocusIn event for a client that was managed
322            but has disappeared.
323         */
324         if (in_client_only) {
325             ObWindow *w = g_hash_table_lookup(window_map, &e->xfocus.window);
326             if (!w || !WINDOW_IS_CLIENT(w))
327                 return FALSE;
328         }
329         else {
330             /* This means focus reverted to parent from the client (this
331                happens often during iconify animation) */
332             if (detail == NotifyInferior)
333                 return TRUE;
334         }
335
336         /* This means focus moved from the root window to a client */
337         if (detail == NotifyVirtual)
338             return TRUE;
339         /* This means focus moved from one client to another */
340         if (detail == NotifyNonlinearVirtual)
341             return TRUE;
342
343         /* Otherwise.. */
344         return FALSE;
345     } else {
346         g_assert(e->type == FocusOut);
347
348         /* These are ones we never want.. */
349
350         /* This means focus was taken by a keyboard/mouse grab. */
351         if (mode == NotifyGrab)
352             return FALSE;
353         /* This means focus was grabbed on a window and it was released. */
354         if (mode == NotifyUngrab)
355             return FALSE;
356
357         /* Focus left the root window revertedto state */
358         if (win == RootWindow(ob_display, ob_screen))
359             return FALSE;
360
361         /* These are the ones we want.. */
362
363         /* This means focus moved from a client to the root window */
364         if (detail == NotifyVirtual)
365             return TRUE;
366         /* This means focus moved from one client to another */
367         if (detail == NotifyNonlinearVirtual)
368             return TRUE;
369
370         /* Otherwise.. */
371         return FALSE;
372     }
373 }
374
375 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
376 {
377     return e->type == FocusIn && wanted_focusevent(e, FALSE);
378 }
379
380 static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
381 {
382     return e->type == FocusIn && wanted_focusevent(e, TRUE);
383 }
384
385 static void print_focusevent(XEvent *e)
386 {
387     gint mode = e->xfocus.mode;
388     gint detail = e->xfocus.detail;
389     Window win = e->xany.window;
390     const gchar *modestr, *detailstr;
391
392     switch (mode) {
393     case NotifyNormal:       modestr="NotifyNormal";       break;
394     case NotifyGrab:         modestr="NotifyGrab";         break;
395     case NotifyUngrab:       modestr="NotifyUngrab";       break;
396     case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break;
397     }
398     switch (detail) {
399     case NotifyAncestor:    detailstr="NotifyAncestor";    break;
400     case NotifyVirtual:     detailstr="NotifyVirtual";     break;
401     case NotifyInferior:    detailstr="NotifyInferior";    break;
402     case NotifyNonlinear:   detailstr="NotifyNonlinear";   break;
403     case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break;
404     case NotifyPointer:     detailstr="NotifyPointer";     break;
405     case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break;
406     case NotifyDetailNone:  detailstr="NotifyDetailNone";  break;
407     }
408
409     if (mode == NotifyGrab || mode == NotifyUngrab)
410         return;
411
412     g_assert(modestr);
413     g_assert(detailstr);
414     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
415                   (e->xfocus.type == FocusIn ? "In" : "Out"),
416                   win,
417                   modestr, detailstr);
418
419 }
420
421 static gboolean event_ignore(XEvent *e, ObClient *client)
422 {
423     switch(e->type) {
424     case FocusIn:
425         print_focusevent(e);
426         if (!wanted_focusevent(e, FALSE))
427             return TRUE;
428         break;
429     case FocusOut:
430         print_focusevent(e);
431         if (!wanted_focusevent(e, FALSE))
432             return TRUE;
433         break;
434     }
435     return FALSE;
436 }
437
438 static void event_process(const XEvent *ec, gpointer data)
439 {
440     Window window;
441     ObClient *client = NULL;
442     ObDock *dock = NULL;
443     ObDockApp *dockapp = NULL;
444     ObWindow *obwin = NULL;
445     GSList *timewinclients = NULL;
446     XEvent ee, *e;
447     ObEventData *ed = data;
448
449     /* make a copy we can mangle */
450     ee = *ec;
451     e = &ee;
452
453     window = event_get_window(e);
454     if (e->type != PropertyNotify ||
455         !(timewinclients = propwin_get_clients(window,
456                                                OB_PROPWIN_USER_TIME)))
457         if ((obwin = g_hash_table_lookup(window_map, &window))) {
458             switch (obwin->type) {
459             case Window_Dock:
460                 dock = WINDOW_AS_DOCK(obwin);
461                 break;
462             case Window_DockApp:
463                 dockapp = WINDOW_AS_DOCKAPP(obwin);
464                 break;
465             case Window_Client:
466                 client = WINDOW_AS_CLIENT(obwin);
467                 break;
468             case Window_Menu:
469             case Window_Internal:
470                 /* not to be used for events */
471                 g_assert_not_reached();
472                 break;
473             }
474         }
475
476     event_set_curtime(e);
477     event_hack_mods(e);
478     if (event_ignore(e, client)) {
479         if (ed)
480             ed->ignored = TRUE;
481         return;
482     } else if (ed)
483             ed->ignored = FALSE;
484
485     /* deal with it in the kernel */
486
487     if (menu_frame_visible &&
488         (e->type == EnterNotify || e->type == LeaveNotify))
489     {
490         /* crossing events for menu */
491         event_handle_menu(e);
492     } else if (e->type == FocusIn) {
493         if (client &&
494             e->xfocus.detail == NotifyInferior)
495         {
496             ob_debug_type(OB_DEBUG_FOCUS,
497                           "Focus went to the frame window");
498
499             focus_left_screen = FALSE;
500
501             focus_fallback(FALSE, config_focus_under_mouse, TRUE);
502
503             /* We don't get a FocusOut for this case, because it's just moving
504                from our Inferior up to us. This happens when iconifying a
505                window with RevertToParent focus */
506             frame_adjust_focus(client->frame, FALSE);
507             /* focus_set_client(NULL) has already been called */
508             client_calc_layer(client);
509         }
510         if (e->xfocus.detail == NotifyPointerRoot ||
511             e->xfocus.detail == NotifyDetailNone ||
512             e->xfocus.detail == NotifyInferior ||
513             e->xfocus.detail == NotifyNonlinear)
514         {
515             XEvent ce;
516
517             ob_debug_type(OB_DEBUG_FOCUS,
518                           "Focus went to root or pointer root/none\n");
519
520             if (e->xfocus.detail == NotifyInferior ||
521                 e->xfocus.detail == NotifyNonlinear)
522             {
523                 focus_left_screen = FALSE;
524             }
525
526             /* If another FocusIn is in the queue then don't fallback yet. This
527                fixes the fun case of:
528                window map -> send focusin
529                window unmap -> get focusout
530                window map -> send focusin
531                get first focus out -> fall back to something (new window
532                  hasn't received focus yet, so something else) -> send focusin
533                which means the "something else" is the last thing to get a
534                focusin sent to it, so the new window doesn't end up with focus.
535
536                But if the other focus in is something like PointerRoot then we
537                still want to fall back.
538             */
539             if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,
540                               NULL))
541             {
542                 XPutBackEvent(ob_display, &ce);
543                 ob_debug_type(OB_DEBUG_FOCUS,
544                               "  but another FocusIn is coming\n");
545             } else {
546                 /* Focus has been reverted.
547
548                    FocusOut events come after UnmapNotify, so we don't need to
549                    worry about focusing an invalid window
550                 */
551
552                 if (!focus_left_screen)
553                     focus_fallback(FALSE, config_focus_under_mouse, TRUE);
554             }
555         }
556         else if (!client)
557         {
558             ob_debug_type(OB_DEBUG_FOCUS,
559                           "Focus went to a window that is already gone\n");
560
561             /* If you send focus to a window and then it disappears, you can
562                get the FocusIn for it, after it is unmanaged.
563                Just wait for the next FocusOut/FocusIn pair, but make note that
564                the window that was focused no longer is. */
565             focus_set_client(NULL);
566         }
567         else if (client != focus_client) {
568             focus_left_screen = FALSE;
569             frame_adjust_focus(client->frame, TRUE);
570             focus_set_client(client);
571             client_calc_layer(client);
572             client_bring_helper_windows(client);
573         }
574     } else if (e->type == FocusOut) {
575         XEvent ce;
576
577         /* Look for the followup FocusIn */
578         if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) {
579             /* There is no FocusIn, this means focus went to a window that
580                is not being managed, or a window on another screen. */
581             Window win, root;
582             gint i;
583             guint u;
584             xerror_set_ignore(TRUE);
585             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
586                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
587                 root != RootWindow(ob_display, ob_screen))
588             {
589                 ob_debug_type(OB_DEBUG_FOCUS,
590                               "Focus went to another screen !\n");
591                 focus_left_screen = TRUE;
592             }
593             else
594                 ob_debug_type(OB_DEBUG_FOCUS,
595                               "Focus went to a black hole !\n");
596             xerror_set_ignore(FALSE);
597             /* nothing is focused */
598             focus_set_client(NULL);
599         } else {
600             /* Focus moved, so process the FocusIn event */
601             ObEventData ed = { .ignored = FALSE };
602             event_process(&ce, &ed);
603             if (ed.ignored) {
604                 /* The FocusIn was ignored, this means it was on a window
605                    that isn't a client. */
606                 ob_debug_type(OB_DEBUG_FOCUS,
607                               "Focus went to an unmanaged window 0x%x !\n",
608                               ce.xfocus.window);
609                 focus_fallback(TRUE, config_focus_under_mouse, TRUE);
610             }
611         }
612
613         if (client && client != focus_client) {
614             frame_adjust_focus(client->frame, FALSE);
615             /* focus_set_client(NULL) has already been called in this
616                section or by focus_fallback */
617             client_calc_layer(client);
618         }
619     } else if (timewinclients)
620         event_handle_user_time_window_clients(timewinclients, e);
621     else if (client)
622         event_handle_client(client, e);
623     else if (dockapp)
624         event_handle_dockapp(dockapp, e);
625     else if (dock)
626         event_handle_dock(dock, e);
627     else if (window == RootWindow(ob_display, ob_screen))
628         event_handle_root(e);
629     else if (e->type == MapRequest)
630         client_manage(window);
631     else if (e->type == ClientMessage) {
632         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
633            windows that are not managed yet. */
634         if (e->xclient.message_type == prop_atoms.net_request_frame_extents) {
635             /* Pretend to manage the client, getting information used to
636                determine its decorations */
637             ObClient *c = client_fake_manage(e->xclient.window);
638             gulong vals[4];
639
640             /* set the frame extents on the window */
641             vals[0] = c->frame->size.left;
642             vals[1] = c->frame->size.right;
643             vals[2] = c->frame->size.top;
644             vals[3] = c->frame->size.bottom;
645             PROP_SETA32(e->xclient.window, net_frame_extents,
646                         cardinal, vals, 4);
647
648             /* Free the pretend client */
649             client_fake_unmanage(c);
650         }
651     }
652     else if (e->type == ConfigureRequest) {
653         /* unhandled configure requests must be used to configure the
654            window directly */
655         XWindowChanges xwc;
656
657         xwc.x = e->xconfigurerequest.x;
658         xwc.y = e->xconfigurerequest.y;
659         xwc.width = e->xconfigurerequest.width;
660         xwc.height = e->xconfigurerequest.height;
661         xwc.border_width = e->xconfigurerequest.border_width;
662         xwc.sibling = e->xconfigurerequest.above;
663         xwc.stack_mode = e->xconfigurerequest.detail;
664        
665         /* we are not to be held responsible if someone sends us an
666            invalid request! */
667         xerror_set_ignore(TRUE);
668         XConfigureWindow(ob_display, window,
669                          e->xconfigurerequest.value_mask, &xwc);
670         xerror_set_ignore(FALSE);
671     }
672 #ifdef SYNC
673     else if (extensions_sync &&
674         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
675     {
676         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
677         if (se->alarm == moveresize_alarm && moveresize_in_progress)
678             moveresize_event(e);
679     }
680 #endif
681
682     if (e->type == ButtonPress || e->type == ButtonRelease ||
683         e->type == MotionNotify || e->type == KeyPress ||
684         e->type == KeyRelease)
685     {
686         event_handle_user_input(client, e);
687     }
688
689     /* if something happens and it's not from an XEvent, then we don't know
690        the time */
691     event_curtime = CurrentTime;
692 }
693
694 static void event_handle_root(XEvent *e)
695 {
696     Atom msgtype;
697      
698     switch(e->type) {
699     case SelectionClear:
700         ob_debug("Another WM has requested to replace us. Exiting.\n");
701         ob_exit_replace();
702         break;
703
704     case ClientMessage:
705         if (e->xclient.format != 32) break;
706
707         msgtype = e->xclient.message_type;
708         if (msgtype == prop_atoms.net_current_desktop) {
709             guint d = e->xclient.data.l[0];
710             if (d < screen_num_desktops) {
711                 event_curtime = e->xclient.data.l[1];
712                 if (event_curtime == 0)
713                     ob_debug_type(OB_DEBUG_APP_BUGS,
714                                   "_NET_CURRENT_DESKTOP message is missing "
715                                   "a timestamp\n");
716                 screen_set_desktop(d, TRUE);
717             }
718         } else if (msgtype == prop_atoms.net_number_of_desktops) {
719             guint d = e->xclient.data.l[0];
720             if (d > 0 && d <= 1000)
721                 screen_set_num_desktops(d);
722         } else if (msgtype == prop_atoms.net_showing_desktop) {
723             screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
724         } else if (msgtype == prop_atoms.ob_control) {
725             ob_debug("OB_CONTROL: %d\n", e->xclient.data.l[0]);
726             if (e->xclient.data.l[0] == 1)
727                 ob_reconfigure();
728             else if (e->xclient.data.l[0] == 2)
729                 ob_restart();
730         }
731         break;
732     case PropertyNotify:
733         if (e->xproperty.atom == prop_atoms.net_desktop_names) {
734             ob_debug("UPDATE DESKTOP NAMES\n");
735             screen_update_desktop_names();
736         }
737         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
738             screen_update_layout();
739         break;
740     case ConfigureNotify:
741 #ifdef XRANDR
742         XRRUpdateConfiguration(e);
743 #endif
744         screen_resize();
745         break;
746     default:
747         ;
748     }
749 }
750
751 void event_enter_client(ObClient *client)
752 {
753     g_assert(config_focus_follow);
754
755     if (client_enter_focusable(client) && client_can_focus(client)) {
756         if (config_focus_delay) {
757             ObFocusDelayData *data;
758
759             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
760
761             data = g_new(ObFocusDelayData, 1);
762             data->client = client;
763             data->time = event_curtime;
764
765             ob_main_loop_timeout_add(ob_main_loop,
766                                      config_focus_delay,
767                                      focus_delay_func,
768                                      data, focus_delay_cmp, focus_delay_dest);
769         } else {
770             ObFocusDelayData data;
771             data.client = client;
772             data.time = event_curtime;
773             focus_delay_func(&data);
774         }
775     }
776 }
777
778 static void event_handle_user_time_window_clients(GSList *l, XEvent *e)
779 {
780     g_assert(e->type == PropertyNotify);
781     if (e->xproperty.atom == prop_atoms.net_wm_user_time) {
782         for (; l; l = g_slist_next(l))
783             client_update_user_time(l->data);
784     }
785 }
786
787 static void event_handle_client(ObClient *client, XEvent *e)
788 {
789     XEvent ce;
790     Atom msgtype;
791     ObFrameContext con;
792     static gint px = -1, py = -1;
793     static guint pb = 0;
794      
795     switch (e->type) {
796     case ButtonPress:
797         /* save where the press occured for the first button pressed */
798         if (!pb) {
799             pb = e->xbutton.button;
800             px = e->xbutton.x;
801             py = e->xbutton.y;
802         }
803     case ButtonRelease:
804         /* Wheel buttons don't draw because they are an instant click, so it
805            is a waste of resources to go drawing it.
806            if the user is doing an intereactive thing, or has a menu open then
807            the mouse is grabbed (possibly) and if we get these events we don't
808            want to deal with them
809         */
810         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
811             !grab_on_keyboard())
812         {
813             /* use where the press occured */
814             con = frame_context(client, e->xbutton.window, px, py);
815             con = mouse_button_frame_context(con, e->xbutton.button,
816                                              e->xbutton.state);
817
818             if (e->type == ButtonRelease && e->xbutton.button == pb)
819                 pb = 0, px = py = -1;
820
821             switch (con) {
822             case OB_FRAME_CONTEXT_MAXIMIZE:
823                 client->frame->max_press = (e->type == ButtonPress);
824                 frame_adjust_state(client->frame);
825                 break;
826             case OB_FRAME_CONTEXT_CLOSE:
827                 client->frame->close_press = (e->type == ButtonPress);
828                 frame_adjust_state(client->frame);
829                 break;
830             case OB_FRAME_CONTEXT_ICONIFY:
831                 client->frame->iconify_press = (e->type == ButtonPress);
832                 frame_adjust_state(client->frame);
833                 break;
834             case OB_FRAME_CONTEXT_ALLDESKTOPS:
835                 client->frame->desk_press = (e->type == ButtonPress);
836                 frame_adjust_state(client->frame);
837                 break; 
838             case OB_FRAME_CONTEXT_SHADE:
839                 client->frame->shade_press = (e->type == ButtonPress);
840                 frame_adjust_state(client->frame);
841                 break;
842             default:
843                 /* nothing changes with clicks for any other contexts */
844                 break;
845             }
846         }
847         break;
848     case MotionNotify:
849         /* when there is a grab on the pointer, we won't get enter/leave
850            notifies, but we still get motion events */
851         if (grab_on_pointer()) break;
852
853         con = frame_context(client, e->xmotion.window,
854                             e->xmotion.x, e->xmotion.y);
855         switch (con) {
856         case OB_FRAME_CONTEXT_TITLEBAR:
857         case OB_FRAME_CONTEXT_TLCORNER:
858         case OB_FRAME_CONTEXT_TRCORNER:
859             /* we've left the button area inside the titlebar */
860             if (client->frame->max_hover || client->frame->desk_hover ||
861                 client->frame->shade_hover || client->frame->iconify_hover ||
862                 client->frame->close_hover)
863             {
864                 client->frame->max_hover = FALSE;
865                 client->frame->desk_hover = FALSE;
866                 client->frame->shade_hover = FALSE;
867                 client->frame->iconify_hover = FALSE;
868                 client->frame->close_hover = FALSE;
869                 frame_adjust_state(client->frame);
870             }
871             break;
872         case OB_FRAME_CONTEXT_MAXIMIZE:
873             if (!client->frame->max_hover) {
874                 client->frame->max_hover = TRUE;
875                 frame_adjust_state(client->frame);
876             }
877             break;
878         case OB_FRAME_CONTEXT_ALLDESKTOPS:
879             if (!client->frame->desk_hover) {
880                 client->frame->desk_hover = TRUE;
881                 frame_adjust_state(client->frame);
882             }
883             break;
884         case OB_FRAME_CONTEXT_SHADE:
885             if (!client->frame->shade_hover) {
886                 client->frame->shade_hover = TRUE;
887                 frame_adjust_state(client->frame);
888             }
889             break;
890         case OB_FRAME_CONTEXT_ICONIFY:
891             if (!client->frame->iconify_hover) {
892                 client->frame->iconify_hover = TRUE;
893                 frame_adjust_state(client->frame);
894             }
895             break;
896         case OB_FRAME_CONTEXT_CLOSE:
897             if (!client->frame->close_hover) {
898                 client->frame->close_hover = TRUE;
899                 frame_adjust_state(client->frame);
900             }
901             break;
902         default:
903             break;
904         }
905         break;
906     case LeaveNotify:
907         con = frame_context(client, e->xcrossing.window,
908                             e->xcrossing.x, e->xcrossing.y);
909         switch (con) {
910         case OB_FRAME_CONTEXT_TITLEBAR:
911         case OB_FRAME_CONTEXT_TLCORNER:
912         case OB_FRAME_CONTEXT_TRCORNER:
913             /* we've left the button area inside the titlebar */
914             if (client->frame->max_hover || client->frame->desk_hover ||
915                 client->frame->shade_hover || client->frame->iconify_hover ||
916                 client->frame->close_hover)
917             {
918                 client->frame->max_hover = FALSE;
919                 client->frame->desk_hover = FALSE;
920                 client->frame->shade_hover = FALSE;
921                 client->frame->iconify_hover = FALSE;
922                 client->frame->close_hover = FALSE;
923                 frame_adjust_state(client->frame);
924             }
925             break;
926         case OB_FRAME_CONTEXT_MAXIMIZE:
927             client->frame->max_hover = FALSE;
928             frame_adjust_state(client->frame);
929             break;
930         case OB_FRAME_CONTEXT_ALLDESKTOPS:
931             client->frame->desk_hover = FALSE;
932             frame_adjust_state(client->frame);
933             break;
934         case OB_FRAME_CONTEXT_SHADE:
935             client->frame->shade_hover = FALSE;
936             frame_adjust_state(client->frame);
937             break;
938         case OB_FRAME_CONTEXT_ICONIFY:
939             client->frame->iconify_hover = FALSE;
940             frame_adjust_state(client->frame);
941             break;
942         case OB_FRAME_CONTEXT_CLOSE:
943             client->frame->close_hover = FALSE;
944             frame_adjust_state(client->frame);
945             break;
946         case OB_FRAME_CONTEXT_FRAME:
947             /* When the mouse leaves an animating window, don't use the
948                corresponding enter events. Pretend like the animating window
949                doesn't even exist..! */
950             if (frame_iconify_animating(client->frame))
951                 event_end_ignore_all_enters(event_start_ignore_all_enters());
952
953             ob_debug_type(OB_DEBUG_FOCUS,
954                           "%sNotify mode %d detail %d on %lx\n",
955                           (e->type == EnterNotify ? "Enter" : "Leave"),
956                           e->xcrossing.mode,
957                           e->xcrossing.detail, (client?client->window:0));
958             if (grab_on_keyboard())
959                 break;
960             if (config_focus_follow && config_focus_delay &&
961                 /* leave inferior events can happen when the mouse goes onto
962                    the window's border and then into the window before the
963                    delay is up */
964                 e->xcrossing.detail != NotifyInferior)
965             {
966                 ob_main_loop_timeout_remove_data(ob_main_loop,
967                                                  focus_delay_func,
968                                                  client, FALSE);
969             }
970             break;
971         default:
972             break;
973         }
974         break;
975     case EnterNotify:
976     {
977         con = frame_context(client, e->xcrossing.window,
978                             e->xcrossing.x, e->xcrossing.y);
979         switch (con) {
980         case OB_FRAME_CONTEXT_MAXIMIZE:
981             client->frame->max_hover = TRUE;
982             frame_adjust_state(client->frame);
983             break;
984         case OB_FRAME_CONTEXT_ALLDESKTOPS:
985             client->frame->desk_hover = TRUE;
986             frame_adjust_state(client->frame);
987             break;
988         case OB_FRAME_CONTEXT_SHADE:
989             client->frame->shade_hover = TRUE;
990             frame_adjust_state(client->frame);
991             break;
992         case OB_FRAME_CONTEXT_ICONIFY:
993             client->frame->iconify_hover = TRUE;
994             frame_adjust_state(client->frame);
995             break;
996         case OB_FRAME_CONTEXT_CLOSE:
997             client->frame->close_hover = TRUE;
998             frame_adjust_state(client->frame);
999             break;
1000         case OB_FRAME_CONTEXT_FRAME:
1001             if (grab_on_keyboard())
1002                 break;
1003             if (e->xcrossing.mode == NotifyGrab ||
1004                 e->xcrossing.mode == NotifyUngrab ||
1005                 /*ignore enters when we're already in the window */
1006                 e->xcrossing.detail == NotifyInferior ||
1007                 is_enter_focus_event_ignored(e))
1008             {
1009                 ob_debug_type(OB_DEBUG_FOCUS,
1010                               "%sNotify mode %d detail %d on %lx IGNORED\n",
1011                               (e->type == EnterNotify ? "Enter" : "Leave"),
1012                               e->xcrossing.mode,
1013                               e->xcrossing.detail, client?client->window:0);
1014             }
1015             else {
1016                 ob_debug_type(OB_DEBUG_FOCUS,
1017                               "%sNotify mode %d detail %d on %lx, "
1018                               "focusing window\n",
1019                               (e->type == EnterNotify ? "Enter" : "Leave"),
1020                               e->xcrossing.mode,
1021                               e->xcrossing.detail, (client?client->window:0));
1022                 if (config_focus_follow)
1023                     event_enter_client(client);
1024             }
1025             break;
1026         default:
1027             break;
1028         }
1029         break;
1030     }
1031     case ConfigureRequest:
1032     {
1033         /* dont compress these unless you're going to watch for property
1034            notifies in between (these can change what the configure would
1035            do to the window).
1036            also you can't compress stacking events
1037         */
1038
1039         gint x, y, w, h;
1040         gboolean move = FALSE;
1041         gboolean resize = FALSE;
1042
1043         /* get the current area */
1044         RECT_TO_DIMS(client->area, x, y, w, h);
1045
1046         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1047                  "visibile %d\n"
1048                  "                     x %d y %d w %d h %d b %d\n",
1049                  client->title,
1050                  screen_desktop, client->wmstate, client->frame->visible,
1051                  x, y, w, h, client->border_width);
1052
1053         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1054             if (client->border_width != e->xconfigurerequest.border_width) {
1055                 client->border_width = e->xconfigurerequest.border_width;
1056
1057                 /* if the border width is changing then that is the same
1058                    as requesting a resize, but we don't actually change
1059                    the client's border, so it will change their root
1060                    coordiantes (since they include the border width) and
1061                    we need to a notify then */
1062                 move = TRUE;
1063             }
1064
1065
1066         if (e->xconfigurerequest.value_mask & CWStackMode) {
1067             ObClient *sibling = NULL;
1068             gulong ignore_start;
1069
1070             /* get the sibling */
1071             if (e->xconfigurerequest.value_mask & CWSibling) {
1072                 ObWindow *win;
1073                 win = g_hash_table_lookup(window_map,
1074                                           &e->xconfigurerequest.above);
1075                 if (WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client)
1076                     sibling = WINDOW_AS_CLIENT(win);
1077             }
1078
1079             if (!config_focus_under_mouse)
1080                 ignore_start = event_start_ignore_all_enters();
1081             stacking_restack_request(client, sibling,
1082                                      e->xconfigurerequest.detail);
1083             if (!config_focus_under_mouse)
1084                 event_end_ignore_all_enters(ignore_start);
1085
1086             /* if a stacking change moves the window without resizing */
1087             move = TRUE;
1088         }
1089
1090         if ((e->xconfigurerequest.value_mask & CWX) ||
1091             (e->xconfigurerequest.value_mask & CWY) ||
1092             (e->xconfigurerequest.value_mask & CWWidth) ||
1093             (e->xconfigurerequest.value_mask & CWHeight))
1094         {
1095             if (e->xconfigurerequest.value_mask & CWX) {
1096                 /* don't allow clients to move shaded windows (fvwm does this)
1097                  */
1098                 if (!client->shaded)
1099                     x = e->xconfigurerequest.x;
1100                 move = TRUE;
1101             }
1102             if (e->xconfigurerequest.value_mask & CWY) {
1103                 /* don't allow clients to move shaded windows (fvwm does this)
1104                  */
1105                 if (!client->shaded)
1106                     y = e->xconfigurerequest.y;
1107                 move = TRUE;
1108             }
1109
1110             if (e->xconfigurerequest.value_mask & CWWidth) {
1111                 w = e->xconfigurerequest.width;
1112                 resize = TRUE;
1113             }
1114             if (e->xconfigurerequest.value_mask & CWHeight) {
1115                 h = e->xconfigurerequest.height;
1116                 resize = TRUE;
1117             }
1118         }
1119
1120         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
1121                  "move %d resize %d\n",
1122                  e->xconfigurerequest.value_mask & CWX, x,
1123                  e->xconfigurerequest.value_mask & CWY, y,
1124                  e->xconfigurerequest.value_mask & CWWidth, w,
1125                  e->xconfigurerequest.value_mask & CWHeight, h,
1126                  move, resize);
1127
1128         /* check for broken apps moving to their root position
1129
1130            XXX remove this some day...that would be nice. right now all
1131            kde apps do this when they try activate themselves on another
1132            desktop. eg. open amarok window on desktop 1, switch to desktop
1133            2, click amarok tray icon. it will move by its decoration size.
1134         */
1135         if (x != client->area.x &&
1136             x == (client->frame->area.x + client->frame->size.left -
1137                   (gint)client->border_width) &&
1138             y != client->area.y &&
1139             y == (client->frame->area.y + client->frame->size.top -
1140                   (gint)client->border_width) &&
1141             w == client->area.width &&
1142             h == client->area.height)
1143         {
1144             ob_debug_type(OB_DEBUG_APP_BUGS,
1145                           "Application %s is trying to move via "
1146                           "ConfigureRequest to it's root window position "
1147                           "but it is not using StaticGravity\n",
1148                           client->title);
1149             /* don't move it */
1150             x = client->area.x;
1151             y = client->area.y;
1152
1153             /* they still requested a move, so don't change whether a
1154                notify is sent or not */
1155         }
1156
1157         {
1158             gint lw,lh;
1159
1160             client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
1161
1162             /* if x was not given, then use gravity to figure out the new
1163                x.  the reference point should not be moved */
1164             if ((e->xconfigurerequest.value_mask & CWWidth &&
1165                  !(e->xconfigurerequest.value_mask & CWX)))
1166                 client_gravity_resize_w(client, &x, client->area.width, w);
1167             /* if y was not given, then use gravity to figure out the new
1168                y.  the reference point should not be moved */
1169             if ((e->xconfigurerequest.value_mask & CWHeight &&
1170                  !(e->xconfigurerequest.value_mask & CWY)))
1171                 client_gravity_resize_h(client, &y, client->area.height,h);
1172
1173             client_find_onscreen(client, &x, &y, w, h, FALSE);
1174
1175             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d\n",
1176                      x, y, w, h);
1177             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1178         }
1179         break;
1180     }
1181     case UnmapNotify:
1182         if (client->ignore_unmaps) {
1183             client->ignore_unmaps--;
1184             break;
1185         }
1186         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1187                  "ignores left %d\n",
1188                  client->window, e->xunmap.event, e->xunmap.from_configure,
1189                  client->ignore_unmaps);
1190         client_unmanage(client);
1191         break;
1192     case DestroyNotify:
1193         ob_debug("DestroyNotify for window 0x%x\n", client->window);
1194         client_unmanage(client);
1195         break;
1196     case ReparentNotify:
1197         /* this is when the client is first taken captive in the frame */
1198         if (e->xreparent.parent == client->frame->window) break;
1199
1200         /*
1201           This event is quite rare and is usually handled in unmapHandler.
1202           However, if the window is unmapped when the reparent event occurs,
1203           the window manager never sees it because an unmap event is not sent
1204           to an already unmapped window.
1205         */
1206
1207         /* we don't want the reparent event, put it back on the stack for the
1208            X server to deal with after we unmanage the window */
1209         XPutBackEvent(ob_display, e);
1210      
1211         ob_debug("ReparentNotify for window 0x%x\n", client->window);
1212         client_unmanage(client);
1213         break;
1214     case MapRequest:
1215         ob_debug("MapRequest for 0x%lx\n", client->window);
1216         if (!client->iconic) break; /* this normally doesn't happen, but if it
1217                                        does, we don't want it!
1218                                        it can happen now when the window is on
1219                                        another desktop, but we still don't
1220                                        want it! */
1221         client_activate(client, FALSE, TRUE);
1222         break;
1223     case ClientMessage:
1224         /* validate cuz we query stuff off the client here */
1225         if (!client_validate(client)) break;
1226
1227         if (e->xclient.format != 32) return;
1228
1229         msgtype = e->xclient.message_type;
1230         if (msgtype == prop_atoms.wm_change_state) {
1231             /* compress changes into a single change */
1232             while (XCheckTypedWindowEvent(ob_display, client->window,
1233                                           e->type, &ce)) {
1234                 /* XXX: it would be nice to compress ALL messages of a
1235                    type, not just messages in a row without other
1236                    message types between. */
1237                 if (ce.xclient.message_type != msgtype) {
1238                     XPutBackEvent(ob_display, &ce);
1239                     break;
1240                 }
1241                 e->xclient = ce.xclient;
1242             }
1243             client_set_wm_state(client, e->xclient.data.l[0]);
1244         } else if (msgtype == prop_atoms.net_wm_desktop) {
1245             /* compress changes into a single change */
1246             while (XCheckTypedWindowEvent(ob_display, client->window,
1247                                           e->type, &ce)) {
1248                 /* XXX: it would be nice to compress ALL messages of a
1249                    type, not just messages in a row without other
1250                    message types between. */
1251                 if (ce.xclient.message_type != msgtype) {
1252                     XPutBackEvent(ob_display, &ce);
1253                     break;
1254                 }
1255                 e->xclient = ce.xclient;
1256             }
1257             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1258                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1259                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1260                                    FALSE, FALSE);
1261         } else if (msgtype == prop_atoms.net_wm_state) {
1262             gulong ignore_start;
1263
1264             /* can't compress these */
1265             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1266                      (e->xclient.data.l[0] == 0 ? "Remove" :
1267                       e->xclient.data.l[0] == 1 ? "Add" :
1268                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1269                      e->xclient.data.l[1], e->xclient.data.l[2],
1270                      client->window);
1271
1272             /* ignore enter events caused by these like ob actions do */
1273             if (!config_focus_under_mouse)
1274                 ignore_start = event_start_ignore_all_enters();
1275             client_set_state(client, e->xclient.data.l[0],
1276                              e->xclient.data.l[1], e->xclient.data.l[2]);
1277             if (!config_focus_under_mouse)
1278                 event_end_ignore_all_enters(ignore_start);
1279         } else if (msgtype == prop_atoms.net_close_window) {
1280             ob_debug("net_close_window for 0x%lx\n", client->window);
1281             client_close(client);
1282         } else if (msgtype == prop_atoms.net_active_window) {
1283             ob_debug("net_active_window for 0x%lx source=%s\n",
1284                      client->window,
1285                      (e->xclient.data.l[0] == 0 ? "unknown" :
1286                       (e->xclient.data.l[0] == 1 ? "application" :
1287                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1288             /* XXX make use of data.l[2] !? */
1289             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1290                 event_curtime = e->xclient.data.l[1];
1291                 if (event_curtime == 0)
1292                     ob_debug_type(OB_DEBUG_APP_BUGS,
1293                                   "_NET_ACTIVE_WINDOW message for window %s is"
1294                                   " missing a timestamp\n", client->title);
1295             } else
1296                 ob_debug_type(OB_DEBUG_APP_BUGS,
1297                               "_NET_ACTIVE_WINDOW message for window %s is "
1298                               "missing source indication\n");
1299             client_activate(client, FALSE,
1300                             (e->xclient.data.l[0] == 0 ||
1301                              e->xclient.data.l[0] == 2));
1302         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1303             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1304                      client->window, e->xclient.data.l[2]);
1305             if ((Atom)e->xclient.data.l[2] ==
1306                 prop_atoms.net_wm_moveresize_size_topleft ||
1307                 (Atom)e->xclient.data.l[2] ==
1308                 prop_atoms.net_wm_moveresize_size_top ||
1309                 (Atom)e->xclient.data.l[2] ==
1310                 prop_atoms.net_wm_moveresize_size_topright ||
1311                 (Atom)e->xclient.data.l[2] ==
1312                 prop_atoms.net_wm_moveresize_size_right ||
1313                 (Atom)e->xclient.data.l[2] ==
1314                 prop_atoms.net_wm_moveresize_size_right ||
1315                 (Atom)e->xclient.data.l[2] ==
1316                 prop_atoms.net_wm_moveresize_size_bottomright ||
1317                 (Atom)e->xclient.data.l[2] ==
1318                 prop_atoms.net_wm_moveresize_size_bottom ||
1319                 (Atom)e->xclient.data.l[2] ==
1320                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1321                 (Atom)e->xclient.data.l[2] ==
1322                 prop_atoms.net_wm_moveresize_size_left ||
1323                 (Atom)e->xclient.data.l[2] ==
1324                 prop_atoms.net_wm_moveresize_move ||
1325                 (Atom)e->xclient.data.l[2] ==
1326                 prop_atoms.net_wm_moveresize_size_keyboard ||
1327                 (Atom)e->xclient.data.l[2] ==
1328                 prop_atoms.net_wm_moveresize_move_keyboard) {
1329
1330                 moveresize_start(client, e->xclient.data.l[0],
1331                                  e->xclient.data.l[1], e->xclient.data.l[3],
1332                                  e->xclient.data.l[2]);
1333             }
1334             else if ((Atom)e->xclient.data.l[2] ==
1335                      prop_atoms.net_wm_moveresize_cancel)
1336                 moveresize_end(TRUE);
1337         } else if (msgtype == prop_atoms.net_moveresize_window) {
1338             gint ograv, x, y, w, h;
1339
1340             ograv = client->gravity;
1341
1342             if (e->xclient.data.l[0] & 0xff)
1343                 client->gravity = e->xclient.data.l[0] & 0xff;
1344
1345             if (e->xclient.data.l[0] & 1 << 8)
1346                 x = e->xclient.data.l[1];
1347             else
1348                 x = client->area.x;
1349             if (e->xclient.data.l[0] & 1 << 9)
1350                 y = e->xclient.data.l[2];
1351             else
1352                 y = client->area.y;
1353
1354             if (e->xclient.data.l[0] & 1 << 10) {
1355                 w = e->xclient.data.l[3];
1356
1357                 /* if x was not given, then use gravity to figure out the new
1358                    x.  the reference point should not be moved */
1359                 if (!(e->xclient.data.l[0] & 1 << 8))
1360                     client_gravity_resize_w(client, &x, client->area.width, w);
1361             }
1362             else
1363                 w = client->area.width;
1364
1365             if (e->xclient.data.l[0] & 1 << 11) {
1366                 h = e->xclient.data.l[4];
1367
1368                 /* if y was not given, then use gravity to figure out the new
1369                    y.  the reference point should not be moved */
1370                 if (!(e->xclient.data.l[0] & 1 << 9))
1371                     client_gravity_resize_h(client, &y, client->area.height,h);
1372             }
1373             else
1374                 h = client->area.height;
1375
1376             ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)\n",
1377                      e->xclient.data.l[0] & 1 << 8, x,
1378                      e->xclient.data.l[0] & 1 << 9, y,
1379                      client->gravity);
1380
1381             client_find_onscreen(client, &x, &y, w, h, FALSE);
1382
1383             client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
1384
1385             client->gravity = ograv;
1386         } else if (msgtype == prop_atoms.net_restack_window) {
1387             if (e->xclient.data.l[0] != 2) {
1388                 ob_debug_type(OB_DEBUG_APP_BUGS,
1389                               "_NET_RESTACK_WINDOW sent for window %s with "
1390                               "invalid source indication %ld\n",
1391                               client->title, e->xclient.data.l[0]);
1392             } else {
1393                 ObClient *sibling = NULL;
1394                 if (e->xclient.data.l[1]) {
1395                     ObWindow *win = g_hash_table_lookup
1396                         (window_map, &e->xclient.data.l[1]);
1397                     if (WINDOW_IS_CLIENT(win) &&
1398                         WINDOW_AS_CLIENT(win) != client)
1399                     {
1400                         sibling = WINDOW_AS_CLIENT(win);
1401                     }
1402                     if (sibling == NULL)
1403                         ob_debug_type(OB_DEBUG_APP_BUGS,
1404                                       "_NET_RESTACK_WINDOW sent for window %s "
1405                                       "with invalid sibling 0x%x\n",
1406                                  client->title, e->xclient.data.l[1]);
1407                 }
1408                 if (e->xclient.data.l[2] == Below ||
1409                     e->xclient.data.l[2] == BottomIf ||
1410                     e->xclient.data.l[2] == Above ||
1411                     e->xclient.data.l[2] == TopIf ||
1412                     e->xclient.data.l[2] == Opposite)
1413                 {
1414                     gulong ignore_start;
1415
1416                     if (!config_focus_under_mouse)
1417                         ignore_start = event_start_ignore_all_enters();
1418                     /* just raise, don't activate */
1419                     stacking_restack_request(client, sibling,
1420                                              e->xclient.data.l[2]);
1421                     if (!config_focus_under_mouse)
1422                         event_end_ignore_all_enters(ignore_start);
1423
1424                     /* send a synthetic ConfigureNotify, cuz this is supposed
1425                        to be like a ConfigureRequest. */
1426                     client_reconfigure(client, TRUE);
1427                 } else
1428                     ob_debug_type(OB_DEBUG_APP_BUGS,
1429                                   "_NET_RESTACK_WINDOW sent for window %s "
1430                                   "with invalid detail %d\n",
1431                                   client->title, e->xclient.data.l[2]);
1432             }
1433         }
1434         break;
1435     case PropertyNotify:
1436         /* validate cuz we query stuff off the client here */
1437         if (!client_validate(client)) break;
1438   
1439         /* compress changes to a single property into a single change */
1440         while (XCheckTypedWindowEvent(ob_display, client->window,
1441                                       e->type, &ce)) {
1442             Atom a, b;
1443
1444             /* XXX: it would be nice to compress ALL changes to a property,
1445                not just changes in a row without other props between. */
1446
1447             a = ce.xproperty.atom;
1448             b = e->xproperty.atom;
1449
1450             if (a == b)
1451                 continue;
1452             if ((a == prop_atoms.net_wm_name ||
1453                  a == prop_atoms.wm_name ||
1454                  a == prop_atoms.net_wm_icon_name ||
1455                  a == prop_atoms.wm_icon_name)
1456                 &&
1457                 (b == prop_atoms.net_wm_name ||
1458                  b == prop_atoms.wm_name ||
1459                  b == prop_atoms.net_wm_icon_name ||
1460                  b == prop_atoms.wm_icon_name)) {
1461                 continue;
1462             }
1463             if (a == prop_atoms.net_wm_icon &&
1464                 b == prop_atoms.net_wm_icon)
1465                 continue;
1466
1467             XPutBackEvent(ob_display, &ce);
1468             break;
1469         }
1470
1471         msgtype = e->xproperty.atom;
1472         if (msgtype == XA_WM_NORMAL_HINTS) {
1473             ob_debug("Update NORMAL hints\n");
1474             client_update_normal_hints(client);
1475             /* normal hints can make a window non-resizable */
1476             client_setup_decor_and_functions(client, FALSE);
1477
1478             /* make sure the client's sizes are within its bounds, but only
1479                reconfigure the window if it needs to. emacs will update its
1480                normal hints every time it receives a conigurenotify */
1481             client_reconfigure(client, FALSE);
1482         } else if (msgtype == XA_WM_HINTS) {
1483             client_update_wmhints(client);
1484         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1485             client_update_transient_for(client);
1486             client_get_type_and_transientness(client);
1487             /* type may have changed, so update the layer */
1488             client_calc_layer(client);
1489             client_setup_decor_and_functions(client, TRUE);
1490         } else if (msgtype == prop_atoms.net_wm_name ||
1491                    msgtype == prop_atoms.wm_name ||
1492                    msgtype == prop_atoms.net_wm_icon_name ||
1493                    msgtype == prop_atoms.wm_icon_name) {
1494             client_update_title(client);
1495         } else if (msgtype == prop_atoms.wm_protocols) {
1496             client_update_protocols(client);
1497             client_setup_decor_and_functions(client, TRUE);
1498         }
1499         else if (msgtype == prop_atoms.net_wm_strut) {
1500             client_update_strut(client);
1501         }
1502         else if (msgtype == prop_atoms.net_wm_strut_partial) {
1503             client_update_strut(client);
1504         }
1505         else if (msgtype == prop_atoms.net_wm_icon) {
1506             client_update_icons(client);
1507         }
1508         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1509             client_update_icon_geometry(client);
1510         }
1511         else if (msgtype == prop_atoms.net_wm_user_time) {
1512             client_update_user_time(client);
1513         }
1514         else if (msgtype == prop_atoms.net_wm_user_time_window) {
1515             client_update_user_time_window(client);
1516         }
1517 #ifdef SYNC
1518         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1519             client_update_sync_request_counter(client);
1520         }
1521 #endif
1522         break;
1523     case ColormapNotify:
1524         client_update_colormap(client, e->xcolormap.colormap);
1525         break;
1526     default:
1527         ;
1528 #ifdef SHAPE
1529         if (extensions_shape && e->type == extensions_shape_event_basep) {
1530             client->shaped = ((XShapeEvent*)e)->shaped;
1531             frame_adjust_area(client->frame, FALSE, TRUE, FALSE);
1532         }
1533 #endif
1534     }
1535 }
1536
1537 static void event_handle_dock(ObDock *s, XEvent *e)
1538 {
1539     switch (e->type) {
1540     case ButtonPress:
1541         if (e->xbutton.button == 1)
1542             stacking_raise(DOCK_AS_WINDOW(s));
1543         else if (e->xbutton.button == 2)
1544             stacking_lower(DOCK_AS_WINDOW(s));
1545         break;
1546     case EnterNotify:
1547         dock_hide(FALSE);
1548         break;
1549     case LeaveNotify:
1550         /* don't hide when moving into a dock app */
1551         if (e->xcrossing.detail != NotifyInferior)
1552             dock_hide(TRUE);
1553         break;
1554     }
1555 }
1556
1557 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1558 {
1559     switch (e->type) {
1560     case MotionNotify:
1561         dock_app_drag(app, &e->xmotion);
1562         break;
1563     case UnmapNotify:
1564         if (app->ignore_unmaps) {
1565             app->ignore_unmaps--;
1566             break;
1567         }
1568         dock_remove(app, TRUE);
1569         break;
1570     case DestroyNotify:
1571         dock_remove(app, FALSE);
1572         break;
1573     case ReparentNotify:
1574         dock_remove(app, FALSE);
1575         break;
1576     case ConfigureNotify:
1577         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1578         break;
1579     }
1580 }
1581
1582 static ObMenuFrame* find_active_menu()
1583 {
1584     GList *it;
1585     ObMenuFrame *ret = NULL;
1586
1587     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1588         ret = it->data;
1589         if (ret->selected)
1590             break;
1591         ret = NULL;
1592     }
1593     return ret;
1594 }
1595
1596 static ObMenuFrame* find_active_or_last_menu()
1597 {
1598     ObMenuFrame *ret = NULL;
1599
1600     ret = find_active_menu();
1601     if (!ret && menu_frame_visible)
1602         ret = menu_frame_visible->data;
1603     return ret;
1604 }
1605
1606 static gboolean event_handle_menu_keyboard(XEvent *ev)
1607 {
1608     guint keycode, state;
1609     gunichar unikey;
1610     ObMenuFrame *frame;
1611     gboolean ret = TRUE;
1612
1613     keycode = ev->xkey.keycode;
1614     state = ev->xkey.state;
1615     unikey = translate_unichar(keycode);
1616
1617     frame = find_active_or_last_menu();
1618     if (frame == NULL)
1619         ret = FALSE;
1620
1621     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0)
1622         menu_frame_hide_all();
1623
1624     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1625                                                       state == ControlMask))
1626     {
1627         /* Enter runs the active item or goes into the submenu.
1628            Control-Enter runs it without closing the menu. */
1629         if (frame->child)
1630             menu_frame_select_next(frame->child);
1631         else if (frame->selected)
1632             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1633     }
1634
1635     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1636         /* Left goes to the parent menu */
1637         menu_frame_select(frame, NULL, TRUE);
1638     }
1639
1640     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1641         /* Right goes to the selected submenu */
1642         if (frame->child) menu_frame_select_next(frame->child);
1643     }
1644
1645     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1646         menu_frame_select_previous(frame);
1647     }
1648
1649     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1650         menu_frame_select_next(frame);
1651     }
1652
1653     /* keyboard accelerator shortcuts. (allow controlmask) */
1654     else if ((ev->xkey.state & ~ControlMask) == 0 &&
1655              /* was it a valid key? */
1656              unikey != 0 &&
1657              /* don't bother if the menu is empty. */
1658              frame->entries)
1659     {
1660         GList *start;
1661         GList *it;
1662         ObMenuEntryFrame *found = NULL;
1663         guint num_found = 0;
1664
1665         /* start after the selected one */
1666         start = frame->entries;
1667         if (frame->selected) {
1668             for (it = start; frame->selected != it->data; it = g_list_next(it))
1669                 g_assert(it != NULL); /* nothing was selected? */
1670             /* next with wraparound */
1671             start = g_list_next(it);
1672             if (start == NULL) start = frame->entries;
1673         }
1674
1675         it = start;
1676         do {
1677             ObMenuEntryFrame *e = it->data;
1678             gunichar entrykey = 0;
1679
1680             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1681                 entrykey = e->entry->data.normal.shortcut;
1682             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1683                 entrykey = e->entry->data.submenu.submenu->shortcut;
1684
1685             if (unikey == entrykey) {
1686                 if (found == NULL) found = e;
1687                 ++num_found;
1688             }
1689
1690             /* next with wraparound */
1691             it = g_list_next(it);
1692             if (it == NULL) it = frame->entries;
1693         } while (it != start);
1694
1695         if (found) {
1696             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1697                 num_found == 1)
1698             {
1699                 menu_frame_select(frame, found, TRUE);
1700                 usleep(50000); /* highlight the item for a short bit so the
1701                                   user can see what happened */
1702                 menu_entry_frame_execute(found, state, ev->xkey.time);
1703             } else {
1704                 menu_frame_select(frame, found, TRUE);
1705                 if (num_found == 1)
1706                     menu_frame_select_next(frame->child);
1707             }
1708         } else
1709             ret = FALSE;
1710     }
1711     else
1712         ret = FALSE;
1713
1714     return ret;
1715 }
1716
1717 static gboolean event_handle_menu(XEvent *ev)
1718 {
1719     ObMenuFrame *f;
1720     ObMenuEntryFrame *e;
1721     gboolean ret = TRUE;
1722
1723     switch (ev->type) {
1724     case ButtonRelease:
1725         if (menu_hide_delay_reached() &&
1726             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1727         {
1728             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1729                                             ev->xbutton.y_root)))
1730             {
1731                 menu_frame_select(e->frame, e, TRUE);
1732                 menu_entry_frame_execute(e, ev->xbutton.state,
1733                                          ev->xbutton.time);
1734             }
1735             else
1736                 menu_frame_hide_all();
1737         }
1738         break;
1739     case EnterNotify:
1740         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1741             if (e->ignore_enters)
1742                 --e->ignore_enters;
1743             else if (!(f = find_active_menu()) ||
1744                      f == e->frame ||
1745                      f->parent == e->frame ||
1746                      f->child == e->frame)
1747                 menu_frame_select(e->frame, e, FALSE);
1748         }
1749         break;
1750     case LeaveNotify:
1751         /*ignore leaves when we're already in the window */
1752         if (ev->xcrossing.detail == NotifyInferior)
1753             break;
1754
1755         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1756             (f = find_active_menu()) && f->selected == e &&
1757             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1758         {
1759             menu_frame_select(e->frame, NULL, FALSE);
1760         }
1761         break;
1762     case MotionNotify:   
1763         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1764                                         ev->xmotion.y_root)))
1765             if (!(f = find_active_menu()) ||
1766                 f == e->frame ||
1767                 f->parent == e->frame ||
1768                 f->child == e->frame)
1769                 menu_frame_select(e->frame, e, FALSE);
1770         break;
1771     case KeyPress:
1772         ret = event_handle_menu_keyboard(ev);
1773         break;
1774     }
1775     return ret;
1776 }
1777
1778 static void event_handle_user_input(ObClient *client, XEvent *e)
1779 {
1780     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1781              e->type == MotionNotify || e->type == KeyPress ||
1782              e->type == KeyRelease);
1783
1784     if (menu_frame_visible) {
1785         if (event_handle_menu(e))
1786             /* don't use the event if the menu used it, but if the menu
1787                didn't use it and it's a keypress that is bound, it will
1788                close the menu and be used */
1789             return;
1790     }
1791
1792     /* if the keyboard interactive action uses the event then dont
1793        use it for bindings. likewise is moveresize uses the event. */
1794     if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1795         if (moveresize_in_progress)
1796             /* make further actions work on the client being
1797                moved/resized */
1798             client = moveresize_client;
1799
1800         if (e->type == ButtonPress ||
1801             e->type == ButtonRelease ||
1802             e->type == MotionNotify)
1803         {
1804             /* the frame may not be "visible" but they can still click on it
1805                in the case where it is animating before disappearing */
1806             if (!client || !frame_iconify_animating(client->frame))
1807                 mouse_event(client, e);
1808         } else
1809             keyboard_event((focus_cycle_target ? focus_cycle_target :
1810                             (client ? client : focus_client)), e);
1811     }
1812 }
1813
1814 static void focus_delay_dest(gpointer data)
1815 {
1816     g_free(data);
1817 }
1818
1819 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1820 {
1821     const ObFocusDelayData *f1 = d1;
1822     return f1->client == d2;
1823 }
1824
1825 static gboolean focus_delay_func(gpointer data)
1826 {
1827     ObFocusDelayData *d = data;
1828     Time old = event_curtime;
1829
1830     event_curtime = d->time;
1831     if (focus_client != d->client) {
1832         if (client_focus(d->client) && config_focus_raise)
1833             stacking_raise(CLIENT_AS_WINDOW(d->client));
1834     }
1835     event_curtime = old;
1836     return FALSE; /* no repeat */
1837 }
1838
1839 static void focus_delay_client_dest(ObClient *client, gpointer data)
1840 {
1841     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1842                                      client, FALSE);
1843 }
1844
1845 void event_halt_focus_delay()
1846 {
1847     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1848 }
1849
1850 gulong event_start_ignore_all_enters()
1851 {
1852     XSync(ob_display, FALSE);
1853     return LastKnownRequestProcessed(ob_display);
1854 }
1855
1856 void event_end_ignore_all_enters(gulong start)
1857 {
1858     ObSerialRange *r;
1859
1860     g_assert(start != 0);
1861     XSync(ob_display, FALSE);
1862
1863     r = g_new(ObSerialRange, 1);
1864     r->start = start;
1865     r->end = LastKnownRequestProcessed(ob_display);
1866     ignore_serials = g_slist_prepend(ignore_serials, r);
1867
1868     /* increment the serial so we don't ignore events we weren't meant to */
1869     XSync(ob_display, FALSE);
1870 }
1871
1872 static gboolean is_enter_focus_event_ignored(XEvent *e)
1873 {
1874     GSList *it, *next;
1875
1876     g_assert(e->type == EnterNotify &&
1877              !(e->xcrossing.mode == NotifyGrab ||
1878                e->xcrossing.mode == NotifyUngrab ||
1879                e->xcrossing.detail == NotifyInferior));
1880
1881     for (it = ignore_serials; it; it = next) {
1882         ObSerialRange *r = it->data;
1883
1884         next = g_slist_next(it);
1885
1886         if ((glong)(e->xany.serial - r->end) > 0) {
1887             /* past the end */
1888             ignore_serials = g_slist_delete_link(ignore_serials, it);
1889             g_free(r);
1890         }
1891         else if ((glong)(e->xany.serial - r->start) >= 0)
1892             return TRUE;
1893     }
1894     return FALSE;
1895 }
1896
1897 void event_cancel_all_key_grabs()
1898 {
1899     if (actions_interactive_act_running()) {
1900         actions_interactive_cancel_act();
1901         ob_debug("KILLED interactive action\n");
1902     }
1903     else if (menu_frame_visible) {
1904         menu_frame_hide_all();
1905         ob_debug("KILLED open menus\n");
1906     }
1907     else if (grab_on_keyboard()) {
1908         ungrab_keyboard();
1909         ob_debug("KILLED active grab on keyboard\n");
1910     }
1911     else
1912         ungrab_passive_key();
1913 }
1914
1915 gboolean event_time_after(Time t1, Time t2)
1916 {
1917     g_assert(t1 != CurrentTime);
1918     g_assert(t2 != CurrentTime);
1919
1920     /*
1921       Timestamp values wrap around (after about 49.7 days). The server, given
1922       its current time is represented by timestamp T, always interprets
1923       timestamps from clients by treating half of the timestamp space as being
1924       later in time than T.
1925       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1926     */
1927
1928     /* TIME_HALF is half of the number space of a Time type variable */
1929 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1930
1931     if (t2 >= TIME_HALF)
1932         /* t2 is in the second half so t1 might wrap around and be smaller than
1933            t2 */
1934         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1935     else
1936         /* t2 is in the first half so t1 has to come after it */
1937         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1938 }