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