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