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