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