call client_reconfigure after setup_decor_and_functions to make the changes appear
[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                 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);
1434             client_reconfigure(client);
1435         } else if (msgtype == XA_WM_HINTS) {
1436             client_update_wmhints(client);
1437         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1438             client_update_transient_for(client);
1439             client_get_type_and_transientness(client);
1440             /* type may have changed, so update the layer */
1441             client_calc_layer(client);
1442             client_setup_decor_and_functions(client);
1443             client_reconfigure(client);
1444         } else if (msgtype == prop_atoms.net_wm_name ||
1445                    msgtype == prop_atoms.wm_name ||
1446                    msgtype == prop_atoms.net_wm_icon_name ||
1447                    msgtype == prop_atoms.wm_icon_name) {
1448             client_update_title(client);
1449         } else if (msgtype == prop_atoms.wm_protocols) {
1450             client_update_protocols(client);
1451             client_setup_decor_and_functions(client);
1452             client_reconfigure(client);
1453         }
1454         else if (msgtype == prop_atoms.net_wm_strut) {
1455             client_update_strut(client);
1456         }
1457         else if (msgtype == prop_atoms.net_wm_strut_partial) {
1458             client_update_strut(client);
1459         }
1460         else if (msgtype == prop_atoms.net_wm_icon) {
1461             client_update_icons(client);
1462         }
1463         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1464             client_update_icon_geometry(client);
1465         }
1466         else if (msgtype == prop_atoms.net_wm_user_time) {
1467             client_update_user_time(client);
1468         }
1469         else if (msgtype == prop_atoms.net_wm_user_time_window) {
1470             client_update_user_time_window(client);
1471         }
1472 #ifdef SYNC
1473         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1474             client_update_sync_request_counter(client);
1475         }
1476 #endif
1477         break;
1478     case ColormapNotify:
1479         client_update_colormap(client, e->xcolormap.colormap);
1480         break;
1481     default:
1482         ;
1483 #ifdef SHAPE
1484         if (extensions_shape && e->type == extensions_shape_event_basep) {
1485             client->shaped = ((XShapeEvent*)e)->shaped;
1486             frame_adjust_shape(client->frame);
1487         }
1488 #endif
1489     }
1490 }
1491
1492 static void event_handle_dock(ObDock *s, XEvent *e)
1493 {
1494     switch (e->type) {
1495     case ButtonPress:
1496         if (e->xbutton.button == 1)
1497             stacking_raise(DOCK_AS_WINDOW(s));
1498         else if (e->xbutton.button == 2)
1499             stacking_lower(DOCK_AS_WINDOW(s));
1500         break;
1501     case EnterNotify:
1502         dock_hide(FALSE);
1503         break;
1504     case LeaveNotify:
1505         dock_hide(TRUE);
1506         break;
1507     }
1508 }
1509
1510 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1511 {
1512     switch (e->type) {
1513     case MotionNotify:
1514         dock_app_drag(app, &e->xmotion);
1515         break;
1516     case UnmapNotify:
1517         if (app->ignore_unmaps) {
1518             app->ignore_unmaps--;
1519             break;
1520         }
1521         dock_remove(app, TRUE);
1522         break;
1523     case DestroyNotify:
1524         dock_remove(app, FALSE);
1525         break;
1526     case ReparentNotify:
1527         dock_remove(app, FALSE);
1528         break;
1529     case ConfigureNotify:
1530         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1531         break;
1532     }
1533 }
1534
1535 static ObMenuFrame* find_active_menu()
1536 {
1537     GList *it;
1538     ObMenuFrame *ret = NULL;
1539
1540     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1541         ret = it->data;
1542         if (ret->selected)
1543             break;
1544         ret = NULL;
1545     }
1546     return ret;
1547 }
1548
1549 static ObMenuFrame* find_active_or_last_menu()
1550 {
1551     ObMenuFrame *ret = NULL;
1552
1553     ret = find_active_menu();
1554     if (!ret && menu_frame_visible)
1555         ret = menu_frame_visible->data;
1556     return ret;
1557 }
1558
1559 static gboolean event_handle_menu_keyboard(XEvent *ev)
1560 {
1561     guint keycode, state;
1562     gunichar unikey;
1563     ObMenuFrame *frame;
1564     gboolean ret = TRUE;
1565
1566     keycode = ev->xkey.keycode;
1567     state = ev->xkey.state;
1568     unikey = translate_unichar(keycode);
1569
1570     frame = find_active_or_last_menu();
1571     if (frame == NULL)
1572         ret = FALSE;
1573
1574     else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) {
1575         /* Escape goes to the parent menu or closes the last one */
1576         if (frame->parent)
1577             menu_frame_select(frame, NULL, TRUE);
1578         else
1579             menu_frame_hide_all();
1580     }
1581
1582     else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 ||
1583                                                       state == ControlMask))
1584     {
1585         /* Enter runs the active item or goes into the submenu.
1586            Control-Enter runs it without closing the menu. */
1587         if (frame->child)
1588             menu_frame_select_next(frame->child);
1589         else if (frame->selected)
1590             menu_entry_frame_execute(frame->selected, state, ev->xkey.time);
1591     }
1592
1593     else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) {
1594         /* Left goes to the parent menu */
1595         menu_frame_select(frame, NULL, TRUE);
1596     }
1597
1598     else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) {
1599         /* Right goes to the selected submenu */
1600         if (frame->child) menu_frame_select_next(frame->child);
1601     }
1602
1603     else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) {
1604         menu_frame_select_previous(frame);
1605     }
1606
1607     else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) {
1608         menu_frame_select_next(frame);
1609     }
1610
1611     /* keyboard accelerator shortcuts. (allow controlmask) */
1612     else if ((ev->xkey.state & ~ControlMask) == 0 &&
1613              /* was it a valid key? */
1614              unikey != 0 &&
1615              /* don't bother if the menu is empty. */
1616              frame->entries)
1617     {
1618         GList *start;
1619         GList *it;
1620         ObMenuEntryFrame *found = NULL;
1621         guint num_found = 0;
1622
1623         /* start after the selected one */
1624         start = frame->entries;
1625         if (frame->selected) {
1626             for (it = start; frame->selected != it->data; it = g_list_next(it))
1627                 g_assert(it != NULL); /* nothing was selected? */
1628             /* next with wraparound */
1629             start = g_list_next(it);
1630             if (start == NULL) start = frame->entries;
1631         }
1632
1633         it = start;
1634         do {
1635             ObMenuEntryFrame *e = it->data;
1636             gunichar entrykey = 0;
1637
1638             if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1639                 entrykey = e->entry->data.normal.shortcut;
1640             else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1641                 entrykey = e->entry->data.submenu.submenu->shortcut;
1642
1643             if (unikey == entrykey) {
1644                 if (found == NULL) found = e;
1645                 ++num_found;
1646             }
1647
1648             /* next with wraparound */
1649             it = g_list_next(it);
1650             if (it == NULL) it = frame->entries;
1651         } while (it != start);
1652
1653         if (found) {
1654             if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1655                 num_found == 1)
1656             {
1657                 menu_frame_select(frame, found, TRUE);
1658                 usleep(50000); /* highlight the item for a short bit so the
1659                                   user can see what happened */
1660                 menu_entry_frame_execute(found, state, ev->xkey.time);
1661             } else {
1662                 menu_frame_select(frame, found, TRUE);
1663                 if (num_found == 1)
1664                     menu_frame_select_next(frame->child);
1665             }
1666         } else
1667             ret = FALSE;
1668     }
1669     else
1670         ret = FALSE;
1671
1672     return ret;
1673 }
1674
1675 static gboolean event_handle_menu(XEvent *ev)
1676 {
1677     ObMenuFrame *f;
1678     ObMenuEntryFrame *e;
1679     gboolean ret = TRUE;
1680
1681     switch (ev->type) {
1682     case ButtonRelease:
1683         if ((ev->xbutton.button < 4 || ev->xbutton.button > 5)
1684             && menu_can_hide)
1685         {
1686             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1687                                             ev->xbutton.y_root)))
1688             {
1689                 menu_frame_select(e->frame, e, TRUE);
1690                 menu_entry_frame_execute(e, ev->xbutton.state,
1691                                          ev->xbutton.time);
1692             }
1693             else
1694                 menu_frame_hide_all();
1695         }
1696         break;
1697     case EnterNotify:
1698         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1699             if (e->ignore_enters)
1700                 --e->ignore_enters;
1701             else if (!(f = find_active_menu()) ||
1702                      f == e->frame ||
1703                      f->parent == e->frame ||
1704                      f->child == e->frame)
1705                 menu_frame_select(e->frame, e, FALSE);
1706         }
1707         break;
1708     case LeaveNotify:
1709         /*ignore leaves when we're already in the window */
1710         if (ev->xcrossing.detail == NotifyInferior)
1711             break;
1712
1713         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1714             (f = find_active_menu()) && f->selected == e &&
1715             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1716         {
1717             menu_frame_select(e->frame, NULL, FALSE);
1718         }
1719         break;
1720     case MotionNotify:   
1721         if ((e = menu_entry_frame_under(ev->xmotion.x_root,   
1722                                         ev->xmotion.y_root)))
1723             if (!(f = find_active_menu()) ||
1724                 f == e->frame ||
1725                 f->parent == e->frame ||
1726                 f->child == e->frame)
1727                 menu_frame_select(e->frame, e, FALSE);
1728         break;
1729     case KeyPress:
1730         ret = event_handle_menu_keyboard(ev);
1731         break;
1732     }
1733     return ret;
1734 }
1735
1736 static void event_handle_user_input(ObClient *client, XEvent *e)
1737 {
1738     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1739              e->type == MotionNotify || e->type == KeyPress ||
1740              e->type == KeyRelease);
1741
1742     if (menu_frame_visible) {
1743         if (event_handle_menu(e))
1744             /* don't use the event if the menu used it, but if the menu
1745                didn't use it and it's a keypress that is bound, it will
1746                close the menu and be used */
1747             return;
1748     }
1749
1750     /* if the keyboard interactive action uses the event then dont
1751        use it for bindings. likewise is moveresize uses the event. */
1752     if (!keyboard_process_interactive_grab(e, &client) &&
1753         !(moveresize_in_progress && moveresize_event(e)))
1754     {
1755         if (moveresize_in_progress)
1756             /* make further actions work on the client being
1757                moved/resized */
1758             client = moveresize_client;
1759
1760         menu_can_hide = FALSE;
1761         ob_main_loop_timeout_add(ob_main_loop,
1762                                  config_menu_hide_delay * 1000,
1763                                  menu_hide_delay_func,
1764                                  NULL, g_direct_equal, NULL);
1765
1766         if (e->type == ButtonPress ||
1767             e->type == ButtonRelease ||
1768             e->type == MotionNotify)
1769         {
1770             /* the frame may not be "visible" but they can still click on it
1771                in the case where it is animating before disappearing */
1772             if (!client || !frame_iconify_animating(client->frame))
1773                 mouse_event(client, e);
1774         } else if (e->type == KeyPress) {
1775             keyboard_event((focus_cycle_target ? focus_cycle_target :
1776                             (client ? client : focus_client)), e);
1777         }
1778     }
1779 }
1780
1781 static gboolean menu_hide_delay_func(gpointer data)
1782 {
1783     menu_can_hide = TRUE;
1784     return FALSE; /* no repeat */
1785 }
1786
1787 static void focus_delay_dest(gpointer data)
1788 {
1789     g_free(data);
1790 }
1791
1792 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1793 {
1794     const ObFocusDelayData *f1 = d1;
1795     return f1->client == d2;
1796 }
1797
1798 static gboolean focus_delay_func(gpointer data)
1799 {
1800     ObFocusDelayData *d = data;
1801     Time old = event_curtime;
1802
1803     event_curtime = d->time;
1804     if (focus_client != d->client) {
1805         if (client_focus(d->client) && config_focus_raise)
1806             stacking_raise(CLIENT_AS_WINDOW(d->client));
1807     }
1808     event_curtime = old;
1809     return FALSE; /* no repeat */
1810 }
1811
1812 static void focus_delay_client_dest(ObClient *client, gpointer data)
1813 {
1814     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1815                                      client, FALSE);
1816 }
1817
1818 void event_halt_focus_delay()
1819 {
1820     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1821 }
1822
1823 static Bool event_look_for_enters(Display *d, XEvent *e, XPointer arg)
1824 {
1825     if (e->type == EnterNotify &&
1826         /* these types aren't used for focusing */
1827         !(e->xcrossing.mode == NotifyGrab ||
1828           e->xcrossing.mode == NotifyUngrab ||
1829           e->xcrossing.detail == NotifyInferior))
1830     {
1831         ObWindow *win;
1832
1833         /* found an enter for that leave, ignore it if it's going to
1834            another window */
1835         win = g_hash_table_lookup(window_map, &e->xany.window);
1836         if (win && WINDOW_IS_CLIENT(win))
1837             ++ignore_enter_focus;
1838     }
1839     return False; /* don't disrupt the queue order, just count them */
1840 }
1841
1842 void event_ignore_all_queued_enters()
1843 {
1844     XEvent e;
1845
1846     XSync(ob_display, FALSE);
1847
1848     /* count the events without disrupting them */
1849     ignore_enter_focus = 0;
1850     XCheckIfEvent(ob_display, &e, event_look_for_enters, NULL);
1851 }
1852
1853 static gboolean is_enter_focus_event_ignored(XEvent *e)
1854 {
1855     g_assert(e->type == EnterNotify &&
1856              !(e->xcrossing.mode == NotifyGrab ||
1857                e->xcrossing.mode == NotifyUngrab ||
1858                e->xcrossing.detail == NotifyInferior));
1859
1860     ob_debug_type(OB_DEBUG_FOCUS, "# enters ignored: %d\n",
1861                   ignore_enter_focus);
1862
1863     if (ignore_enter_focus) {
1864         --ignore_enter_focus;
1865         return TRUE;
1866     }
1867     return FALSE;
1868 }
1869
1870 void event_cancel_all_key_grabs()
1871 {
1872     if (keyboard_interactively_grabbed())
1873         keyboard_interactive_cancel();
1874     else if (menu_frame_visible)
1875         menu_frame_hide_all();
1876     else if (grab_on_keyboard())
1877         ungrab_keyboard();
1878     else
1879         /* If we don't have the keyboard grabbed, then ungrab it with
1880            XUngrabKeyboard, so that there is not a passive grab left
1881            on from the KeyPress. If the grab is left on, and focus
1882            moves during that time, it will be NotifyWhileGrabbed, and
1883            applications like to ignore those! */
1884         if (!keyboard_interactively_grabbed())
1885             XUngrabKeyboard(ob_display, CurrentTime);
1886
1887 }
1888
1889 gboolean event_time_after(Time t1, Time t2)
1890 {
1891     g_assert(t1 != CurrentTime);
1892     g_assert(t2 != CurrentTime);
1893
1894     /*
1895       Timestamp values wrap around (after about 49.7 days). The server, given
1896       its current time is represented by timestamp T, always interprets
1897       timestamps from clients by treating half of the timestamp space as being
1898       later in time than T.
1899       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1900     */
1901
1902     /* TIME_HALF is half of the number space of a Time type variable */
1903 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1904
1905     if (t2 >= TIME_HALF)
1906         /* t2 is in the second half so t1 might wrap around and be smaller than
1907            t2 */
1908         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1909     else
1910         /* t2 is in the first half so t1 has to come after it */
1911         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1912 }