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