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