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