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