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