show a popup notification when switching desktops (also make all the config options...
[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 "actions.h"
26 #include "client.h"
27 #include "xerror.h"
28 #include "prop.h"
29 #include "config.h"
30 #include "screen.h"
31 #include "frame.h"
32 #include "grab.h"
33 #include "menu.h"
34 #include "menuframe.h"
35 #include "keyboard.h"
36 #include "modkeys.h"
37 #include "mouse.h"
38 #include "mainloop.h"
39 #include "focus.h"
40 #include "focus_cycle.h"
41 #include "moveresize.h"
42 #include "group.h"
43 #include "stacking.h"
44 #include "extensions.h"
45 #include "translate.h"
46
47 #include <X11/Xlib.h>
48 #include <X11/Xatom.h>
49 #include <glib.h>
50
51 #ifdef HAVE_SYS_SELECT_H
52 #  include <sys/select.h>
53 #endif
54 #ifdef HAVE_SIGNAL_H
55 #  include <signal.h>
56 #endif
57 #ifdef HAVE_UNISTD_H
58 #  include <unistd.h> /* for usleep() */
59 #endif
60 #ifdef XKB
61 #  include <X11/XKBlib.h>
62 #endif
63
64 #ifdef USE_SM
65 #include <X11/ICE/ICElib.h>
66 #endif
67
68 typedef struct
69 {
70     gboolean ignored;
71 } ObEventData;
72
73 typedef struct
74 {
75     ObClient *client;
76     Time time;
77 } ObFocusDelayData;
78
79 typedef struct
80 {
81     gulong start; /* inclusive */
82     gulong end;   /* inclusive */
83 } ObSerialRange;
84
85 static void event_process(const XEvent *e, gpointer data);
86 static void event_handle_root(XEvent *e);
87 static gboolean event_handle_menu_keyboard(XEvent *e);
88 static gboolean event_handle_menu(XEvent *e);
89 static void event_handle_dock(ObDock *s, XEvent *e);
90 static void event_handle_dockapp(ObDockApp *app, XEvent *e);
91 static void event_handle_client(ObClient *c, XEvent *e);
92 static void event_handle_user_input(ObClient *client, XEvent *e);
93 static gboolean is_enter_focus_event_ignored(XEvent *e);
94
95 static void focus_delay_dest(gpointer data);
96 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2);
97 static gboolean focus_delay_func(gpointer data);
98 static void focus_delay_client_dest(ObClient *client, gpointer data);
99
100 Time event_curtime = CurrentTime;
101 Time event_last_user_time = CurrentTime;
102
103 static gboolean focus_left_screen = FALSE;
104 /*! A list of ObSerialRanges which are to be ignored for mouse enter events */
105 static GSList *ignore_serials = NULL;
106
107 #ifdef USE_SM
108 static void ice_handler(gint fd, gpointer conn)
109 {
110     Bool b;
111     IceProcessMessages(conn, NULL, &b);
112 }
113
114 static void ice_watch(IceConn conn, IcePointer data, Bool opening,
115                       IcePointer *watch_data)
116 {
117     static gint fd = -1;
118
119     if (opening) {
120         fd = IceConnectionNumber(conn);
121         ob_main_loop_fd_add(ob_main_loop, fd, ice_handler, conn, NULL);
122     } else {
123         ob_main_loop_fd_remove(ob_main_loop, fd);
124         fd = -1;
125     }
126 }
127 #endif
128
129 void event_startup(gboolean reconfig)
130 {
131     if (reconfig) return;
132
133     ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL);
134
135 #ifdef USE_SM
136     IceAddConnectionWatch(ice_watch, NULL);
137 #endif
138
139     client_add_destroy_notify(focus_delay_client_dest, NULL);
140 }
141
142 void event_shutdown(gboolean reconfig)
143 {
144     if (reconfig) return;
145
146 #ifdef USE_SM
147     IceRemoveConnectionWatch(ice_watch, NULL);
148 #endif
149
150     client_remove_destroy_notify(focus_delay_client_dest);
151 }
152
153 static Window event_get_window(XEvent *e)
154 {
155     Window window;
156
157     /* pick a window */
158     switch (e->type) {
159     case SelectionClear:
160         window = RootWindow(ob_display, ob_screen);
161         break;
162     case MapRequest:
163         window = e->xmap.window;
164         break;
165     case UnmapNotify:
166         window = e->xunmap.window;
167         break;
168     case DestroyNotify:
169         window = e->xdestroywindow.window;
170         break;
171     case ConfigureRequest:
172         window = e->xconfigurerequest.window;
173         break;
174     case ConfigureNotify:
175         window = e->xconfigure.window;
176         break;
177     default:
178 #ifdef XKB
179         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
180             switch (((XkbAnyEvent*)e)->xkb_type) {
181             case XkbBellNotify:
182                 window = ((XkbBellNotifyEvent*)e)->window;
183             default:
184                 window = None;
185             }
186         } else
187 #endif
188 #ifdef SYNC
189         if (extensions_sync &&
190             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
191         {
192             window = None;
193         } else
194 #endif
195             window = e->xany.window;
196     }
197     return window;
198 }
199
200 static void event_set_curtime(XEvent *e)
201 {
202     Time t = CurrentTime;
203
204     /* grab the lasttime and hack up the state */
205     switch (e->type) {
206     case ButtonPress:
207     case ButtonRelease:
208         t = e->xbutton.time;
209         break;
210     case KeyPress:
211         t = e->xkey.time;
212         break;
213     case KeyRelease:
214         t = e->xkey.time;
215         break;
216     case MotionNotify:
217         t = e->xmotion.time;
218         break;
219     case PropertyNotify:
220         t = e->xproperty.time;
221         break;
222     case EnterNotify:
223     case LeaveNotify:
224         t = e->xcrossing.time;
225         break;
226     default:
227 #ifdef SYNC
228         if (extensions_sync &&
229             e->type == extensions_sync_event_basep + XSyncAlarmNotify)
230         {
231             t = ((XSyncAlarmNotifyEvent*)e)->time;
232         }
233 #endif
234         /* if more event types are anticipated, get their timestamp
235            explicitly */
236         break;
237     }
238
239     /* watch that if we get an event earlier than the last specified user_time,
240        which can happen if the clock goes backwards, we erase the last
241        specified user_time */
242     if (t && event_last_user_time && event_time_after(event_last_user_time, t))
243         event_last_user_time = CurrentTime;
244
245     event_curtime = t;
246 }
247
248 static void event_hack_mods(XEvent *e)
249 {
250     switch (e->type) {
251     case ButtonPress:
252     case ButtonRelease:
253         e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
254         break;
255     case KeyPress:
256         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
257         break;
258     case KeyRelease:
259         e->xkey.state = modkeys_only_modifier_masks(e->xkey.state);
260         /* remove from the state the mask of the modifier key being released,
261            if it is a modifier key being released that is */
262         e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode);
263         break;
264     case MotionNotify:
265         e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
266         /* compress events */
267         {
268             XEvent ce;
269             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
270                                           e->type, &ce)) {
271                 e->xmotion.x = ce.xmotion.x;
272                 e->xmotion.y = ce.xmotion.y;
273                 e->xmotion.x_root = ce.xmotion.x_root;
274                 e->xmotion.y_root = ce.xmotion.y_root;
275             }
276         }
277         break;
278     }
279 }
280
281 static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
282 {
283     gint mode = e->xfocus.mode;
284     gint detail = e->xfocus.detail;
285     Window win = e->xany.window;
286
287     if (e->type == FocusIn) {
288         /* These are ones we never want.. */
289
290         /* This means focus was given by a keyboard/mouse grab. */
291         if (mode == NotifyGrab)
292             return FALSE;
293         /* This means focus was given back from a keyboard/mouse grab. */
294         if (mode == NotifyUngrab)
295             return FALSE;
296
297         /* These are the ones we want.. */
298
299         if (win == RootWindow(ob_display, ob_screen)) {
300             /* If looking for a focus in on a client, then always return
301                FALSE for focus in's to the root window */
302             if (in_client_only)
303                 return FALSE;
304             /* This means focus reverted off of a client */
305             else if (detail == NotifyPointerRoot ||
306                      detail == NotifyDetailNone ||
307                      detail == NotifyInferior ||
308                      /* This means focus got here from another screen */
309                      detail == NotifyNonlinear)
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     XEvent ee, *e;
441     ObEventData *ed = data;
442
443     /* make a copy we can mangle */
444     ee = *ec;
445     e = &ee;
446
447     window = event_get_window(e);
448     if ((obwin = g_hash_table_lookup(window_map, &window))) {
449         switch (obwin->type) {
450         case Window_Dock:
451             dock = WINDOW_AS_DOCK(obwin);
452             break;
453         case Window_DockApp:
454             dockapp = WINDOW_AS_DOCKAPP(obwin);
455             break;
456         case Window_Client:
457             client = WINDOW_AS_CLIENT(obwin);
458             break;
459         case Window_Menu:
460         case Window_Internal:
461             /* not to be used for events */
462             g_assert_not_reached();
463             break;
464         }
465     }
466
467     event_set_curtime(e);
468     event_hack_mods(e);
469     if (event_ignore(e, client)) {
470         if (ed)
471             ed->ignored = TRUE;
472         return;
473     } else if (ed)
474             ed->ignored = FALSE;
475
476     /* deal with it in the kernel */
477
478     if (menu_frame_visible &&
479         (e->type == EnterNotify || e->type == LeaveNotify))
480     {
481         /* crossing events for menu */
482         event_handle_menu(e);
483     } else if (e->type == FocusIn) {
484         if (client &&
485             e->xfocus.detail == NotifyInferior)
486         {
487             ob_debug_type(OB_DEBUG_FOCUS,
488                           "Focus went to the frame window");
489
490             focus_left_screen = FALSE;
491
492             focus_fallback(FALSE, config_focus_under_mouse, TRUE);
493
494             /* We don't get a FocusOut for this case, because it's just moving
495                from our Inferior up to us. This happens when iconifying a
496                window with RevertToParent focus */
497             frame_adjust_focus(client->frame, FALSE);
498             /* focus_set_client(NULL) has already been called */
499             client_calc_layer(client);
500         }
501         if (e->xfocus.detail == NotifyPointerRoot ||
502             e->xfocus.detail == NotifyDetailNone ||
503             e->xfocus.detail == NotifyInferior ||
504             e->xfocus.detail == NotifyNonlinear)
505         {
506             XEvent ce;
507
508             ob_debug_type(OB_DEBUG_FOCUS,
509                           "Focus went to root or pointer root/none\n");
510
511             if (e->xfocus.detail == NotifyInferior ||
512                 e->xfocus.detail == NotifyNonlinear)
513             {
514                 focus_left_screen = FALSE;
515             }
516
517             /* If another FocusIn is in the queue then don't fallback yet. This
518                fixes the fun case of:
519                window map -> send focusin
520                window unmap -> get focusout
521                window map -> send focusin
522                get first focus out -> fall back to something (new window
523                  hasn't received focus yet, so something else) -> send focusin
524                which means the "something else" is the last thing to get a
525                focusin sent to it, so the new window doesn't end up with focus.
526
527                But if the other focus in is something like PointerRoot then we
528                still want to fall back.
529             */
530             if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client,
531                               NULL))
532             {
533                 XPutBackEvent(ob_display, &ce);
534                 ob_debug_type(OB_DEBUG_FOCUS,
535                               "  but another FocusIn is coming\n");
536             } else {
537                 /* Focus has been reverted.
538
539                    FocusOut events come after UnmapNotify, so we don't need to
540                    worry about focusing an invalid window
541                 */
542
543                 if (!focus_left_screen)
544                     focus_fallback(FALSE, config_focus_under_mouse, TRUE);
545             }
546         }
547         else if (!client)
548         {
549             ob_debug_type(OB_DEBUG_FOCUS,
550                           "Focus went to a window that is already gone\n");
551
552             /* If you send focus to a window and then it disappears, you can
553                get the FocusIn for it, after it is unmanaged.
554                Just wait for the next FocusOut/FocusIn pair, but make note that
555                the window that was focused no longer is. */
556             focus_set_client(NULL);
557         }
558         else if (client != focus_client) {
559             focus_left_screen = FALSE;
560             frame_adjust_focus(client->frame, TRUE);
561             focus_set_client(client);
562             client_calc_layer(client);
563             client_bring_helper_windows(client);
564         }
565     } else if (e->type == FocusOut) {
566         XEvent ce;
567
568         /* Look for the followup FocusIn */
569         if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) {
570             /* There is no FocusIn, this means focus went to a window that
571                is not being managed, or a window on another screen. */
572             Window win, root;
573             gint i;
574             guint u;
575             xerror_set_ignore(TRUE);
576             if (XGetInputFocus(ob_display, &win, &i) != 0 &&
577                 XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 &&
578                 root != RootWindow(ob_display, ob_screen))
579             {
580                 ob_debug_type(OB_DEBUG_FOCUS,
581                               "Focus went to another screen !\n");
582                 focus_left_screen = TRUE;
583             }
584             else
585                 ob_debug_type(OB_DEBUG_FOCUS,
586                               "Focus went to a black hole !\n");
587             xerror_set_ignore(FALSE);
588             /* nothing is focused */
589             focus_set_client(NULL);
590         } else {
591             /* Focus moved, so process the FocusIn event */
592             ObEventData ed = { .ignored = FALSE };
593             event_process(&ce, &ed);
594             if (ed.ignored) {
595                 /* The FocusIn was ignored, this means it was on a window
596                    that isn't a client. */
597                 ob_debug_type(OB_DEBUG_FOCUS,
598                               "Focus went to an unmanaged window 0x%x !\n",
599                               ce.xfocus.window);
600                 focus_fallback(TRUE, config_focus_under_mouse, TRUE);
601             }
602         }
603
604         if (client && client != focus_client) {
605             frame_adjust_focus(client->frame, FALSE);
606             /* focus_set_client(NULL) has already been called in this
607                section or by focus_fallback */
608             client_calc_layer(client);
609         }
610     }
611     else if (client)
612         event_handle_client(client, e);
613     else if (dockapp)
614         event_handle_dockapp(dockapp, e);
615     else if (dock)
616         event_handle_dock(dock, e);
617     else if (window == RootWindow(ob_display, ob_screen))
618         event_handle_root(e);
619     else if (e->type == MapRequest)
620         client_manage(window);
621     else if (e->type == ClientMessage) {
622         /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for
623            windows that are not managed yet. */
624         if (e->xclient.message_type == prop_atoms.net_request_frame_extents) {
625             /* Pretend to manage the client, getting information used to
626                determine its decorations */
627             ObClient *c = client_fake_manage(e->xclient.window);
628             gulong vals[4];
629
630             /* set the frame extents on the window */
631             vals[0] = c->frame->size.left;
632             vals[1] = c->frame->size.right;
633             vals[2] = c->frame->size.top;
634             vals[3] = c->frame->size.bottom;
635             PROP_SETA32(e->xclient.window, net_frame_extents,
636                         cardinal, vals, 4);
637
638             /* Free the pretend client */
639             client_fake_unmanage(c);
640         }
641     }
642     else if (e->type == ConfigureRequest) {
643         /* unhandled configure requests must be used to configure the
644            window directly */
645         XWindowChanges xwc;
646
647         xwc.x = e->xconfigurerequest.x;
648         xwc.y = e->xconfigurerequest.y;
649         xwc.width = e->xconfigurerequest.width;
650         xwc.height = e->xconfigurerequest.height;
651         xwc.border_width = e->xconfigurerequest.border_width;
652         xwc.sibling = e->xconfigurerequest.above;
653         xwc.stack_mode = e->xconfigurerequest.detail;
654
655         /* we are not to be held responsible if someone sends us an
656            invalid request! */
657         xerror_set_ignore(TRUE);
658         XConfigureWindow(ob_display, window,
659                          e->xconfigurerequest.value_mask, &xwc);
660         xerror_set_ignore(FALSE);
661     }
662 #ifdef SYNC
663     else if (extensions_sync &&
664         e->type == extensions_sync_event_basep + XSyncAlarmNotify)
665     {
666         XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e;
667         if (se->alarm == moveresize_alarm && moveresize_in_progress)
668             moveresize_event(e);
669     }
670 #endif
671
672     if (e->type == ButtonPress || e->type == ButtonRelease ||
673         e->type == MotionNotify || e->type == KeyPress ||
674         e->type == KeyRelease)
675     {
676         event_handle_user_input(client, e);
677     }
678
679     /* if something happens and it's not from an XEvent, then we don't know
680        the time */
681     event_curtime = CurrentTime;
682 }
683
684 static void event_handle_root(XEvent *e)
685 {
686     Atom msgtype;
687
688     switch(e->type) {
689     case SelectionClear:
690         ob_debug("Another WM has requested to replace us. Exiting.\n");
691         ob_exit_replace();
692         break;
693
694     case ClientMessage:
695         if (e->xclient.format != 32) break;
696
697         msgtype = e->xclient.message_type;
698         if (msgtype == prop_atoms.net_current_desktop) {
699             guint d = e->xclient.data.l[0];
700             if (d < screen_num_desktops) {
701                 event_curtime = e->xclient.data.l[1];
702                 if (event_curtime == 0)
703                     ob_debug_type(OB_DEBUG_APP_BUGS,
704                                   "_NET_CURRENT_DESKTOP message is missing "
705                                   "a timestamp\n");
706                 screen_set_desktop(d, TRUE);
707             }
708         } else if (msgtype == prop_atoms.net_number_of_desktops) {
709             guint d = e->xclient.data.l[0];
710             if (d > 0 && d <= 1000)
711                 screen_set_num_desktops(d);
712         } else if (msgtype == prop_atoms.net_showing_desktop) {
713             screen_show_desktop(e->xclient.data.l[0] != 0, NULL);
714         } else if (msgtype == prop_atoms.ob_control) {
715             ob_debug("OB_CONTROL: %d\n", e->xclient.data.l[0]);
716             if (e->xclient.data.l[0] == 1)
717                 ob_reconfigure();
718             else if (e->xclient.data.l[0] == 2)
719                 ob_restart();
720         }
721         break;
722     case PropertyNotify:
723         if (e->xproperty.atom == prop_atoms.net_desktop_names) {
724             ob_debug("UPDATE DESKTOP NAMES\n");
725             screen_update_desktop_names();
726         }
727         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
728             screen_update_layout();
729         break;
730     case ConfigureNotify:
731 #ifdef XRANDR
732         XRRUpdateConfiguration(e);
733 #endif
734         screen_resize();
735         break;
736     default:
737         ;
738     }
739 }
740
741 void event_enter_client(ObClient *client)
742 {
743     g_assert(config_focus_follow);
744
745     if (client_enter_focusable(client) && client_can_focus(client)) {
746         if (config_focus_delay) {
747             ObFocusDelayData *data;
748
749             ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
750
751             data = g_new(ObFocusDelayData, 1);
752             data->client = client;
753             data->time = event_curtime;
754
755             ob_main_loop_timeout_add(ob_main_loop,
756                                      config_focus_delay * 1000,
757                                      focus_delay_func,
758                                      data, focus_delay_cmp, focus_delay_dest);
759         } else {
760             ObFocusDelayData data;
761             data.client = client;
762             data.time = event_curtime;
763             focus_delay_func(&data);
764         }
765     }
766 }
767
768 static void event_handle_client(ObClient *client, XEvent *e)
769 {
770     XEvent ce;
771     Atom msgtype;
772     ObFrameContext con;
773     static gint px = -1, py = -1;
774     static guint pb = 0;
775
776     switch (e->type) {
777     case ButtonPress:
778         /* save where the press occured for the first button pressed */
779         if (!pb) {
780             pb = e->xbutton.button;
781             px = e->xbutton.x;
782             py = e->xbutton.y;
783         }
784     case ButtonRelease:
785         /* Wheel buttons don't draw because they are an instant click, so it
786            is a waste of resources to go drawing it.
787            if the user is doing an intereactive thing, or has a menu open then
788            the mouse is grabbed (possibly) and if we get these events we don't
789            want to deal with them
790         */
791         if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
792             !grab_on_keyboard())
793         {
794             /* use where the press occured */
795             con = frame_context(client, e->xbutton.window, px, py);
796             con = mouse_button_frame_context(con, e->xbutton.button,
797                                              e->xbutton.state);
798
799             if (e->type == ButtonRelease && e->xbutton.button == pb)
800                 pb = 0, px = py = -1;
801
802             switch (con) {
803             case OB_FRAME_CONTEXT_MAXIMIZE:
804                 client->frame->max_press = (e->type == ButtonPress);
805                 frame_adjust_state(client->frame);
806                 break;
807             case OB_FRAME_CONTEXT_CLOSE:
808                 client->frame->close_press = (e->type == ButtonPress);
809                 frame_adjust_state(client->frame);
810                 break;
811             case OB_FRAME_CONTEXT_ICONIFY:
812                 client->frame->iconify_press = (e->type == ButtonPress);
813                 frame_adjust_state(client->frame);
814                 break;
815             case OB_FRAME_CONTEXT_ALLDESKTOPS:
816                 client->frame->desk_press = (e->type == ButtonPress);
817                 frame_adjust_state(client->frame);
818                 break;
819             case OB_FRAME_CONTEXT_SHADE:
820                 client->frame->shade_press = (e->type == ButtonPress);
821                 frame_adjust_state(client->frame);
822                 break;
823             default:
824                 /* nothing changes with clicks for any other contexts */
825                 break;
826             }
827         }
828         break;
829     case MotionNotify:
830         /* when there is a grab on the pointer, we won't get enter/leave
831            notifies, but we still get motion events */
832         if (grab_on_pointer()) break;
833
834         con = frame_context(client, e->xmotion.window,
835                             e->xmotion.x, e->xmotion.y);
836         switch (con) {
837         case OB_FRAME_CONTEXT_TITLEBAR:
838         case OB_FRAME_CONTEXT_TLCORNER:
839         case OB_FRAME_CONTEXT_TRCORNER:
840             /* we've left the button area inside the titlebar */
841             if (client->frame->max_hover || client->frame->desk_hover ||
842                 client->frame->shade_hover || client->frame->iconify_hover ||
843                 client->frame->close_hover)
844             {
845                 client->frame->max_hover = FALSE;
846                 client->frame->desk_hover = FALSE;
847                 client->frame->shade_hover = FALSE;
848                 client->frame->iconify_hover = FALSE;
849                 client->frame->close_hover = FALSE;
850                 frame_adjust_state(client->frame);
851             }
852             break;
853         case OB_FRAME_CONTEXT_MAXIMIZE:
854             if (!client->frame->max_hover) {
855                 client->frame->max_hover = TRUE;
856                 frame_adjust_state(client->frame);
857             }
858             break;
859         case OB_FRAME_CONTEXT_ALLDESKTOPS:
860             if (!client->frame->desk_hover) {
861                 client->frame->desk_hover = TRUE;
862                 frame_adjust_state(client->frame);
863             }
864             break;
865         case OB_FRAME_CONTEXT_SHADE:
866             if (!client->frame->shade_hover) {
867                 client->frame->shade_hover = TRUE;
868                 frame_adjust_state(client->frame);
869             }
870             break;
871         case OB_FRAME_CONTEXT_ICONIFY:
872             if (!client->frame->iconify_hover) {
873                 client->frame->iconify_hover = TRUE;
874                 frame_adjust_state(client->frame);
875             }
876             break;
877         case OB_FRAME_CONTEXT_CLOSE:
878             if (!client->frame->close_hover) {
879                 client->frame->close_hover = TRUE;
880                 frame_adjust_state(client->frame);
881             }
882             break;
883         default:
884             break;
885         }
886         break;
887     case LeaveNotify:
888         con = frame_context(client, e->xcrossing.window,
889                             e->xcrossing.x, e->xcrossing.y);
890         switch (con) {
891         case OB_FRAME_CONTEXT_TITLEBAR:
892         case OB_FRAME_CONTEXT_TLCORNER:
893         case OB_FRAME_CONTEXT_TRCORNER:
894             /* we've left the button area inside the titlebar */
895             if (client->frame->max_hover || client->frame->desk_hover ||
896                 client->frame->shade_hover || client->frame->iconify_hover ||
897                 client->frame->close_hover)
898             {
899                 client->frame->max_hover = FALSE;
900                 client->frame->desk_hover = FALSE;
901                 client->frame->shade_hover = FALSE;
902                 client->frame->iconify_hover = FALSE;
903                 client->frame->close_hover = FALSE;
904                 frame_adjust_state(client->frame);
905             }
906             break;
907         case OB_FRAME_CONTEXT_MAXIMIZE:
908             client->frame->max_hover = FALSE;
909             frame_adjust_state(client->frame);
910             break;
911         case OB_FRAME_CONTEXT_ALLDESKTOPS:
912             client->frame->desk_hover = FALSE;
913             frame_adjust_state(client->frame);
914             break;
915         case OB_FRAME_CONTEXT_SHADE:
916             client->frame->shade_hover = FALSE;
917             frame_adjust_state(client->frame);
918             break;
919         case OB_FRAME_CONTEXT_ICONIFY:
920             client->frame->iconify_hover = FALSE;
921             frame_adjust_state(client->frame);
922             break;
923         case OB_FRAME_CONTEXT_CLOSE:
924             client->frame->close_hover = FALSE;
925             frame_adjust_state(client->frame);
926             break;
927         case OB_FRAME_CONTEXT_FRAME:
928             /* When the mouse leaves an animating window, don't use the
929                corresponding enter events. Pretend like the animating window
930                doesn't even exist..! */
931             if (frame_iconify_animating(client->frame))
932                 event_end_ignore_all_enters(event_start_ignore_all_enters());
933
934             ob_debug_type(OB_DEBUG_FOCUS,
935                           "%sNotify mode %d detail %d on %lx\n",
936                           (e->type == EnterNotify ? "Enter" : "Leave"),
937                           e->xcrossing.mode,
938                           e->xcrossing.detail, (client?client->window:0));
939             if (grab_on_keyboard())
940                 break;
941             if (config_focus_follow && config_focus_delay &&
942                 /* leave inferior events can happen when the mouse goes onto
943                    the window's border and then into the window before the
944                    delay is up */
945                 e->xcrossing.detail != NotifyInferior)
946             {
947                 ob_main_loop_timeout_remove_data(ob_main_loop,
948                                                  focus_delay_func,
949                                                  client, FALSE);
950             }
951             break;
952         default:
953             break;
954         }
955         break;
956     case EnterNotify:
957     {
958         con = frame_context(client, e->xcrossing.window,
959                             e->xcrossing.x, e->xcrossing.y);
960         switch (con) {
961         case OB_FRAME_CONTEXT_MAXIMIZE:
962             client->frame->max_hover = TRUE;
963             frame_adjust_state(client->frame);
964             break;
965         case OB_FRAME_CONTEXT_ALLDESKTOPS:
966             client->frame->desk_hover = TRUE;
967             frame_adjust_state(client->frame);
968             break;
969         case OB_FRAME_CONTEXT_SHADE:
970             client->frame->shade_hover = TRUE;
971             frame_adjust_state(client->frame);
972             break;
973         case OB_FRAME_CONTEXT_ICONIFY:
974             client->frame->iconify_hover = TRUE;
975             frame_adjust_state(client->frame);
976             break;
977         case OB_FRAME_CONTEXT_CLOSE:
978             client->frame->close_hover = TRUE;
979             frame_adjust_state(client->frame);
980             break;
981         case OB_FRAME_CONTEXT_FRAME:
982             if (grab_on_keyboard())
983                 break;
984             if (e->xcrossing.mode == NotifyGrab ||
985                 e->xcrossing.mode == NotifyUngrab ||
986                 /*ignore enters when we're already in the window */
987                 e->xcrossing.detail == NotifyInferior ||
988                 is_enter_focus_event_ignored(e))
989             {
990                 ob_debug_type(OB_DEBUG_FOCUS,
991                               "%sNotify mode %d detail %d on %lx IGNORED\n",
992                               (e->type == EnterNotify ? "Enter" : "Leave"),
993                               e->xcrossing.mode,
994                               e->xcrossing.detail, client?client->window:0);
995             }
996             else {
997                 ob_debug_type(OB_DEBUG_FOCUS,
998                               "%sNotify mode %d detail %d on %lx, "
999                               "focusing window\n",
1000                               (e->type == EnterNotify ? "Enter" : "Leave"),
1001                               e->xcrossing.mode,
1002                               e->xcrossing.detail, (client?client->window:0));
1003                 if (config_focus_follow)
1004                     event_enter_client(client);
1005             }
1006             break;
1007         default:
1008             break;
1009         }
1010         break;
1011     }
1012     case ConfigureRequest:
1013     {
1014         /* dont compress these unless you're going to watch for property
1015            notifies in between (these can change what the configure would
1016            do to the window).
1017            also you can't compress stacking events
1018         */
1019
1020         gint x, y, w, h;
1021         gboolean move = FALSE;
1022         gboolean resize = FALSE;
1023
1024         /* get the current area */
1025         RECT_TO_DIMS(client->area, x, y, w, h);
1026
1027         ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
1028                  "visibile %d\n"
1029                  "                     x %d y %d w %d h %d b %d\n",
1030                  client->title,
1031                  screen_desktop, client->wmstate, client->frame->visible,
1032                  x, y, w, h, client->border_width);
1033
1034         if (e->xconfigurerequest.value_mask & CWBorderWidth)
1035             if (client->border_width != e->xconfigurerequest.border_width) {
1036                 client->border_width = e->xconfigurerequest.border_width;
1037
1038                 /* if the border width is changing then that is the same
1039                    as requesting a resize, but we don't actually change
1040                    the client's border, so it will change their root
1041                    coordiantes (since they include the border width) and
1042                    we need to a notify then */
1043                 move = TRUE;
1044             }
1045
1046
1047         if (e->xconfigurerequest.value_mask & CWStackMode) {
1048             ObClient *sibling = NULL;
1049             gulong ignore_start;
1050             gboolean ok = TRUE;
1051
1052             /* get the sibling */
1053             if (e->xconfigurerequest.value_mask & CWSibling) {
1054                 ObWindow *win;
1055                 win = g_hash_table_lookup(window_map,
1056                                           &e->xconfigurerequest.above);
1057                 if (win && WINDOW_IS_CLIENT(win) &&
1058                     WINDOW_AS_CLIENT(win) != client)
1059                 {
1060                     sibling = WINDOW_AS_CLIENT(win);
1061                 }
1062                 else
1063                     /* an invalid sibling was specified so don't restack at
1064                        all, it won't make sense no matter what we do */
1065                     ok = FALSE;
1066             }
1067
1068             if (ok) {
1069                 if (!config_focus_under_mouse)
1070                     ignore_start = event_start_ignore_all_enters();
1071                 stacking_restack_request(client, sibling,
1072                                          e->xconfigurerequest.detail);
1073                 if (!config_focus_under_mouse)
1074                     event_end_ignore_all_enters(ignore_start);
1075             }
1076
1077             /* 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         {
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             ob_debug("Granting ConfigureRequest x %d y %d w %d h %d\n",
1167                      x, y, w, h);
1168             client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
1169         }
1170         break;
1171     }
1172     case UnmapNotify:
1173         if (client->ignore_unmaps) {
1174             client->ignore_unmaps--;
1175             break;
1176         }
1177         ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
1178                  "ignores left %d\n",
1179                  client->window, e->xunmap.event, e->xunmap.from_configure,
1180                  client->ignore_unmaps);
1181         client_unmanage(client);
1182         break;
1183     case DestroyNotify:
1184         ob_debug("DestroyNotify for window 0x%x\n", client->window);
1185         client_unmanage(client);
1186         break;
1187     case ReparentNotify:
1188         /* this is when the client is first taken captive in the frame */
1189         if (e->xreparent.parent == client->frame->window) break;
1190
1191         /*
1192           This event is quite rare and is usually handled in unmapHandler.
1193           However, if the window is unmapped when the reparent event occurs,
1194           the window manager never sees it because an unmap event is not sent
1195           to an already unmapped window.
1196         */
1197
1198         /* we don't want the reparent event, put it back on the stack for the
1199            X server to deal with after we unmanage the window */
1200         XPutBackEvent(ob_display, e);
1201
1202         ob_debug("ReparentNotify for window 0x%x\n", client->window);
1203         client_unmanage(client);
1204         break;
1205     case MapRequest:
1206         ob_debug("MapRequest for 0x%lx\n", client->window);
1207         if (!client->iconic) break; /* this normally doesn't happen, but if it
1208                                        does, we don't want it!
1209                                        it can happen now when the window is on
1210                                        another desktop, but we still don't
1211                                        want it! */
1212         client_activate(client, FALSE, TRUE, TRUE, TRUE);
1213         break;
1214     case ClientMessage:
1215         /* validate cuz we query stuff off the client here */
1216         if (!client_validate(client)) break;
1217
1218         if (e->xclient.format != 32) return;
1219
1220         msgtype = e->xclient.message_type;
1221         if (msgtype == prop_atoms.wm_change_state) {
1222             /* compress changes into a single change */
1223             while (XCheckTypedWindowEvent(ob_display, client->window,
1224                                           e->type, &ce)) {
1225                 /* XXX: it would be nice to compress ALL messages of a
1226                    type, not just messages in a row without other
1227                    message types between. */
1228                 if (ce.xclient.message_type != msgtype) {
1229                     XPutBackEvent(ob_display, &ce);
1230                     break;
1231                 }
1232                 e->xclient = ce.xclient;
1233             }
1234             client_set_wm_state(client, e->xclient.data.l[0]);
1235         } else if (msgtype == prop_atoms.net_wm_desktop) {
1236             /* compress changes into a single change */
1237             while (XCheckTypedWindowEvent(ob_display, client->window,
1238                                           e->type, &ce)) {
1239                 /* XXX: it would be nice to compress ALL messages of a
1240                    type, not just messages in a row without other
1241                    message types between. */
1242                 if (ce.xclient.message_type != msgtype) {
1243                     XPutBackEvent(ob_display, &ce);
1244                     break;
1245                 }
1246                 e->xclient = ce.xclient;
1247             }
1248             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
1249                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
1250                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
1251                                    FALSE, FALSE);
1252         } else if (msgtype == prop_atoms.net_wm_state) {
1253             gulong ignore_start;
1254
1255             /* can't compress these */
1256             ob_debug("net_wm_state %s %ld %ld for 0x%lx\n",
1257                      (e->xclient.data.l[0] == 0 ? "Remove" :
1258                       e->xclient.data.l[0] == 1 ? "Add" :
1259                       e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
1260                      e->xclient.data.l[1], e->xclient.data.l[2],
1261                      client->window);
1262
1263             /* ignore enter events caused by these like ob actions do */
1264             if (!config_focus_under_mouse)
1265                 ignore_start = event_start_ignore_all_enters();
1266             client_set_state(client, e->xclient.data.l[0],
1267                              e->xclient.data.l[1], e->xclient.data.l[2]);
1268             if (!config_focus_under_mouse)
1269                 event_end_ignore_all_enters(ignore_start);
1270         } else if (msgtype == prop_atoms.net_close_window) {
1271             ob_debug("net_close_window for 0x%lx\n", client->window);
1272             client_close(client);
1273         } else if (msgtype == prop_atoms.net_active_window) {
1274             ob_debug("net_active_window for 0x%lx source=%s\n",
1275                      client->window,
1276                      (e->xclient.data.l[0] == 0 ? "unknown" :
1277                       (e->xclient.data.l[0] == 1 ? "application" :
1278                        (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
1279             /* XXX make use of data.l[2] !? */
1280             if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
1281                 /* don't use the user's timestamp for client_focus, cuz if it's
1282                    an old broken timestamp (happens all the time) then focus
1283                    won't move even though we're trying to move it
1284                   event_curtime = e->xclient.data.l[1];*/
1285                 if (e->xclient.data.l[1] == 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, TRUE, TRUE,
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, FALSE);
1378
1379             client->gravity = ograv;
1380         } else if (msgtype == prop_atoms.net_restack_window) {
1381             if (e->xclient.data.l[0] != 2) {
1382                 ob_debug_type(OB_DEBUG_APP_BUGS,
1383                               "_NET_RESTACK_WINDOW sent for window %s with "
1384                               "invalid source indication %ld\n",
1385                               client->title, e->xclient.data.l[0]);
1386             } else {
1387                 ObClient *sibling = NULL;
1388                 if (e->xclient.data.l[1]) {
1389                     ObWindow *win = g_hash_table_lookup
1390                         (window_map, &e->xclient.data.l[1]);
1391                     if (WINDOW_IS_CLIENT(win) &&
1392                         WINDOW_AS_CLIENT(win) != client)
1393                     {
1394                         sibling = WINDOW_AS_CLIENT(win);
1395                     }
1396                     if (sibling == NULL)
1397                         ob_debug_type(OB_DEBUG_APP_BUGS,
1398                                       "_NET_RESTACK_WINDOW sent for window %s "
1399                                       "with invalid sibling 0x%x\n",
1400                                  client->title, e->xclient.data.l[1]);
1401                 }
1402                 if (e->xclient.data.l[2] == Below ||
1403                     e->xclient.data.l[2] == BottomIf ||
1404                     e->xclient.data.l[2] == Above ||
1405                     e->xclient.data.l[2] == TopIf ||
1406                     e->xclient.data.l[2] == Opposite)
1407                 {
1408                     gulong ignore_start;
1409
1410                     if (!config_focus_under_mouse)
1411                         ignore_start = event_start_ignore_all_enters();
1412                     /* just raise, don't activate */
1413                     stacking_restack_request(client, sibling,
1414                                              e->xclient.data.l[2]);
1415                     if (!config_focus_under_mouse)
1416                         event_end_ignore_all_enters(ignore_start);
1417
1418                     /* send a synthetic ConfigureNotify, cuz this is supposed
1419                        to be like a ConfigureRequest. */
1420                     client_reconfigure(client, TRUE);
1421                 } else
1422                     ob_debug_type(OB_DEBUG_APP_BUGS,
1423                                   "_NET_RESTACK_WINDOW sent for window %s "
1424                                   "with invalid detail %d\n",
1425                                   client->title, e->xclient.data.l[2]);
1426             }
1427         }
1428         break;
1429     case PropertyNotify:
1430         /* validate cuz we query stuff off the client here */
1431         if (!client_validate(client)) break;
1432
1433         /* compress changes to a single property into a single change */
1434         while (XCheckTypedWindowEvent(ob_display, client->window,
1435                                       e->type, &ce)) {
1436             Atom a, b;
1437
1438             /* XXX: it would be nice to compress ALL changes to a property,
1439                not just changes in a row without other props between. */
1440
1441             a = ce.xproperty.atom;
1442             b = e->xproperty.atom;
1443
1444             if (a == b)
1445                 continue;
1446             if ((a == prop_atoms.net_wm_name ||
1447                  a == prop_atoms.wm_name ||
1448                  a == prop_atoms.net_wm_icon_name ||
1449                  a == prop_atoms.wm_icon_name)
1450                 &&
1451                 (b == prop_atoms.net_wm_name ||
1452                  b == prop_atoms.wm_name ||
1453                  b == prop_atoms.net_wm_icon_name ||
1454                  b == prop_atoms.wm_icon_name)) {
1455                 continue;
1456             }
1457             if (a == prop_atoms.net_wm_icon &&
1458                 b == prop_atoms.net_wm_icon)
1459                 continue;
1460
1461             XPutBackEvent(ob_display, &ce);
1462             break;
1463         }
1464
1465         msgtype = e->xproperty.atom;
1466         if (msgtype == XA_WM_NORMAL_HINTS) {
1467             ob_debug("Update NORMAL hints\n");
1468             client_update_normal_hints(client);
1469             /* normal hints can make a window non-resizable */
1470             client_setup_decor_and_functions(client, FALSE);
1471
1472             /* make sure the client's sizes are within its bounds, but only
1473                reconfigure the window if it needs to. emacs will update its
1474                normal hints every time it receives a conigurenotify */
1475             client_reconfigure(client, FALSE);
1476         } else if (msgtype == XA_WM_HINTS) {
1477             client_update_wmhints(client);
1478         } else if (msgtype == XA_WM_TRANSIENT_FOR) {
1479             client_update_transient_for(client);
1480             client_get_type_and_transientness(client);
1481             /* type may have changed, so update the layer */
1482             client_calc_layer(client);
1483             client_setup_decor_and_functions(client, TRUE);
1484         } else if (msgtype == prop_atoms.net_wm_name ||
1485                    msgtype == prop_atoms.wm_name ||
1486                    msgtype == prop_atoms.net_wm_icon_name ||
1487                    msgtype == prop_atoms.wm_icon_name) {
1488             client_update_title(client);
1489         } else if (msgtype == prop_atoms.wm_protocols) {
1490             client_update_protocols(client);
1491             client_setup_decor_and_functions(client, TRUE);
1492         }
1493         else if (msgtype == prop_atoms.net_wm_strut) {
1494             client_update_strut(client);
1495         }
1496         else if (msgtype == prop_atoms.net_wm_strut_partial) {
1497             client_update_strut(client);
1498         }
1499         else if (msgtype == prop_atoms.net_wm_icon) {
1500             client_update_icons(client);
1501         }
1502         else if (msgtype == prop_atoms.net_wm_icon_geometry) {
1503             client_update_icon_geometry(client);
1504         }
1505         else if (msgtype == prop_atoms.net_wm_user_time) {
1506             guint32 t;
1507             if (PROP_GET32(client->window, net_wm_user_time, cardinal, &t) &&
1508                 t && !event_time_after(t, e->xproperty.time) &&
1509                 (!event_last_user_time ||
1510                  event_time_after(t, event_last_user_time)))
1511             {
1512                 event_last_user_time = t;
1513             }
1514         }
1515 #ifdef SYNC
1516         else if (msgtype == prop_atoms.net_wm_sync_request_counter) {
1517             client_update_sync_request_counter(client);
1518         }
1519 #endif
1520         break;
1521     case ColormapNotify:
1522         client_update_colormap(client, e->xcolormap.colormap);
1523         break;
1524     default:
1525         ;
1526 #ifdef SHAPE
1527         if (extensions_shape && e->type == extensions_shape_event_basep) {
1528             client->shaped = ((XShapeEvent*)e)->shaped;
1529             frame_adjust_shape(client->frame);
1530         }
1531 #endif
1532     }
1533 }
1534
1535 static void event_handle_dock(ObDock *s, XEvent *e)
1536 {
1537     switch (e->type) {
1538     case ButtonPress:
1539         if (e->xbutton.button == 1)
1540             stacking_raise(DOCK_AS_WINDOW(s));
1541         else if (e->xbutton.button == 2)
1542             stacking_lower(DOCK_AS_WINDOW(s));
1543         break;
1544     case EnterNotify:
1545         dock_hide(FALSE);
1546         break;
1547     case LeaveNotify:
1548         /* don't hide when moving into a dock app */
1549         if (e->xcrossing.detail != NotifyInferior)
1550             dock_hide(TRUE);
1551         break;
1552     }
1553 }
1554
1555 static void event_handle_dockapp(ObDockApp *app, XEvent *e)
1556 {
1557     switch (e->type) {
1558     case MotionNotify:
1559         dock_app_drag(app, &e->xmotion);
1560         break;
1561     case UnmapNotify:
1562         if (app->ignore_unmaps) {
1563             app->ignore_unmaps--;
1564             break;
1565         }
1566         dock_remove(app, TRUE);
1567         break;
1568     case DestroyNotify:
1569         dock_remove(app, FALSE);
1570         break;
1571     case ReparentNotify:
1572         dock_remove(app, FALSE);
1573         break;
1574     case ConfigureNotify:
1575         dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
1576         break;
1577     }
1578 }
1579
1580 static ObMenuFrame* find_active_menu()
1581 {
1582     GList *it;
1583     ObMenuFrame *ret = NULL;
1584
1585     for (it = menu_frame_visible; it; it = g_list_next(it)) {
1586         ret = it->data;
1587         if (ret->selected)
1588             break;
1589         ret = NULL;
1590     }
1591     return ret;
1592 }
1593
1594 static ObMenuFrame* find_active_or_last_menu()
1595 {
1596     ObMenuFrame *ret = NULL;
1597
1598     ret = find_active_menu();
1599     if (!ret && menu_frame_visible)
1600         ret = menu_frame_visible->data;
1601     return ret;
1602 }
1603
1604 static gboolean event_handle_menu_keyboard(XEvent *ev)
1605 {
1606     guint keycode, state;
1607     gunichar unikey;
1608     ObMenuFrame *frame;
1609     gboolean ret = FALSE;
1610
1611     keycode = ev->xkey.keycode;
1612     state = ev->xkey.state;
1613     unikey = translate_unichar(keycode);
1614
1615     frame = find_active_or_last_menu();
1616     if (frame == NULL)
1617         g_assert_not_reached(); /* there is no active menu */
1618
1619     /* Allow control while going thru the menu */
1620     else if (ev->type == KeyPress && (state & ~ControlMask) == 0) {
1621         if (keycode == ob_keycode(OB_KEY_ESCAPE)) {
1622             menu_frame_hide_all();
1623             ret = TRUE;
1624         }
1625
1626         else if (keycode == ob_keycode(OB_KEY_LEFT)) {
1627             /* Left goes to the parent menu */
1628             menu_frame_select(frame, NULL, TRUE);
1629             ret = TRUE;
1630         }
1631
1632         else if (keycode == ob_keycode(OB_KEY_RIGHT)) {
1633             /* Right goes to the selected submenu */
1634             if (frame->child) menu_frame_select_next(frame->child);
1635             ret = TRUE;
1636         }
1637
1638         else if (keycode == ob_keycode(OB_KEY_UP)) {
1639             menu_frame_select_previous(frame);
1640             ret = TRUE;
1641         }
1642
1643         else if (keycode == ob_keycode(OB_KEY_DOWN)) {
1644             menu_frame_select_next(frame);
1645             ret = TRUE;
1646         }
1647     }
1648
1649     /* Use KeyRelease events for running things so that the key release doesn't
1650        get sent to the focused application.
1651
1652        Allow ControlMask only, and don't bother if the menu is empty */
1653     else if (ev->type == KeyRelease && (state & ~ControlMask) == 0 &&
1654              frame->entries)
1655     {
1656         if (keycode == ob_keycode(OB_KEY_RETURN)) {
1657             /* Enter runs the active item or goes into the submenu.
1658                Control-Enter runs it without closing the menu. */
1659             if (frame->child)
1660                 menu_frame_select_next(frame->child);
1661             else if (frame->selected)
1662                 menu_entry_frame_execute(frame->selected, state);
1663
1664             ret = TRUE;
1665         }
1666
1667         /* keyboard accelerator shortcuts. (if it was a valid key) */
1668         else if (unikey != 0) {
1669             GList *start;
1670             GList *it;
1671             ObMenuEntryFrame *found = NULL;
1672             guint num_found = 0;
1673
1674             /* start after the selected one */
1675             start = frame->entries;
1676             if (frame->selected) {
1677                 for (it = start; frame->selected != it->data;
1678                      it = g_list_next(it))
1679                     g_assert(it != NULL); /* nothing was selected? */
1680                 /* next with wraparound */
1681                 start = g_list_next(it);
1682                 if (start == NULL) start = frame->entries;
1683             }
1684
1685             it = start;
1686             do {
1687                 ObMenuEntryFrame *e = it->data;
1688                 gunichar entrykey = 0;
1689
1690                 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1691                     entrykey = e->entry->data.normal.shortcut;
1692                 else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1693                     entrykey = e->entry->data.submenu.submenu->shortcut;
1694
1695                 if (unikey == entrykey) {
1696                     if (found == NULL) found = e;
1697                     ++num_found;
1698                 }
1699
1700                 /* next with wraparound */
1701                 it = g_list_next(it);
1702                 if (it == NULL) it = frame->entries;
1703             } while (it != start);
1704
1705             if (found) {
1706                 if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1707                     num_found == 1)
1708                 {
1709                     menu_frame_select(frame, found, TRUE);
1710                     usleep(50000); /* highlight the item for a short bit so the
1711                                       user can see what happened */
1712                     menu_entry_frame_execute(found, state);
1713                 } else {
1714                     menu_frame_select(frame, found, TRUE);
1715                     if (num_found == 1)
1716                         menu_frame_select_next(frame->child);
1717                 }
1718
1719                 ret = TRUE;
1720             }
1721         }
1722     }
1723
1724     return ret;
1725 }
1726
1727 static gboolean event_handle_menu(XEvent *ev)
1728 {
1729     ObMenuFrame *f;
1730     ObMenuEntryFrame *e;
1731     gboolean ret = TRUE;
1732
1733     switch (ev->type) {
1734     case ButtonRelease:
1735         if (menu_hide_delay_reached() &&
1736             (ev->xbutton.button < 4 || ev->xbutton.button > 5))
1737         {
1738             if ((e = menu_entry_frame_under(ev->xbutton.x_root,
1739                                             ev->xbutton.y_root)))
1740             {
1741                 menu_frame_select(e->frame, e, TRUE);
1742                 menu_entry_frame_execute(e, ev->xbutton.state);
1743             }
1744             else
1745                 menu_frame_hide_all();
1746         }
1747         break;
1748     case EnterNotify:
1749         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
1750             if (e->ignore_enters)
1751                 --e->ignore_enters;
1752             else if (!(f = find_active_menu()) ||
1753                      f == e->frame ||
1754                      f->parent == e->frame ||
1755                      f->child == e->frame)
1756                 menu_frame_select(e->frame, e, FALSE);
1757         }
1758         break;
1759     case LeaveNotify:
1760         /*ignore leaves when we're already in the window */
1761         if (ev->xcrossing.detail == NotifyInferior)
1762             break;
1763
1764         if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
1765             (f = find_active_menu()) && f->selected == e &&
1766             e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU)
1767         {
1768             menu_frame_select(e->frame, NULL, FALSE);
1769         }
1770         break;
1771     case MotionNotify:
1772         if ((e = menu_entry_frame_under(ev->xmotion.x_root,
1773                                         ev->xmotion.y_root)))
1774             if (!(f = find_active_menu()) ||
1775                 f == e->frame ||
1776                 f->parent == e->frame ||
1777                 f->child == e->frame)
1778                 menu_frame_select(e->frame, e, FALSE);
1779         break;
1780     case KeyPress:
1781     case KeyRelease:
1782         ret = event_handle_menu_keyboard(ev);
1783         break;
1784     }
1785     return ret;
1786 }
1787
1788 static void event_handle_user_input(ObClient *client, XEvent *e)
1789 {
1790     g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
1791              e->type == MotionNotify || e->type == KeyPress ||
1792              e->type == KeyRelease);
1793
1794     if (menu_frame_visible) {
1795         if (event_handle_menu(e))
1796             /* don't use the event if the menu used it, but if the menu
1797                didn't use it and it's a keypress that is bound, it will
1798                close the menu and be used */
1799             return;
1800     }
1801
1802     /* if the keyboard interactive action uses the event then dont
1803        use it for bindings. likewise is moveresize uses the event. */
1804     if (!actions_interactive_input_event(e) && !moveresize_event(e)) {
1805         if (moveresize_in_progress)
1806             /* make further actions work on the client being
1807                moved/resized */
1808             client = moveresize_client;
1809
1810         if (e->type == ButtonPress ||
1811             e->type == ButtonRelease ||
1812             e->type == MotionNotify)
1813         {
1814             /* the frame may not be "visible" but they can still click on it
1815                in the case where it is animating before disappearing */
1816             if (!client || !frame_iconify_animating(client->frame))
1817                 mouse_event(client, e);
1818         } else
1819             keyboard_event((focus_cycle_target ? focus_cycle_target :
1820                             (client ? client : focus_client)), e);
1821     }
1822 }
1823
1824 static void focus_delay_dest(gpointer data)
1825 {
1826     g_free(data);
1827 }
1828
1829 static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2)
1830 {
1831     const ObFocusDelayData *f1 = d1;
1832     return f1->client == d2;
1833 }
1834
1835 static gboolean focus_delay_func(gpointer data)
1836 {
1837     ObFocusDelayData *d = data;
1838     Time old = event_curtime;
1839
1840     /* don't move focus and kill the menu or the move/resize */
1841     if (menu_frame_visible || moveresize_in_progress) return FALSE;
1842
1843     event_curtime = d->time;
1844     if (focus_client != d->client) {
1845         if (client_focus(d->client) && config_focus_raise)
1846             stacking_raise(CLIENT_AS_WINDOW(d->client));
1847     }
1848     event_curtime = old;
1849     return FALSE; /* no repeat */
1850 }
1851
1852 static void focus_delay_client_dest(ObClient *client, gpointer data)
1853 {
1854     ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
1855                                      client, FALSE);
1856 }
1857
1858 void event_halt_focus_delay()
1859 {
1860     ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
1861 }
1862
1863 gulong event_start_ignore_all_enters()
1864 {
1865     XSync(ob_display, FALSE);
1866     return LastKnownRequestProcessed(ob_display);
1867 }
1868
1869 void event_end_ignore_all_enters(gulong start)
1870 {
1871     ObSerialRange *r;
1872
1873     g_assert(start != 0);
1874     XSync(ob_display, FALSE);
1875
1876     r = g_new(ObSerialRange, 1);
1877     r->start = start;
1878     r->end = LastKnownRequestProcessed(ob_display);
1879     ignore_serials = g_slist_prepend(ignore_serials, r);
1880
1881     /* increment the serial so we don't ignore events we weren't meant to */
1882     XSync(ob_display, FALSE);
1883 }
1884
1885 static gboolean is_enter_focus_event_ignored(XEvent *e)
1886 {
1887     GSList *it, *next;
1888
1889     g_assert(e->type == EnterNotify &&
1890              !(e->xcrossing.mode == NotifyGrab ||
1891                e->xcrossing.mode == NotifyUngrab ||
1892                e->xcrossing.detail == NotifyInferior));
1893
1894     for (it = ignore_serials; it; it = next) {
1895         ObSerialRange *r = it->data;
1896
1897         next = g_slist_next(it);
1898
1899         if ((glong)(e->xany.serial - r->end) > 0) {
1900             /* past the end */
1901             ignore_serials = g_slist_delete_link(ignore_serials, it);
1902             g_free(r);
1903         }
1904         else if ((glong)(e->xany.serial - r->start) >= 0)
1905             return TRUE;
1906     }
1907     return FALSE;
1908 }
1909
1910 void event_cancel_all_key_grabs()
1911 {
1912     if (actions_interactive_act_running()) {
1913         actions_interactive_cancel_act();
1914         ob_debug("KILLED interactive action\n");
1915     }
1916     else if (menu_frame_visible) {
1917         menu_frame_hide_all();
1918         ob_debug("KILLED open menus\n");
1919     }
1920     else if (moveresize_in_progress) {
1921         moveresize_end(TRUE);
1922         ob_debug("KILLED interactive moveresize\n");
1923     }
1924     else if (grab_on_keyboard()) {
1925         ungrab_keyboard();
1926         ob_debug("KILLED active grab on keyboard\n");
1927     }
1928     else
1929         ungrab_passive_key();
1930 }
1931
1932 gboolean event_time_after(Time t1, Time t2)
1933 {
1934     g_assert(t1 != CurrentTime);
1935     g_assert(t2 != CurrentTime);
1936
1937     /*
1938       Timestamp values wrap around (after about 49.7 days). The server, given
1939       its current time is represented by timestamp T, always interprets
1940       timestamps from clients by treating half of the timestamp space as being
1941       later in time than T.
1942       - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
1943     */
1944
1945     /* TIME_HALF is half of the number space of a Time type variable */
1946 #define TIME_HALF (Time)(1 << (sizeof(Time)*8-1))
1947
1948     if (t2 >= TIME_HALF)
1949         /* t2 is in the second half so t1 might wrap around and be smaller than
1950            t2 */
1951         return t1 >= t2 || t1 < (t2 + TIME_HALF);
1952     else
1953         /* t2 is in the first half so t1 has to come after it */
1954         return t1 >= t2 && t1 < (t2 + TIME_HALF);
1955 }
1956
1957 Time event_get_server_time()
1958 {
1959     /* Generate a timestamp */
1960     XEvent event;
1961
1962     XChangeProperty(ob_display, screen_support_win,
1963                     prop_atoms.wm_class, prop_atoms.string,
1964                     8, PropModeAppend, NULL, 0);
1965     XWindowEvent(ob_display, screen_support_win, PropertyChangeMask, &event);
1966     return event.xproperty.time;
1967 }