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