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