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