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