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