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