no more losing focus from wm_torture -t map_response
[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 "menu.h"
32 #include "menuframe.h"
33 #include "keyboard.h"
34 #include "modkeys.h"
35 #include "propwin.h"
36 #include "mouse.h"
37 #include "mainloop.h"
38 #include "framerender.h"
39 #include "focus.h"
40 #include "moveresize.h"
41 #include "group.h"
42 #include "stacking.h"
43 #include "extensions.h"
44 #include "translate.h"
45
46 #include <X11/Xlib.h>
47 #include <X11/Xatom.h>
48 #include <glib.h>
49
50 #ifdef HAVE_SYS_SELECT_H
51 #  include <sys/select.h>
52 #endif
53 #ifdef HAVE_SIGNAL_H
54 #  include <signal.h>
55 #endif
56 #ifdef HAVE_UNISTD_H
57 #  include <unistd.h> /* for usleep() */
58 #endif
59 #ifdef XKB
60 #  include <X11/XKBlib.h>
61 #endif
62
63 #ifdef USE_SM
64 #include <X11/ICE/ICElib.h>
65 #endif
66
67 typedef struct
68 {
69     gboolean ignored;
70 } ObEventData;
71
72 typedef struct
73 {
74     ObClient *client;
75     Time time;
76 } ObFocusDelayData;
77
78 static void event_process(const XEvent *e, gpointer data);
79 static void event_handle_root(XEvent *e);
80 static gboolean event_handle_menu_keyboard(XEvent *e);
81 static gboolean event_handle_menu(XEvent *e);
82 static void event_handle_dock(ObDock *s, XEvent *e);
83 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
84 static void event_handle_client(ObClient *c, XEvent *e);
85 static void event_handle_user_time_window_clients(GSList *l, XEvent *e);
86 static void event_handle_user_input(ObClient *client, XEvent *e);
87
88 static void focus_delay_dest(gpointer data);
89 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
90 static gboolean focus_delay_func(gpointer data);
91 static void focus_delay_client_dest(ObClient *client, gpointer data);
92
93 static gboolean menu_hide_delay_func(gpointer data);
94
95 /* The time for the current event being processed */
96 Time event_curtime = CurrentTime;
97
98 static guint ignore_enter_focus = 0;
99 static gboolean menu_can_hide;
100 static gboolean focus_left_screen = FALSE;
101
102 #ifdef USE_SM
103 static void ice_handler(gint fd, gpointer conn)
104 {
105     Bool b;
106     IceProcessMessages(conn, NULL, &b);
107 }
108
109 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
110                       IcePointer *watch_data)
111 {
112     static gint fd = -1;
113
114     if (opening) {
115         fd = IceConnectionNumber(conn);
116         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
117     } else {
118         ob_main_loop_fd_remove(ob_main_loop, fd);
119         fd = -1;
120     }
121 }
122 #endif
123
124 void event_startup(gboolean reconfig)
125 {
126     if (reconfig) return;
127
128     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
129
130 #ifdef USE_SM
131     IceAddConnectionWatch(ice_watch, NULL);
132 #endif
133
134     client_add_destroy_notify(focus_delay_client_dest, NULL);
135 }
136
137 void event_shutdown(gboolean reconfig)
138 {
139     if (reconfig) return;
140
141 #ifdef USE_SM
142     IceRemoveConnectionWatch(ice_watch, NULL);
143 #endif
144
145     client_remove_destroy_notify(focus_delay_client_dest);
146 }
147
148 static Window event_get_window(XEvent *e)
149 {
150     Window window;
151
152     /* pick a window */
153     switch (e->type) {
154     case SelectionClear:
155         window = RootWindow(ob_display, ob_screen);
156         break;
157     case MapRequest:
158         window = e->xmap.window;
159         break;
160     case UnmapNotify:
161         window = e->xunmap.window;
162         break;
163     case DestroyNotify:
164         window = e->xdestroywindow.window;
165         break;
166     case ConfigureRequest:
167         window = e->xconfigurerequest.window;
168         break;
169     case ConfigureNotify:
170         window = e->xconfigure.window;
171         break;
172     default:
173 #ifdef XKB
174         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
175             switch (((XkbAnyEvent*)e)->xkb_type) {
176             case XkbBellNotify:
177                 window = ((XkbBellNotifyEvent*)e)->window;
178             default:
179                 window = None;
180             }
181         } else
182 #endif
183 #ifdef SYNC
184         if (extensions_sync &&
185             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
186         {
187             window = None;
188         } else
189 #endif
190             window = e->xany.window;
191     }
192     return window;
193 }
194
195 static void event_set_curtime(XEvent *e)
196 {
197     Time t = CurrentTime;
198
199     /* grab the lasttime and hack up the state */
200     switch (e->type) {
201     case ButtonPress:
202     case ButtonRelease:
203         t = e->xbutton.time;
204         break;
205     case KeyPress:
206         t = e->xkey.time;
207         break;
208     case KeyRelease:
209         t = e->xkey.time;
210         break;
211     case MotionNotify:
212         t = e->xmotion.time;
213         break;
214     case PropertyNotify:
215         t = e->xproperty.time;
216         break;
217     case EnterNotify:
218     case LeaveNotify:
219         t = e->xcrossing.time;
220         break;
221     default:
222 #ifdef SYNC
223         if (extensions_sync &&
224             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
225         {
226             t = ((XSyncAlarmNotifyEvent*)e)->time;
227         }
228 #endif
229         /* if more event types are anticipated, get their timestamp
230            explicitly */
231         break;
232     }
233
234     event_curtime = t;
235 }
236
237 static void event_hack_mods(XEvent *e)
238 {
239 #ifdef XKB
240     XkbStateRec xkb_state;
241 #endif
242
243     switch (e->type) {
244     case ButtonPress:
245     case ButtonRelease:
246         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
247         break;
248     case KeyPress:
249         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
250         break;
251     case KeyRelease:
252         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
253 #ifdef XKB
254         if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) {
255             e->xkey.state = xkb_state.compat_state;
256             break;
257         }
258 #endif
259         /* remove from the state the mask of the modifier key being released,
260            if it is a modifier key being released that is */
261         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
262         break;
263     case MotionNotify:
264         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
265         /* compress events */
266         {
267             XEvent ce;
268             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
269                                           e->type, &ce)) {
270                 e->xmotion.x_root = ce.xmotion.x_root;
271                 e->xmotion.y_root = ce.xmotion.y_root;
272             }
273         }
274         break;
275     }
276 }
277
278 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
279 {
280     gint mode = e->xfocus.mode;
281     gint detail = e->xfocus.detail;
282     Window win = e->xany.window;
283
284     if (e->type == FocusIn) {
285         /* These are ones we never want.. */
286
287         /* This means focus was given by a keyboard/mouse grab. */
288         if (mode == NotifyGrab)
289             return FALSE;
290         /* This means focus was given back from a keyboard/mouse grab. */
291         if (mode == NotifyUngrab)
292             return FALSE;
293
294         /* These are the ones we want.. */
295
296         if (win == RootWindow(ob_display, ob_screen)) {
297             /* If looking for a focus in on a client, then always return
298                FALSE for focus in's to the root window */
299             if (in_client_only)
300                 return FALSE;
301             /* This means focus reverted off of a client */
302             else if (detail == NotifyPointerRoot ||
303                      detail == NotifyDetailNone ||
304                      detail == NotifyInferior)
305                 return TRUE;
306             else
307                 return FALSE;
308         }
309
310         /* This means focus moved from the root window to a client */
311         if (detail == NotifyVirtual)
312             return TRUE;
313         /* This means focus moved from one client to another */
314         if (detail == NotifyNonlinearVirtual)
315             return TRUE;
316         /* This means focus moved to the frame window */
317         if (detail == NotifyInferior && !in_client_only)
318             return TRUE;
319
320         /* Otherwise.. */
321         return FALSE;
322     } else {
323         g_assert(e->type == FocusOut);
324
325         /* These are ones we never want.. */
326
327         /* This means focus was taken by a keyboard/mouse grab. */
328         if (mode == NotifyGrab)
329             return FALSE;
330
331         /* Focus left the root window revertedto state */
332         if (win == RootWindow(ob_display, ob_screen))
333             return FALSE;
334
335         /* These are the ones we want.. */
336
337         /* This means focus moved from a client to the root window */
338         if (detail == NotifyVirtual)
339             return TRUE;
340         /* This means focus moved from one client to another */
341         if (detail == NotifyNonlinearVirtual)
342             return TRUE;
343         /* This means focus had moved to our frame window and now moved off */
344         if (detail == NotifyNonlinear)
345             return TRUE;
346
347         /* Otherwise.. */
348         return FALSE;
349     }
350 }
351
352 static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg)
353 {
354     return e->type == FocusIn && wanted_focusevent(e, FALSE);
355 }
356
357 Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg)
358 {
359     ObWindow *w;
360
361     /* It is possible to get FocusIn events or unmanaged windows, meaning
362        they won't be for any known client */
363     return e->type == FocusIn && wanted_focusevent(e, TRUE) &&
364         (w = g_hash_table_lookup(window_map, &e->xfocus.window)) &&
365         WINDOW_IS_CLIENT(w);
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     g_assert(modestr);
393     g_assert(detailstr);
394     ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n",
395                   (e->xfocus.type == FocusIn ? "In" : "Out"),
396                   win,
397                   modestr, detailstr);
398
399 }
400
401 static gboolean event_ignore(XEvent *e, ObClient *client)
402 {
403     switch(e->type) {
404     case FocusIn:
405         print_focusevent(e);
406         if (!wanted_focusevent(e, FALSE))
407             return TRUE;
408         break;
409     case FocusOut:
410         print_focusevent(e);
411         if (!wanted_focusevent(e, FALSE))
412             return TRUE;
413         break;
414     }
415     return FALSE;
416 }
417
418 static void event_process(const XEvent *ec, gpointer data)
419 {
420     Window window;
421     ObClient *client = NULL;
422     ObDock *dock = NULL;
423     ObDockApp *dockapp = NULL;
424     ObWindow *obwin = NULL;
425     GSList *timewinclients = NULL;
426     XEvent ee, *e;
427     ObEventData *ed = data;
428
429     /* make a copy we can mangle */
430     ee = *ec;
431     e = &ee;
432
433     window = event_get_window(e);
434     if (e->type != PropertyNotify ||
435         !(timewinclients = propwin_get_clients(window,
436                                                OB_PROPWIN_USER_TIME)))
437         if ((obwin = g_hash_table_lookup(window_map, &window))) {
438             switch (obwin->type) {
439             case Window_Dock:
440                 dock = WINDOW_AS_DOCK(obwin);
441                 break;
442             case Window_DockApp:
443                 dockapp = WINDOW_AS_DOCKAPP(obwin);
444                 break;
445             case Window_Client:
446                 client = WINDOW_AS_CLIENT(obwin);
447                 break;
448             case Window_Menu:
449             case Window_Internal:
450                 /* not to be used for events */
451                 g_assert_not_reached();
452                 break;
453             }
454         }
455
456     event_set_curtime(e);
457     event_hack_mods(e);
458     if (event_ignore(e, client)) {
459         if (ed)
460             ed->ignored = TRUE;
461         return;
462     } else if (ed)
463             ed->ignored = FALSE;
464
465     /* deal with it in the kernel */
466
467     if (menu_frame_visible &&
468         (e->type == EnterNotify || e->type == LeaveNotify))
469     {
470         /* crossing events for menu */
471         event_handle_menu(e);
472     } else if (e->type == FocusIn) {
473         if (e->xfocus.detail == NotifyPointerRoot ||
474             e->xfocus.detail == NotifyDetailNone ||
475             e->xfocus.detail == NotifyInferior)
476         {
477             XEvent ce;
478             ob_debug_type(OB_DEBUG_FOCUS,
479                           "Focus went to pointer root/none or to our frame "
480                           "window\n");
481
482             /* If another FocusIn is in the queue then don't fallback yet. This
483                fixes the fun case of:
484                window map -> send focusin
485                window unmap -> get focusout
486                window map -> send focusin
487                get first focus out -> fall back to something (new window
488                  hasn't received focus yet, so something else) -> send focusin
489                which means the "something else" is the last thing to get a
490                focusin sent to it, so the new window doesn't end up with focus.
491
492                But if the other focus in is something like PointerRoot then we
493                still want to fall back.
494             */
495             if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,
496                               NULL))
497             {
498                 XPutBackEvent(ob_display, &ce);
499                 ob_debug_type(OB_DEBUG_FOCUS,
500                               "  but another FocusIn is coming\n");
501             } else {
502                 /* Focus has been reverted to the root window, nothing, or to
503                    our frame window.
504
505                    FocusOut events come after UnmapNotify, so we don't need to
506                    worry about focusing an invalid window
507                 */
508
509                 /* In this case we know focus is in our screen */
510                 if (e->xfocus.detail == NotifyInferior)
511                     focus_left_screen = FALSE;
512
513                 if (!focus_left_screen)
514                     focus_fallback(TRUE);
515             }
516         }
517         else if (!client) {
518             /* It is possible to get FocusIn events or unmanaged windows,
519                meaning they won't be for any known client
520
521                If this happens, set the client to NULL so we know focus
522                has wandered off, and we'll get a focus out for it
523                shortly.
524             */
525             ob_debug_type(OB_DEBUG_FOCUS, "Focus went to an invalid target\n");
526             focus_set_client(NULL);
527         }
528         else if (client != focus_client) {
529             focus_left_screen = FALSE;
530             frame_adjust_focus(client->frame, TRUE);
531             focus_set_client(client);
532             client_calc_layer(client);
533             client_bring_helper_windows(client);
534         }
535     } else if (e->type == FocusOut) {
536         gboolean nomove = FALSE;
537         XEvent ce;
538
539         /* Look for the followup FocusIn */
540         if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) {
541             /* There is no FocusIn, this means focus went to a window that
542                is not being managed, or a window on another screen. */
543             Window win, root;
544             gint i;
545             guint u;
546             xerror_set_ignore(TRUE);
547             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
548                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
549                 root != RootWindow(ob_display, ob_screen))
550             {
551                 ob_debug_type(OB_DEBUG_FOCUS,
552                               "Focus went to another screen !\n");
553                 focus_left_screen = TRUE;
554             }
555             else
556                 ob_debug_type(OB_DEBUG_FOCUS,
557                               "Focus went to a black hole !\n");
558             xerror_set_ignore(FALSE);
559             /* nothing is focused */
560             focus_set_client(NULL);
561         } else if (ce.xany.window == e->xany.window) {
562             ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n");
563             /* If focus didn't actually move anywhere, there is nothing to do*/
564             nomove = TRUE;
565         } else {
566             /* Focus did move, so process the FocusIn event */
567             ObEventData ed = { .ignored = FALSE };
568             event_process(&ce, &ed);
569             if (ed.ignored) {
570                 /* The FocusIn was ignored, this means it was on a window
571                    that isn't a client. */
572                 ob_debug_type(OB_DEBUG_FOCUS,
573                               "Focus went to an unmanaged window 0x%x !\n",
574                               ce.xfocus.window);
575                 focus_fallback(TRUE);
576             }
577         }
578
579         if (client && !nomove) {
580             frame_adjust_focus(client->frame, FALSE);
581             /* focus_set_client has already been called for sure */
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.openbox_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_queued_enters();
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 (ignore_enter_focus) {
920             ignore_enter_focus--;
921             nofocus = TRUE;
922         }
923
924         con = frame_context(client, e->xcrossing.window,
925                             e->xcrossing.x, e->xcrossing.y);
926         switch (con) {
927         case OB_FRAME_CONTEXT_MAXIMIZE:
928             client->frame->max_hover = TRUE;
929             frame_adjust_state(client->frame);
930             break;
931         case OB_FRAME_CONTEXT_ALLDESKTOPS:
932             client->frame->desk_hover = TRUE;
933             frame_adjust_state(client->frame);
934             break;
935         case OB_FRAME_CONTEXT_SHADE:
936             client->frame->shade_hover = TRUE;
937             frame_adjust_state(client->frame);
938             break;
939         case OB_FRAME_CONTEXT_ICONIFY:
940             client->frame->iconify_hover = TRUE;
941             frame_adjust_state(client->frame);
942             break;
943         case OB_FRAME_CONTEXT_CLOSE:
944             client->frame->close_hover = TRUE;
945             frame_adjust_state(client->frame);
946             break;
947         case OB_FRAME_CONTEXT_FRAME:
948             if (keyboard_interactively_grabbed())
949                 break;
950             if (e->xcrossing.mode == NotifyGrab ||
951                 e->xcrossing.mode == NotifyUngrab ||
952                 /*ignore enters when we're already in the window */
953                 e->xcrossing.detail == NotifyInferior)
954             {
955                 ob_debug_type(OB_DEBUG_FOCUS,
956                               "%sNotify mode %d detail %d on %lx IGNORED\n",
957                               (e->type == EnterNotify ? "Enter" : "Leave"),
958                               e->xcrossing.mode,
959                               e->xcrossing.detail, client?client->window:0);
960             } else {
961                 ob_debug_type(OB_DEBUG_FOCUS,
962                               "%sNotify mode %d detail %d on %lx, "
963                               "focusing window: %d\n",
964                               (e->type == EnterNotify ? "Enter" : "Leave"),
965                               e->xcrossing.mode,
966                               e->xcrossing.detail, (client?client->window:0),
967                               !nofocus);
968                 if (!nofocus && config_focus_follow)
969                     event_enter_client(client);
970             }
971             break;
972         default:
973             break;
974         }
975         break;
976     }
977     case ConfigureRequest:
978     {
979         /* dont compress these unless you're going to watch for property
980            notifies in between (these can change what the configure would
981            do to the window).
982            also you can't compress stacking events
983         */
984
985         gint x, y, w, h;
986
987         /* if nothing is changed, then a configurenotify is needed */
988         gboolean config = TRUE;
989
990         x = client->area.x;
991         y = client->area.y;
992         w = client->area.width;
993         h = client->area.height;
994
995         ob_debug("ConfigureRequest desktop %d wmstate %d visibile %d\n",
996                  screen_desktop, client->wmstate, client->frame->visible);
997
998         if (e->xconfigurerequest.value_mask & CWBorderWidth)
999             if (client->border_width != e->xconfigurerequest.border_width) {
1000                 client->border_width = e->xconfigurerequest.border_width;
1001                 /* if only the border width is changing, then it's not needed*/
1002                 config = FALSE;
1003             }
1004
1005
1006         if (e->xconfigurerequest.value_mask & CWStackMode) {
1007             ObClient *sibling = NULL;
1008
1009             /* get the sibling */
1010             if (e->xconfigurerequest.value_mask & CWSibling) {
1011                 ObWindow *win;
1012                 win = g_hash_table_lookup(window_map,
1013                                           &e->xconfigurerequest.above);
1014                 if (WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client)
1015                     sibling = WINDOW_AS_CLIENT(win);
1016             }
1017
1018             /* activate it rather than just focus it */
1019             stacking_restack_request(client, sibling,
1020                                      e->xconfigurerequest.detail, TRUE);
1021
1022             /* if a stacking change is requested then it is needed */
1023             config = TRUE;
1024         }
1025
1026         /* don't allow clients to move shaded windows (fvwm does this) */
1027         if (client->shaded && (e->xconfigurerequest.value_mask & CWX ||
1028                                e->xconfigurerequest.value_mask & CWY))
1029         {
1030             e->xconfigurerequest.value_mask &= ~CWX;
1031             e->xconfigurerequest.value_mask &= ~CWY;
1032
1033             /* if the client tried to move and we aren't letting it then a
1034                synthetic event is needed */
1035             config = TRUE;
1036         }
1037
1038         if (e->xconfigurerequest.value_mask & CWX ||
1039             e->xconfigurerequest.value_mask & CWY ||
1040             e->xconfigurerequest.value_mask & CWWidth ||
1041             e->xconfigurerequest.value_mask & CWHeight)
1042         {
1043             if (e->xconfigurerequest.value_mask & CWX)
1044                 x = e->xconfigurerequest.x;
1045             if (e->xconfigurerequest.value_mask & CWY)
1046                 y = e->xconfigurerequest.y;
1047             if (e->xconfigurerequest.value_mask & CWWidth)
1048                 w = e->xconfigurerequest.width;
1049             if (e->xconfigurerequest.value_mask & CWHeight)
1050                 h = e->xconfigurerequest.height;
1051
1052             /* if a new position or size is requested, then a configure is
1053                needed */
1054             config = TRUE;
1055         }
1056
1057         ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d\n",
1058                  e->xconfigurerequest.value_mask & CWX, x,
1059                  e->xconfigurerequest.value_mask & CWY, y,
1060                  e->xconfigurerequest.value_mask & CWWidth, w,
1061                  e->xconfigurerequest.value_mask & CWHeight, h);
1062
1063         /* check for broken apps moving to their root position
1064
1065            XXX remove this some day...that would be nice. right now all
1066            kde apps do this when they try activate themselves on another
1067            desktop. eg. open amarok window on desktop 1, switch to desktop
1068            2, click amarok tray icon. it will move by its decoration size.
1069         */
1070         if (x != client->area.x &&
1071             x == (client->frame->area.x + client->frame->size.left -
1072                   (gint)client->border_width) &&
1073             y != client->area.y &&
1074             y == (client->frame->area.y + client->frame->size.top -
1075                   (gint)client->border_width))
1076         {
1077             ob_debug_type(OB_DEBUG_APP_BUGS,
1078                           "Application %s is trying to move via "
1079                           "ConfigureRequest to it's root window position "
1080                           "but it is not using StaticGravity\n",
1081                           client->title);
1082             /* don't move it */
1083             x = client->area.x;
1084             y = client->area.y;
1085         }
1086
1087         if (config) {
1088             client_find_onscreen(client, &x, &y, w, h, FALSE);
1089             client_configure_full(client, x, y, w, h, FALSE, TRUE);
1090         }
1091         break;
1092     }
1093     case UnmapNotify:
1094         if (client->ignore_unmaps) {
1095             client->ignore_unmaps--;
1096             break;
1097         }
1098         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1099                  "ignores left %d\n",
1100                  client->window, e->xunmap.event, e->xunmap.from_configure,
1101                  client->ignore_unmaps);
1102         client_unmanage(client);
1103         break;
1104     case DestroyNotify:
1105         ob_debug("DestroyNotify for window 0x%x\n", client->window);
1106         client_unmanage(client);
1107         break;
1108     case ReparentNotify:
1109         /* this is when the client is first taken captive in the frame */
1110         if (e->xreparent.parent == client->frame->plate) break;
1111
1112         /*
1113           This event is quite rare and is usually handled in unmapHandler.
1114           However, if the window is unmapped when the reparent event occurs,
1115           the window manager never sees it because an unmap event is not sent
1116           to an already unmapped window.
1117         */
1118
1119         /* we don't want the reparent event, put it back on the stack for the
1120            X server to deal with after we unmanage the window */
1121         XPutBackEvent(ob_display, e);
1122      
1123         ob_debug("ReparentNotify for window 0x%x\n", client->window);
1124         client_unmanage(client);
1125         break;
1126     case MapRequest:
1127         ob_debug("MapRequest for 0x%lx\n", client->window);
1128         if (!client->iconic) break; /* this normally doesn't happen, but if it
1129                                        does, we don't want it!
1130                                        it can happen now when the window is on
1131                                        another desktop, but we still don't
1132                                        want it! */
1133         client_activate(client, FALSE, TRUE);
1134         break;
1135     case ClientMessage:
1136         /* validate cuz we query stuff off the client here */
1137         if (!client_validate(client)) break;
1138
1139         if (e->xclient.format != 32) return;
1140
1141         msgtype = e->xclient.message_type;
1142         if (msgtype == prop_atoms.wm_change_state) {
1143             /* compress changes into a single change */
1144             while (XCheckTypedWindowEvent(ob_display, client->window,
1145                                           e->type, &ce)) {
1146                 /* XXX: it would be nice to compress ALL messages of a
1147                    type, not just messages in a row without other
1148                    message types between. */
1149                 if (ce.xclient.message_type != msgtype) {
1150                     XPutBackEvent(ob_display, &ce);
1151                     break;
1152                 }
1153                 e->xclient = ce.xclient;
1154             }
1155             client_set_wm_state(client, e->xclient.data.l[0]);
1156         } else if (msgtype == prop_atoms.net_wm_desktop) {
1157             /* compress changes into a single change */
1158             while (XCheckTypedWindowEvent(ob_display, client->window,
1159                                           e->type, &ce)) {
1160                 /* XXX: it would be nice to compress ALL messages of a
1161                    type, not just messages in a row without other
1162                    message types between. */
1163                 if (ce.xclient.message_type != msgtype) {
1164                     XPutBackEvent(ob_display, &ce);
1165                     break;
1166                 }
1167                 e->xclient = ce.xclient;
1168             }
1169             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1170                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1171                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1172                                    FALSE);
1173         } else if (msgtype == prop_atoms.net_wm_state) {
1174             /* can't compress these */
1175             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1176                      (e->xclient.data.l[0] == 0 ? "Remove" :
1177                       e->xclient.data.l[0] == 1 ? "Add" :
1178                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1179                      e->xclient.data.l[1], e->xclient.data.l[2],
1180                      client->window);
1181             client_set_state(client, e->xclient.data.l[0],
1182                              e->xclient.data.l[1], e->xclient.data.l[2]);
1183         } else if (msgtype == prop_atoms.net_close_window) {
1184             ob_debug("net_close_window for 0x%lx\n", client->window);
1185             client_close(client);
1186         } else if (msgtype == prop_atoms.net_active_window) {
1187             ob_debug("net_active_window for 0x%lx source=%s\n",
1188                      client->window,
1189                      (e->xclient.data.l[0] == 0 ? "unknown" :
1190                       (e->xclient.data.l[0] == 1 ? "application" :
1191                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1192             /* XXX make use of data.l[2] !? */
1193             event_curtime = e->xclient.data.l[1];
1194             if (event_curtime == 0)
1195                 ob_debug_type(OB_DEBUG_APP_BUGS,
1196                               "_NET_ACTIVE_WINDOW message for window %s is "
1197                               "missing a timestamp\n", client->title);
1198             client_activate(client, FALSE,
1199                             (e->xclient.data.l[0] == 0 ||
1200                              e->xclient.data.l[0] == 2));
1201         } else if (msgtype == prop_atoms.net_wm_moveresize) {
1202             ob_debug("net_wm_moveresize for 0x%lx direction %d\n",
1203                      client->window, e->xclient.data.l[2]);
1204             if ((Atom)e->xclient.data.l[2] ==
1205                 prop_atoms.net_wm_moveresize_size_topleft ||
1206                 (Atom)e->xclient.data.l[2] ==
1207                 prop_atoms.net_wm_moveresize_size_top ||
1208                 (Atom)e->xclient.data.l[2] ==
1209                 prop_atoms.net_wm_moveresize_size_topright ||
1210                 (Atom)e->xclient.data.l[2] ==
1211                 prop_atoms.net_wm_moveresize_size_right ||
1212                 (Atom)e->xclient.data.l[2] ==
1213                 prop_atoms.net_wm_moveresize_size_right ||
1214                 (Atom)e->xclient.data.l[2] ==
1215                 prop_atoms.net_wm_moveresize_size_bottomright ||
1216                 (Atom)e->xclient.data.l[2] ==
1217                 prop_atoms.net_wm_moveresize_size_bottom ||
1218                 (Atom)e->xclient.data.l[2] ==
1219                 prop_atoms.net_wm_moveresize_size_bottomleft ||
1220                 (Atom)e->xclient.data.l[2] ==
1221                 prop_atoms.net_wm_moveresize_size_left ||
1222                 (Atom)e->xclient.data.l[2] ==
1223                 prop_atoms.net_wm_moveresize_move ||
1224                 (Atom)e->xclient.data.l[2] ==
1225                 prop_atoms.net_wm_moveresize_size_keyboard ||
1226                 (Atom)e->xclient.data.l[2] ==
1227                 prop_atoms.net_wm_moveresize_move_keyboard) {
1228
1229                 moveresize_start(client, e->xclient.data.l[0],
1230                                  e->xclient.data.l[1], e->xclient.data.l[3],
1231                                  e->xclient.data.l[2]);
1232             }
1233             else if ((Atom)e->xclient.data.l[2] ==
1234                      prop_atoms.net_wm_moveresize_cancel)
1235                 moveresize_end(TRUE);
1236         } else if (msgtype == prop_atoms.net_moveresize_window) {
1237             gint grav, x, y, w, h;
1238
1239             if (e->xclient.data.l[0] & 0xff)
1240                 grav = e->xclient.data.l[0] & 0xff;
1241             else 
1242                 grav = client->gravity;
1243
1244             if (e->xclient.data.l[0] & 1 << 8)
1245                 x = e->xclient.data.l[1];
1246             else
1247                 x = client->area.x;
1248             if (e->xclient.data.l[0] & 1 << 9)
1249                 y = e->xclient.data.l[2];
1250             else
1251                 y = client->area.y;
1252             if (e->xclient.data.l[0] & 1 << 10)
1253                 w = e->xclient.data.l[3];
1254             else
1255                 w = client->area.width;
1256             if (e->xclient.data.l[0] & 1 << 11)
1257                 h = e->xclient.data.l[4];
1258             else
1259                 h = client->area.height;
1260
1261             ob_debug("MOVERESIZE x %d %d y %d %d\n",
1262                      e->xclient.data.l[0] & 1 << 8, x,
1263                      e->xclient.data.l[0] & 1 << 9, y);
1264             client_convert_gravity(client, grav, &x, &y, w, h);
1265             client_find_onscreen(client, &x, &y, w, h, FALSE);
1266             client_configure(client, x, y, w, h, FALSE, TRUE);
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_configure_full(client, client->area.x,
1301                                           client->area.y,
1302                                           client->area.width,
1303                                           client->area.height,
1304                                           FALSE, TRUE);
1305                 } else
1306                     ob_debug_type(OB_DEBUG_APP_BUGS,
1307                                   "_NET_RESTACK_WINDOW sent for window %s "
1308                                   "with invalid detail %d\n",
1309                                   client->title, e->xclient.data.l[2]);
1310             }
1311         }
1312         break;
1313     case PropertyNotify:
1314         /* validate cuz we query stuff off the client here */
1315         if (!client_validate(client)) break;
1316   
1317         /* compress changes to a single property into a single change */
1318         while (XCheckTypedWindowEvent(ob_display, client->window,
1319                                       e->type, &ce)) {
1320             Atom a, b;
1321
1322             /* XXX: it would be nice to compress ALL changes to a property,
1323                not just changes in a row without other props between. */
1324
1325             a = ce.xproperty.atom;
1326             b = e->xproperty.atom;
1327
1328             if (a == b)
1329                 continue;
1330             if ((a == prop_atoms.net_wm_name ||
1331                  a == prop_atoms.wm_name ||
1332                  a == prop_atoms.net_wm_icon_name ||
1333                  a == prop_atoms.wm_icon_name)
1334                 &&
1335                 (b == prop_atoms.net_wm_name ||
1336                  b == prop_atoms.wm_name ||
1337                  b == prop_atoms.net_wm_icon_name ||
1338                  b == prop_atoms.wm_icon_name)) {
1339                 continue;
1340             }
1341             if (a == prop_atoms.net_wm_icon &&
1342                 b == prop_atoms.net_wm_icon)
1343                 continue;
1344
1345             XPutBackEvent(ob_display, &ce);
1346             break;
1347         }
1348
1349         msgtype = e->xproperty.atom;
1350         if (msgtype == XA_WM_NORMAL_HINTS) {
1351             client_update_normal_hints(client);
1352             /* normal hints can make a window non-resizable */
1353             client_setup_decor_and_functions(client);
1354         } else if (msgtype == XA_WM_HINTS) {
1355             client_update_wmhints(client);
1356         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1357             client_update_transient_for(client);
1358             client_get_type_and_transientness(client);
1359             /* type may have changed, so update the layer */
1360             client_calc_layer(client);
1361             client_setup_decor_and_functions(client);
1362         } else if (msgtype == prop_atoms.net_wm_name ||
1363                    msgtype == prop_atoms.wm_name ||
1364                    msgtype == prop_atoms.net_wm_icon_name ||
1365                    msgtype == prop_atoms.wm_icon_name) {
1366             client_update_title(client);
1367         } else if (msgtype == prop_atoms.wm_protocols) {
1368             client_update_protocols(client);
1369             client_setup_decor_and_functions(client);
1370         }
1371         else if (msgtype == prop_atoms.net_wm_strut) {
1372             client_update_strut(client);
1373         }
1374         else if (msgtype == prop_atoms.net_wm_icon) {
1375             client_update_icons(client);
1376         }
1377         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1378             client_update_icon_geometry(client);
1379         }
1380         else if (msgtype == prop_atoms.net_wm_user_time) {
1381             client_update_user_time(client);
1382         }
1383         else if (msgtype == prop_atoms.net_wm_user_time_window) {
1384             client_update_user_time_window(client);
1385         }
1386 #ifdef SYNC
1387         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1388             client_update_sync_request_counter(client);
1389         }
1390 #endif
1391     case ColormapNotify:
1392         client_update_colormap(client, e->xcolormap.colormap);
1393         break;
1394     default:
1395         ;
1396 #ifdef SHAPE
1397         if (extensions_shape && e->type == extensions_shape_event_basep) {
1398             client->shaped = ((XShapeEvent*)e)->shaped;
1399             frame_adjust_shape(client->frame);
1400         }
1401 #endif
1402     }
1403 }
1404
1405 static void event_handle_dock(ObDock *s, XEvent *e)
1406 {
1407     switch (e->type) {
1408     case ButtonPress:
1409         if (e->xbutton.button == 1)
1410             stacking_raise(DOCK_AS_WINDOW(s));
1411         else if (e->xbutton.button == 2)
1412             stacking_lower(DOCK_AS_WINDOW(s));
1413         break;
1414     case EnterNotify:
1415         dock_hide(FALSE);
1416         break;
1417     case LeaveNotify:
1418         dock_hide(TRUE);
1419         break;
1420     }
1421 }
1422
1423 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1424 {
1425     switch (e->type) {
1426     case MotionNotify:
1427         dock_app_drag(app, &e->xmotion);
1428         break;
1429     case UnmapNotify:
1430         if (app->ignore_unmaps) {
1431             app->ignore_unmaps--;
1432             break;
1433         }
1434         dock_remove(app, TRUE);
1435         break;
1436     case DestroyNotify:
1437         dock_remove(app, FALSE);
1438         break;
1439     case ReparentNotify:
1440         dock_remove(app, FALSE);
1441         break;
1442     case ConfigureNotify:
1443         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1444         break;
1445     }
1446 }
1447
1448 static ObMenuFrame* find_active_menu()
1449 {
1450     GList *it;
1451     ObMenuFrame *ret = NULL;
1452
1453     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1454         ret = it->data;
1455         if (ret->selected)
1456             break;
1457         ret = NULL;
1458     }
1459     return ret;
1460 }
1461
1462 static ObMenuFrame* find_active_or_last_menu()
1463 {
1464     ObMenuFrame *ret = NULL;
1465
1466     ret = find_active_menu();
1467     if (!ret && menu_frame_visible)
1468         ret = menu_frame_visible->data;
1469     return ret;
1470 }
1471
1472 static gboolean event_handle_menu_keyboard(XEvent *ev)
1473 {
1474     guint keycode, state;
1475     gunichar unikey;
1476     ObMenuFrame *frame;
1477     gboolean ret = TRUE;
1478
1479     keycode = ev->xkey.keycode;
1480     state = ev->xkey.state;
1481     unikey = translate_unichar(keycode);
1482
1483     frame = find_active_or_last_menu();
1484     if (frame == NULL)
1485         ret = FALSE;
1486
1487     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1488         /* Escape closes the active menu */
1489         menu_frame_hide(frame);
1490     }
1491
1492     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1493                                                       state == ControlMask))
1494     {
1495         /* Enter runs the active item or goes into the submenu.
1496            Control-Enter runs it without closing the menu. */
1497         if (frame->child)
1498             menu_frame_select_next(frame->child);
1499         else
1500             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1501     }
1502
1503     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1504         /* Left goes to the parent menu */
1505         menu_frame_select(frame, NULL, TRUE);
1506     }
1507
1508     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1509         /* Right goes to the selected submenu */
1510         if (frame->child) menu_frame_select_next(frame->child);
1511     }
1512
1513     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1514         menu_frame_select_previous(frame);
1515     }
1516
1517     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1518         menu_frame_select_next(frame);
1519     }
1520
1521     /* keyboard accelerator shortcuts. */
1522     else if (ev->xkey.state == 0 &&
1523              /* was it a valid key? */
1524              unikey != 0 &&
1525              /* don't bother if the menu is empty. */
1526              frame->entries)
1527     {
1528         GList *start;
1529         GList *it;
1530         ObMenuEntryFrame *found = NULL;
1531         guint num_found = 0;
1532
1533         /* start after the selected one */
1534         start = frame->entries;
1535         if (frame->selected) {
1536             for (it = start; frame->selected != it->data; it = g_list_next(it))
1537                 g_assert(it != NULL); /* nothing was selected? */
1538             /* next with wraparound */
1539             start = g_list_next(it);
1540             if (start == NULL) start = frame->entries;
1541         }
1542
1543         it = start;
1544         do {
1545             ObMenuEntryFrame *e = it->data;
1546             gunichar entrykey = 0;
1547
1548             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1549                 entrykey = e->entry->data.normal.shortcut;
1550             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1551                 entrykey = e->entry->data.submenu.submenu->shortcut;
1552
1553             if (unikey == entrykey) {
1554                 if (found == NULL) found = e;
1555                 ++num_found;
1556             }
1557
1558             /* next with wraparound */
1559             it = g_list_next(it);
1560             if (it == NULL) it = frame->entries;
1561         } while (it != start);
1562
1563         if (found) {
1564             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1565                 num_found == 1)
1566             {
1567                 menu_frame_select(frame, found, TRUE);
1568                 usleep(50000); /* highlight the item for a short bit so the
1569                                   user can see what happened */
1570                 menu_entry_frame_execute(found, state, ev->xkey.time);
1571             } else {
1572                 menu_frame_select(frame, found, TRUE);
1573                 if (num_found == 1)
1574                     menu_frame_select_next(frame->child);
1575             }
1576         } else
1577             ret = FALSE;
1578     }
1579     else
1580         ret = FALSE;
1581
1582     return ret;
1583 }
1584
1585 static gboolean event_handle_menu(XEvent *ev)
1586 {
1587     ObMenuFrame *f;
1588     ObMenuEntryFrame *e;
1589     gboolean ret = TRUE;
1590
1591     switch (ev->type) {
1592     case ButtonRelease:
1593         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1594             && menu_can_hide)
1595         {
1596             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1597                                             ev->xbutton.y_root)))
1598                 menu_entry_frame_execute(e, ev->xbutton.state,
1599                                          ev->xbutton.time);
1600             else
1601                 menu_frame_hide_all();
1602         }
1603         break;
1604     case EnterNotify:
1605         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1606             if (e->ignore_enters)
1607                 --e->ignore_enters;
1608             else
1609                 menu_frame_select(e->frame, e, FALSE);
1610         }
1611         break;
1612     case LeaveNotify:
1613         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1614             (f = find_active_menu()) && f->selected == e &&
1615             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1616         {
1617             menu_frame_select(e->frame, NULL, FALSE);
1618         }
1619         break;
1620     case MotionNotify:   
1621         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1622                                         ev->xmotion.y_root)))
1623             menu_frame_select(e->frame, e, FALSE);
1624         break;
1625     case KeyPress:
1626         ret = event_handle_menu_keyboard(ev);
1627         break;
1628     }
1629     return ret;
1630 }
1631
1632 static void event_handle_user_input(ObClient *client, XEvent *e)
1633 {
1634     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1635              e->type == MotionNotify || e->type == KeyPress ||
1636              e->type == KeyRelease);
1637
1638     if (menu_frame_visible) {
1639         if (event_handle_menu(e))
1640             /* don't use the event if the menu used it, but if the menu
1641                didn't use it and it's a keypress that is bound, it will
1642                close the menu and be used */
1643             return;
1644     }
1645
1646     /* if the keyboard interactive action uses the event then dont
1647        use it for bindings. likewise is moveresize uses the event. */
1648     if (!keyboard_process_interactive_grab(e, &client) &&
1649         !(moveresize_in_progress && moveresize_event(e)))
1650     {
1651         if (moveresize_in_progress)
1652             /* make further actions work on the client being
1653                moved/resized */
1654             client = moveresize_client;
1655
1656         menu_can_hide = FALSE;
1657         ob_main_loop_timeout_add(ob_main_loop,
1658                                  config_menu_hide_delay * 1000,
1659                                  menu_hide_delay_func,
1660                                  NULL, g_direct_equal, NULL);
1661
1662         if (e->type == ButtonPress ||
1663             e->type == ButtonRelease ||
1664             e->type == MotionNotify)
1665         {
1666             /* the frame may not be "visible" but they can still click on it
1667                in the case where it is animating before disappearing */
1668             if (!client || !frame_iconify_animating(client->frame))
1669                 mouse_event(client, e);
1670         } else if (e->type == KeyPress) {
1671             keyboard_event((focus_cycle_target ? focus_cycle_target :
1672                             (client ? client : focus_client)), e);
1673         }
1674     }
1675 }
1676
1677 static gboolean menu_hide_delay_func(gpointer data)
1678 {
1679     menu_can_hide = TRUE;
1680     return FALSE; /* no repeat */
1681 }
1682
1683 static void focus_delay_dest(gpointer data)
1684 {
1685     g_free(data);
1686 }
1687
1688 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1689 {
1690     const ObFocusDelayData *f1 = d1;
1691     return f1->client == d2;
1692 }
1693
1694 static gboolean focus_delay_func(gpointer data)
1695 {
1696     ObFocusDelayData *d = data;
1697     Time old = event_curtime;
1698
1699     event_curtime = d->time;
1700     if (focus_client != d->client) {
1701         if (client_focus(d->client) && config_focus_raise)
1702             stacking_raise(CLIENT_AS_WINDOW(d->client));
1703     }
1704     event_curtime = old;
1705     return FALSE; /* no repeat */
1706 }
1707
1708 static void focus_delay_client_dest(ObClient *client, gpointer data)
1709 {
1710     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1711                                      client, FALSE);
1712 }
1713
1714 void event_halt_focus_delay()
1715 {
1716     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1717 }
1718
1719 void event_ignore_queued_enters()
1720 {
1721     GSList *saved = NULL, *it;
1722     XEvent *e;
1723                 
1724     XSync(ob_display, FALSE);
1725
1726     /* count the events */
1727     while (TRUE) {
1728         e = g_new(XEvent, 1);
1729         if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
1730             ObWindow *win;
1731             
1732             win = g_hash_table_lookup(window_map, &e->xany.window);
1733             if (win && WINDOW_IS_CLIENT(win))
1734                 ++ignore_enter_focus;
1735             
1736             saved = g_slist_append(saved, e);
1737         } else {
1738             g_free(e);
1739             break;
1740         }
1741     }
1742     /* put the events back */
1743     for (it = saved; it; it = g_slist_next(it)) {
1744         XPutBackEvent(ob_display, it->data);
1745         g_free(it->data);
1746     }
1747     g_slist_free(saved);
1748 }
1749
1750 gboolean event_time_after(Time t1, Time t2)
1751 {
1752     g_assert(t1 != CurrentTime);
1753     g_assert(t2 != CurrentTime);
1754
1755     /*
1756       Timestamp values wrap around (after about 49.7 days). The server, given
1757       its current time is represented by timestamp T, always interprets
1758       timestamps from clients by treating half of the timestamp space as being
1759       later in time than T.
1760       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1761     */
1762
1763     /* TIME_HALF is half of the number space of a Time type variable */
1764 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1765
1766     if (t2 >= TIME_HALF)
1767         /* t2 is in the second half so t1 might wrap around and be smaller than
1768            t2 */
1769         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1770     else
1771         /* t2 is in the first half so t1 has to come after it */
1772         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1773 }