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