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