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