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