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