X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=openbox%2Fevent.c;h=c8356be1744aa1c059eb8e69b4a17c0d017b3e80;hb=b8f207892a11b53365c47b4828254269fca5c8e9;hp=2df720b048381075d12a5159f86e3364376a1874;hpb=896098c961a9a37e10541680637eb582805a9523;p=chaz%2Fopenbox diff --git a/openbox/event.c b/openbox/event.c index 2df720b0..ac0e6ff2 100644 --- a/openbox/event.c +++ b/openbox/event.c @@ -1,7 +1,8 @@ /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- event.c for the Openbox window manager - Copyright (c) 2003 Ben Jansens + Copyright (c) 2006 Mikael Magnusson + Copyright (c) 2003-2007 Dana Jansens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,20 +28,24 @@ #include "config.h" #include "screen.h" #include "frame.h" +#include "grab.h" #include "menu.h" #include "menuframe.h" #include "keyboard.h" +#include "modkeys.h" +#include "propwin.h" #include "mouse.h" #include "mainloop.h" #include "framerender.h" #include "focus.h" +#include "focus_cycle.h" #include "moveresize.h" #include "group.h" #include "stacking.h" #include "extensions.h" +#include "translate.h" #include -#include #include #include @@ -50,6 +55,12 @@ #ifdef HAVE_SIGNAL_H # include #endif +#ifdef HAVE_UNISTD_H +# include /* for usleep() */ +#endif +#ifdef XKB +# include +#endif #ifdef USE_SM #include @@ -60,48 +71,39 @@ typedef struct gboolean ignored; } ObEventData; +typedef struct +{ + ObClient *client; + Time time; +} ObFocusDelayData; + static void event_process(const XEvent *e, gpointer data); static void event_handle_root(XEvent *e); -static void event_handle_menu(XEvent *e); +static gboolean event_handle_menu_keyboard(XEvent *e); +static gboolean event_handle_menu(XEvent *e); static void event_handle_dock(ObDock *s, XEvent *e); static void event_handle_dockapp(ObDockApp *app, XEvent *e); static void event_handle_client(ObClient *c, XEvent *e); -static void event_handle_group(ObGroup *g, XEvent *e); +static void event_handle_user_time_window_clients(GSList *l, XEvent *e); +static void event_handle_user_input(ObClient *client, XEvent *e); +static gboolean is_enter_focus_event_ignored(XEvent *e); +static void focus_delay_dest(gpointer data); +static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2); static gboolean focus_delay_func(gpointer data); -static void focus_delay_client_dest(gpointer data); +static void focus_delay_client_dest(ObClient *client, gpointer data); static gboolean menu_hide_delay_func(gpointer data); -#define INVALID_FOCUSIN(e) ((e)->xfocus.detail == NotifyInferior || \ - (e)->xfocus.detail == NotifyAncestor || \ - (e)->xfocus.detail > NotifyNonlinearVirtual) -#define INVALID_FOCUSOUT(e) ((e)->xfocus.mode == NotifyGrab || \ - (e)->xfocus.detail == NotifyInferior || \ - (e)->xfocus.detail == NotifyAncestor || \ - (e)->xfocus.detail > NotifyNonlinearVirtual) - -Time event_lasttime = 0; - -/*! The value of the mask for the NumLock modifier */ -unsigned int NumLockMask; -/*! The value of the mask for the ScrollLock modifier */ -unsigned int ScrollLockMask; -/*! The key codes for the modifier keys */ -static XModifierKeymap *modmap; -/*! Table of the constant modifier masks */ -static const int mask_table[] = { - ShiftMask, LockMask, ControlMask, Mod1Mask, - Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask -}; -static int mask_table_size; +/* The time for the current event being processed */ +Time event_curtime = CurrentTime; static guint ignore_enter_focus = 0; - static gboolean menu_can_hide; +static gboolean focus_left_screen = FALSE; #ifdef USE_SM -static void ice_handler(int fd, gpointer conn) +static void ice_handler(gint fd, gpointer conn) { Bool b; IceProcessMessages(conn, NULL, &b); @@ -126,38 +128,13 @@ void event_startup(gboolean reconfig) { if (reconfig) return; - mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]); - - /* get lock masks that are defined by the display (not constant) */ - modmap = XGetModifierMapping(ob_display); - g_assert(modmap); - if (modmap && modmap->max_keypermod > 0) { - size_t cnt; - const size_t size = mask_table_size * modmap->max_keypermod; - /* get the values of the keyboard lock modifiers - Note: Caps lock is not retrieved the same way as Scroll and Num - lock since it doesn't need to be. */ - const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock); - const KeyCode scroll_lock = XKeysymToKeycode(ob_display, - XK_Scroll_Lock); - - for (cnt = 0; cnt < size; ++cnt) { - if (! modmap->modifiermap[cnt]) continue; - - if (num_lock == modmap->modifiermap[cnt]) - NumLockMask = mask_table[cnt / modmap->max_keypermod]; - if (scroll_lock == modmap->modifiermap[cnt]) - ScrollLockMask = mask_table[cnt / modmap->max_keypermod]; - } - } - ob_main_loop_x_add(ob_main_loop, event_process, NULL, NULL); #ifdef USE_SM IceAddConnectionWatch(ice_watch, NULL); #endif - client_add_destructor(focus_delay_client_dest); + client_add_destroy_notify(focus_delay_client_dest, NULL); } void event_shutdown(gboolean reconfig) @@ -168,8 +145,7 @@ void event_shutdown(gboolean reconfig) IceRemoveConnectionWatch(ice_watch, NULL); #endif - client_remove_destructor(focus_delay_client_dest); - XFreeModifiermap(modmap); + client_remove_destroy_notify(focus_delay_client_dest); } static Window event_get_window(XEvent *e) @@ -182,29 +158,36 @@ static Window event_get_window(XEvent *e) window = RootWindow(ob_display, ob_screen); break; case MapRequest: - window = e->xmap.window; - break; + window = e->xmap.window; + break; case UnmapNotify: - window = e->xunmap.window; - break; + window = e->xunmap.window; + break; case DestroyNotify: - window = e->xdestroywindow.window; - break; + window = e->xdestroywindow.window; + break; case ConfigureRequest: - window = e->xconfigurerequest.window; - break; + window = e->xconfigurerequest.window; + break; case ConfigureNotify: window = e->xconfigure.window; break; default: #ifdef XKB - if (extensions_xkb && e->type == extensions_xkb_event_basep) { - switch (((XkbAnyEvent*)e)->xkb_type) { - case XkbBellNotify: - window = ((XkbBellNotifyEvent*)e)->window; - default: - window = None; - } + if (extensions_xkb && e->type == extensions_xkb_event_basep) { + switch (((XkbAnyEvent*)e)->xkb_type) { + case XkbBellNotify: + window = ((XkbBellNotifyEvent*)e)->window; + default: + window = None; + } + } else +#endif +#ifdef SYNC + if (extensions_sync && + e->type == extensions_sync_event_basep + XSyncAlarmNotify) + { + window = None; } else #endif window = e->xany.window; @@ -212,82 +195,77 @@ static Window event_get_window(XEvent *e) return window; } -static void event_set_lasttime(XEvent *e) +static void event_set_curtime(XEvent *e) { - Time t = 0; + Time t = CurrentTime; /* grab the lasttime and hack up the state */ switch (e->type) { case ButtonPress: case ButtonRelease: - t = e->xbutton.time; - break; + t = e->xbutton.time; + break; case KeyPress: - t = e->xkey.time; - break; + t = e->xkey.time; + break; case KeyRelease: - t = e->xkey.time; - break; + t = e->xkey.time; + break; case MotionNotify: - t = e->xmotion.time; - break; + t = e->xmotion.time; + break; case PropertyNotify: - t = e->xproperty.time; - break; + t = e->xproperty.time; + break; case EnterNotify: case LeaveNotify: - t = e->xcrossing.time; - break; + t = e->xcrossing.time; + break; default: +#ifdef SYNC + if (extensions_sync && + e->type == extensions_sync_event_basep + XSyncAlarmNotify) + { + t = ((XSyncAlarmNotifyEvent*)e)->time; + } +#endif /* if more event types are anticipated, get their timestamp explicitly */ break; } - if (t > event_lasttime) - event_lasttime = t; + event_curtime = t; } -#define STRIP_MODS(s) \ - s &= ~(LockMask | NumLockMask | ScrollLockMask), \ - /* kill off the Button1Mask etc, only want the modifiers */ \ - s &= (ControlMask | ShiftMask | Mod1Mask | \ - Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \ - static void event_hack_mods(XEvent *e) { - KeyCode *kp; - int i, k; +#ifdef XKB + XkbStateRec xkb_state; +#endif switch (e->type) { case ButtonPress: case ButtonRelease: - STRIP_MODS(e->xbutton.state); - break; + e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state); + break; case KeyPress: - STRIP_MODS(e->xkey.state); - break; + e->xkey.state = modkeys_only_modifier_masks(e->xkey.state); + break; case KeyRelease: - STRIP_MODS(e->xkey.state); - /* remove from the state the mask of the modifier being released, if - it is a modifier key being released (this is a little ugly..) */ - kp = modmap->modifiermap; - for (i = 0; i < mask_table_size; ++i) { - for (k = 0; k < modmap->max_keypermod; ++k) { - if (*kp == e->xkey.keycode) { /* found the keycode */ - /* remove the mask for it */ - e->xkey.state &= ~mask_table[i]; - /* cause the first loop to break; */ - i = mask_table_size; - break; /* get outta here! */ - } - ++kp; - } - } - break; + e->xkey.state = modkeys_only_modifier_masks(e->xkey.state); +#ifdef XKB + if (XkbGetState(ob_display, XkbUseCoreKbd, &xkb_state) == Success) { + e->xkey.state = xkb_state.compat_state; + break; + } +#endif + /* remove from the state the mask of the modifier key being released, + if it is a modifier key being released that is */ + e->xkey.state &= ~modkeys_keycode_to_mask(e->xkey.keycode); + break; case MotionNotify: - STRIP_MODS(e->xmotion.state); - /* compress events */ + e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state); + /* compress events */ { XEvent ce; while (XCheckTypedWindowEvent(ob_display, e->xmotion.window, @@ -295,137 +273,147 @@ static void event_hack_mods(XEvent *e) e->xmotion.x_root = ce.xmotion.x_root; e->xmotion.y_root = ce.xmotion.y_root; } - } - break; + } + break; } } -static gboolean event_ignore(XEvent *e, ObClient *client) +static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only) { - switch(e->type) { - case EnterNotify: - case LeaveNotify: - if (e->xcrossing.detail == NotifyInferior) - return TRUE; - break; - case FocusIn: - /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut - because of RevertToPointerRoot. If the focus ends up reverting to - pointer root on a workspace change, then the FocusIn event that we - want will be of type NotifyAncestor. This situation does not occur - for FocusOut, so it is safely ignored there. + gint mode = e->xfocus.mode; + gint detail = e->xfocus.detail; + Window win = e->xany.window; + + if (e->type == FocusIn) { + /* These are ones we never want.. */ + + /* This means focus was given by a keyboard/mouse grab. */ + if (mode == NotifyGrab) + return FALSE; + /* This means focus was given back from a keyboard/mouse grab. */ + if (mode == NotifyUngrab) + return FALSE; + + /* These are the ones we want.. */ + + if (win == RootWindow(ob_display, ob_screen)) { + /* If looking for a focus in on a client, then always return + FALSE for focus in's to the root window */ + if (in_client_only) + return FALSE; + /* This means focus reverted off of a client */ + else if (detail == NotifyPointerRoot || + detail == NotifyDetailNone || + detail == NotifyInferior) + return TRUE; + else + return FALSE; + } + + /* It was on a client, was it a valid one? + It's possible to get a FocusIn event for a client that was managed + but has disappeared. */ - if (INVALID_FOCUSIN(e) || - client == NULL) { -#ifdef DEBUG_FOCUS - ob_debug("FocusIn on %lx mode %d detail %d IGNORED\n", - e->xfocus.window, e->xfocus.mode, e->xfocus.detail); -#endif - /* says a client was not found for the event (or a valid FocusIn - event was not found. - */ - e->xfocus.window = None; - return TRUE; + if (in_client_only) { + ObWindow *w = g_hash_table_lookup(window_map, &e->xfocus.window); + if (!w || !WINDOW_IS_CLIENT(w)) + return FALSE; } -#ifdef DEBUG_FOCUS - ob_debug("FocusIn on %lx mode %d detail %d\n", e->xfocus.window, - e->xfocus.mode, e->xfocus.detail); -#endif - break; - case FocusOut: - if (INVALID_FOCUSOUT(e)) { -#ifdef DEBUG_FOCUS - ob_debug("FocusOut on %lx mode %d detail %d IGNORED\n", - e->xfocus.window, e->xfocus.mode, e->xfocus.detail); -#endif + /* This means focus moved from the root window to a client */ + if (detail == NotifyVirtual) + return TRUE; + /* This means focus moved from one client to another */ + if (detail == NotifyNonlinearVirtual) return TRUE; - } -#ifdef DEBUG_FOCUS - ob_debug("FocusOut on %lx mode %d detail %d\n", - e->xfocus.window, e->xfocus.mode, e->xfocus.detail); -#endif + /* Otherwise.. */ + return FALSE; + } else { + g_assert(e->type == FocusOut); - { - XEvent fe; - gboolean fallback = TRUE; - - while (TRUE) { - if (!XCheckTypedWindowEvent(ob_display, e->xfocus.window, - FocusOut, &fe)) - if (!XCheckTypedEvent(ob_display, FocusIn, &fe)) - break; - if (fe.type == FocusOut) { -#ifdef DEBUG_FOCUS - ob_debug("found pending FocusOut\n"); -#endif - if (!INVALID_FOCUSOUT(&fe)) { - /* if there is a VALID FocusOut still coming, don't - fallback focus yet, we'll deal with it then */ - XPutBackEvent(ob_display, &fe); - fallback = FALSE; - break; - } - } else { -#ifdef DEBUG_FOCUS - ob_debug("found pending FocusIn\n"); -#endif - /* is the focused window getting a FocusOut/In back to - itself? - */ - if (fe.xfocus.window == e->xfocus.window && - !event_ignore(&fe, client)) { - /* - if focus_client is not set, then we can't do - this. we need the FocusIn. This happens in the - case when the set_focus_client(NULL) in the - focus_fallback function fires and then - focus_fallback picks the currently focused - window (such as on a SendToDesktop-esque action. - */ - if (focus_client) { -#ifdef DEBUG_FOCUS - ob_debug("focused window got an Out/In back to " - "itself IGNORED both\n"); -#endif - return TRUE; - } else { - event_process(&fe, NULL); -#ifdef DEBUG_FOCUS - ob_debug("focused window got an Out/In back to " - "itself but focus_client was null " - "IGNORED just the Out\n"); -#endif - return TRUE; - } - } + /* These are ones we never want.. */ - { - ObEventData d; - - /* once all the FocusOut's have been dealt with, if - there is a FocusIn still left and it is valid, then - use it */ - event_process(&fe, &d); - if (!d.ignored) { -#ifdef DEBUG_FOCUS - ob_debug("FocusIn was OK, so don't fallback\n"); -#endif - fallback = FALSE; - break; - } - } - } - } - if (fallback) { -#ifdef DEBUG_FOCUS - ob_debug("no valid FocusIn and no FocusOut events found, " - "falling back\n"); -#endif - focus_fallback(OB_FOCUS_FALLBACK_NOFOCUS); - } - } + /* This means focus was taken by a keyboard/mouse grab. */ + if (mode == NotifyGrab) + return FALSE; + + /* Focus left the root window revertedto state */ + if (win == RootWindow(ob_display, ob_screen)) + return FALSE; + + /* These are the ones we want.. */ + + /* This means focus moved from a client to the root window */ + if (detail == NotifyVirtual) + return TRUE; + /* This means focus moved from one client to another */ + if (detail == NotifyNonlinearVirtual) + return TRUE; + + /* Otherwise.. */ + return FALSE; + } +} + +static Bool event_look_for_focusin(Display *d, XEvent *e, XPointer arg) +{ + return e->type == FocusIn && wanted_focusevent(e, FALSE); +} + +static Bool event_look_for_focusin_client(Display *d, XEvent *e, XPointer arg) +{ + return e->type == FocusIn && wanted_focusevent(e, TRUE); +} + +static void print_focusevent(XEvent *e) +{ + gint mode = e->xfocus.mode; + gint detail = e->xfocus.detail; + Window win = e->xany.window; + const gchar *modestr, *detailstr; + + switch (mode) { + case NotifyNormal: modestr="NotifyNormal"; break; + case NotifyGrab: modestr="NotifyGrab"; break; + case NotifyUngrab: modestr="NotifyUngrab"; break; + case NotifyWhileGrabbed: modestr="NotifyWhileGrabbed"; break; + } + switch (detail) { + case NotifyAncestor: detailstr="NotifyAncestor"; break; + case NotifyVirtual: detailstr="NotifyVirtual"; break; + case NotifyInferior: detailstr="NotifyInferior"; break; + case NotifyNonlinear: detailstr="NotifyNonlinear"; break; + case NotifyNonlinearVirtual: detailstr="NotifyNonlinearVirtual"; break; + case NotifyPointer: detailstr="NotifyPointer"; break; + case NotifyPointerRoot: detailstr="NotifyPointerRoot"; break; + case NotifyDetailNone: detailstr="NotifyDetailNone"; break; + } + + if (mode == NotifyGrab || mode == NotifyUngrab) + return; + + g_assert(modestr); + g_assert(detailstr); + ob_debug_type(OB_DEBUG_FOCUS, "Focus%s 0x%x mode=%s detail=%s\n", + (e->xfocus.type == FocusIn ? "In" : "Out"), + win, + modestr, detailstr); + +} + +static gboolean event_ignore(XEvent *e, ObClient *client) +{ + switch(e->type) { + case FocusIn: + print_focusevent(e); + if (!wanted_focusevent(e, FALSE)) + return TRUE; + break; + case FocusOut: + print_focusevent(e); + if (!wanted_focusevent(e, FALSE)) + return TRUE; break; } return FALSE; @@ -434,11 +422,11 @@ static gboolean event_ignore(XEvent *e, ObClient *client) static void event_process(const XEvent *ec, gpointer data) { Window window; - ObGroup *group = NULL; ObClient *client = NULL; ObDock *dock = NULL; ObDockApp *dockapp = NULL; ObWindow *obwin = NULL; + GSList *timewinclients = NULL; XEvent ee, *e; ObEventData *ed = data; @@ -447,8 +435,9 @@ static void event_process(const XEvent *ec, gpointer data) e = ⅇ window = event_get_window(e); - if (!(e->type == PropertyNotify && - (group = g_hash_table_lookup(group_map, &window)))) + if (e->type != PropertyNotify || + !(timewinclients = propwin_get_clients(window, + OB_PROPWIN_USER_TIME))) if ((obwin = g_hash_table_lookup(window_map, &window))) { switch (obwin->type) { case Window_Dock: @@ -468,7 +457,7 @@ static void event_process(const XEvent *ec, gpointer data) } } - event_set_lasttime(e); + event_set_curtime(e); event_hack_mods(e); if (event_ignore(e, client)) { if (ed) @@ -478,74 +467,190 @@ static void event_process(const XEvent *ec, gpointer data) ed->ignored = FALSE; /* deal with it in the kernel */ - if (group) - event_handle_group(group, e); + + if (menu_frame_visible && + (e->type == EnterNotify || e->type == LeaveNotify)) + { + /* crossing events for menu */ + event_handle_menu(e); + } else if (e->type == FocusIn) { + if (e->xfocus.detail == NotifyPointerRoot || + e->xfocus.detail == NotifyDetailNone || + e->xfocus.detail == NotifyInferior) + { + XEvent ce; + + ob_debug_type(OB_DEBUG_FOCUS, "Focus went to pointer root/none\n"); + + /* If another FocusIn is in the queue then don't fallback yet. This + fixes the fun case of: + window map -> send focusin + window unmap -> get focusout + window map -> send focusin + get first focus out -> fall back to something (new window + hasn't received focus yet, so something else) -> send focusin + which means the "something else" is the last thing to get a + focusin sent to it, so the new window doesn't end up with focus. + + But if the other focus in is something like PointerRoot then we + still want to fall back. + */ + if (XCheckIfEvent(ob_display, &ce, event_look_for_focusin_client, + NULL)) + { + XPutBackEvent(ob_display, &ce); + ob_debug_type(OB_DEBUG_FOCUS, + " but another FocusIn is coming\n"); + } else { + /* Focus has been reverted to the root window or nothing. + + FocusOut events come after UnmapNotify, so we don't need to + worry about focusing an invalid window + */ + + if (!focus_left_screen) + focus_fallback(TRUE); + } + } + else if (!client) + { + ob_debug_type(OB_DEBUG_FOCUS, + "Focus went to a window that is already gone\n"); + + /* If you send focus to a window and then it disappears, you can + get the FocusIn for it, after it is unmanaged. + Just wait for the next FocusOut/FocusIn pair. */ + } + else if (client != focus_client) { + focus_left_screen = FALSE; + frame_adjust_focus(client->frame, TRUE); + focus_set_client(client); + client_calc_layer(client); + client_bring_helper_windows(client); + } + } else if (e->type == FocusOut) { + gboolean nomove = FALSE; + XEvent ce; + + /* Look for the followup FocusIn */ + if (!XCheckIfEvent(ob_display, &ce, event_look_for_focusin, NULL)) { + /* There is no FocusIn, this means focus went to a window that + is not being managed, or a window on another screen. */ + Window win, root; + gint i; + guint u; + xerror_set_ignore(TRUE); + if (XGetInputFocus(ob_display, &win, &i) != 0 && + XGetGeometry(ob_display, win, &root, &i,&i,&u,&u,&u,&u) != 0 && + root != RootWindow(ob_display, ob_screen)) + { + ob_debug_type(OB_DEBUG_FOCUS, + "Focus went to another screen !\n"); + focus_left_screen = TRUE; + } + else + ob_debug_type(OB_DEBUG_FOCUS, + "Focus went to a black hole !\n"); + xerror_set_ignore(FALSE); + /* nothing is focused */ + focus_set_client(NULL); + } else if (ce.xany.window == e->xany.window) { + ob_debug_type(OB_DEBUG_FOCUS, "Focus didn't go anywhere\n"); + /* If focus didn't actually move anywhere, there is nothing to do*/ + nomove = TRUE; + } else { + /* Focus did move, so process the FocusIn event */ + ObEventData ed = { .ignored = FALSE }; + event_process(&ce, &ed); + if (ed.ignored) { + /* The FocusIn was ignored, this means it was on a window + that isn't a client. */ + ob_debug_type(OB_DEBUG_FOCUS, + "Focus went to an unmanaged window 0x%x !\n", + ce.xfocus.window); + focus_fallback(TRUE); + } + } + + if (client && !nomove) { + frame_adjust_focus(client->frame, FALSE); + if (client == focus_client) + focus_set_client(NULL); + client_calc_layer(client); + } + } else if (timewinclients) + event_handle_user_time_window_clients(timewinclients, e); else if (client) - event_handle_client(client, e); + event_handle_client(client, e); else if (dockapp) - event_handle_dockapp(dockapp, e); + event_handle_dockapp(dockapp, e); else if (dock) - event_handle_dock(dock, e); + event_handle_dock(dock, e); else if (window == RootWindow(ob_display, ob_screen)) - event_handle_root(e); + event_handle_root(e); else if (e->type == MapRequest) - client_manage(window); + client_manage(window); + else if (e->type == ClientMessage) { + /* This is for _NET_WM_REQUEST_FRAME_EXTENTS messages. They come for + windows that are not managed yet. */ + if (e->xclient.message_type == prop_atoms.net_request_frame_extents) { + /* Pretend to manage the client, getting information used to + determine its decorations */ + ObClient *c = client_fake_manage(e->xclient.window); + gulong vals[4]; + + /* set the frame extents on the window */ + vals[0] = c->frame->size.left; + vals[1] = c->frame->size.right; + vals[2] = c->frame->size.top; + vals[3] = c->frame->size.bottom; + PROP_SETA32(e->xclient.window, net_frame_extents, + cardinal, vals, 4); + + /* Free the pretend client */ + client_fake_unmanage(c); + } + } else if (e->type == ConfigureRequest) { - /* unhandled configure requests must be used to configure the - window directly */ - XWindowChanges xwc; - - xwc.x = e->xconfigurerequest.x; - xwc.y = e->xconfigurerequest.y; - xwc.width = e->xconfigurerequest.width; - xwc.height = e->xconfigurerequest.height; - xwc.border_width = e->xconfigurerequest.border_width; - xwc.sibling = e->xconfigurerequest.above; - xwc.stack_mode = e->xconfigurerequest.detail; + /* unhandled configure requests must be used to configure the + window directly */ + XWindowChanges xwc; + + xwc.x = e->xconfigurerequest.x; + xwc.y = e->xconfigurerequest.y; + xwc.width = e->xconfigurerequest.width; + xwc.height = e->xconfigurerequest.height; + xwc.border_width = e->xconfigurerequest.border_width; + xwc.sibling = e->xconfigurerequest.above; + xwc.stack_mode = e->xconfigurerequest.detail; - /* we are not to be held responsible if someone sends us an - invalid request! */ - xerror_set_ignore(TRUE); - XConfigureWindow(ob_display, window, - e->xconfigurerequest.value_mask, &xwc); - xerror_set_ignore(FALSE); + /* we are not to be held responsible if someone sends us an + invalid request! */ + xerror_set_ignore(TRUE); + XConfigureWindow(ob_display, window, + e->xconfigurerequest.value_mask, &xwc); + xerror_set_ignore(FALSE); + } +#ifdef SYNC + else if (extensions_sync && + e->type == extensions_sync_event_basep + XSyncAlarmNotify) + { + XSyncAlarmNotifyEvent *se = (XSyncAlarmNotifyEvent*)e; + if (se->alarm == moveresize_alarm && moveresize_in_progress) + moveresize_event(e); } +#endif - /* user input (action-bound) events */ if (e->type == ButtonPress || e->type == ButtonRelease || e->type == MotionNotify || e->type == KeyPress || e->type == KeyRelease) { - if (menu_frame_visible) - event_handle_menu(e); - else { - if (!keyboard_process_interactive_grab(e, &client)) { - if (moveresize_in_progress) { - moveresize_event(e); - - /* make further actions work on the client being - moved/resized */ - client = moveresize_client; - } - - menu_can_hide = FALSE; - ob_main_loop_timeout_add(ob_main_loop, - G_USEC_PER_SEC / 4, - menu_hide_delay_func, - NULL, NULL); - - if (e->type == ButtonPress || e->type == ButtonRelease || - e->type == MotionNotify) - mouse_event(client, e); - else if (e->type == KeyPress) - /* when in the middle of a focus cycling action, this - causes the window which appears to be focused to be - the one on which the actions will be executed */ - keyboard_event((focus_cycle_target ? - focus_cycle_target : client), e); - } - } + event_handle_user_input(client, e); } + + /* if something happens and it's not from an XEvent, then we don't know + the time */ + event_curtime = CurrentTime; } static void event_handle_root(XEvent *e) @@ -555,31 +660,42 @@ static void event_handle_root(XEvent *e) switch(e->type) { case SelectionClear: ob_debug("Another WM has requested to replace us. Exiting.\n"); - ob_exit(0); + ob_exit_replace(); break; case ClientMessage: - if (e->xclient.format != 32) break; - - msgtype = e->xclient.message_type; - if (msgtype == prop_atoms.net_current_desktop) { - unsigned int d = e->xclient.data.l[0]; - if (d < screen_num_desktops) - screen_set_desktop(d); - } else if (msgtype == prop_atoms.net_number_of_desktops) { - unsigned int d = e->xclient.data.l[0]; - if (d > 0) - screen_set_num_desktops(d); - } else if (msgtype == prop_atoms.net_showing_desktop) { - screen_show_desktop(e->xclient.data.l[0] != 0); - } - break; + if (e->xclient.format != 32) break; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.net_current_desktop) { + guint d = e->xclient.data.l[0]; + if (d < screen_num_desktops) { + event_curtime = e->xclient.data.l[1]; + if (event_curtime == 0) + ob_debug_type(OB_DEBUG_APP_BUGS, + "_NET_CURRENT_DESKTOP message is missing " + "a timestamp\n"); + screen_set_desktop(d, TRUE); + } + } else if (msgtype == prop_atoms.net_number_of_desktops) { + guint d = e->xclient.data.l[0]; + if (d > 0) + screen_set_num_desktops(d); + } else if (msgtype == prop_atoms.net_showing_desktop) { + screen_show_desktop(e->xclient.data.l[0] != 0, NULL); + } else if (msgtype == prop_atoms.ob_control) { + if (e->xclient.data.l[0] == 1) + ob_reconfigure(); + else if (e->xclient.data.l[0] == 2) + ob_restart(); + } + break; case PropertyNotify: - if (e->xproperty.atom == prop_atoms.net_desktop_names) - screen_update_desktop_names(); - else if (e->xproperty.atom == prop_atoms.net_desktop_layout) - screen_update_layout(); - break; + if (e->xproperty.atom == prop_atoms.net_desktop_names) + screen_update_desktop_names(); + else if (e->xproperty.atom == prop_atoms.net_desktop_layout) + screen_update_layout(); + break; case ConfigureNotify: #ifdef XRANDR XRRUpdateConfiguration(e); @@ -588,37 +704,42 @@ static void event_handle_root(XEvent *e) break; default: ; -#ifdef VIDMODE - if (extensions_vidmode && e->type == extensions_vidmode_event_basep) { - ob_debug("VIDMODE EVENT\n"); - } -#endif } } -static void event_handle_group(ObGroup *group, XEvent *e) -{ - GSList *it; - - g_assert(e->type == PropertyNotify); - - for (it = group->members; it; it = g_slist_next(it)) - event_handle_client(it->data, e); -} - void event_enter_client(ObClient *client) { g_assert(config_focus_follow); - if (client_normal(client) && client_can_focus(client)) { + if (client_enter_focusable(client) && client_can_focus(client)) { if (config_focus_delay) { + ObFocusDelayData *data; + ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func); + + data = g_new(ObFocusDelayData, 1); + data->client = client; + data->time = event_curtime; + ob_main_loop_timeout_add(ob_main_loop, config_focus_delay, focus_delay_func, - client, NULL); - } else - focus_delay_func(client); + data, focus_delay_cmp, focus_delay_dest); + } else { + ObFocusDelayData data; + data.client = client; + data.time = event_curtime; + focus_delay_func(&data); + } + } +} + +static void event_handle_user_time_window_clients(GSList *l, XEvent *e) +{ + g_assert(e->type == PropertyNotify); + if (e->xproperty.atom == prop_atoms.net_wm_user_time) { + for (; l; l = g_slist_next(l)) + client_update_user_time(l->data); } } @@ -626,20 +747,37 @@ static void event_handle_client(ObClient *client, XEvent *e) { XEvent ce; Atom msgtype; - int i=0; ObFrameContext con; + static gint px = -1, py = -1; + static guint pb = 0; switch (e->type) { - case VisibilityNotify: - client->frame->obscured = e->xvisibility.state != VisibilityUnobscured; - break; case ButtonPress: + /* save where the press occured for the first button pressed */ + if (!pb) { + pb = e->xbutton.button; + px = e->xbutton.x; + py = e->xbutton.y; + } case ButtonRelease: /* Wheel buttons don't draw because they are an instant click, so it - is a waste of resources to go drawing it. */ - if (!(e->xbutton.button == 4 || e->xbutton.button == 5)) { - con = frame_context(client, e->xbutton.window); - con = mouse_button_frame_context(con, e->xbutton.button); + is a waste of resources to go drawing it. + if the user is doing an intereactive thing, or has a menu open then + the mouse is grabbed (possibly) and if we get these events we don't + want to deal with them + */ + if (!(e->xbutton.button == 4 || e->xbutton.button == 5) && + !keyboard_interactively_grabbed() && + !menu_frame_visible) + { + /* use where the press occured */ + con = frame_context(client, e->xbutton.window, px, py); + con = mouse_button_frame_context(con, e->xbutton.button, + e->xbutton.state); + + if (e->type == ButtonRelease && e->xbutton.button == pb) + pb = 0, px = py = -1; + switch (con) { case OB_FRAME_CONTEXT_MAXIMIZE: client->frame->max_press = (e->type == ButtonPress); @@ -667,26 +805,80 @@ static void event_handle_client(ObClient *client, XEvent *e) } } break; - case FocusIn: -#ifdef DEBUG_FOCUS - ob_debug("FocusIn on client for %lx\n", client->window); -#endif - if (client != focus_client) { - focus_set_client(client); - frame_adjust_focus(client->frame, TRUE); - client_calc_layer(client); + case MotionNotify: + con = frame_context(client, e->xmotion.window, + e->xmotion.x, e->xmotion.y); + switch (con) { + case OB_FRAME_CONTEXT_TITLEBAR: + case OB_FRAME_CONTEXT_TLCORNER: + case OB_FRAME_CONTEXT_TRCORNER: + /* we've left the button area inside the titlebar */ + if (client->frame->max_hover || client->frame->desk_hover || + client->frame->shade_hover || client->frame->iconify_hover || + client->frame->close_hover) + { + client->frame->max_hover = FALSE; + client->frame->desk_hover = FALSE; + client->frame->shade_hover = FALSE; + client->frame->iconify_hover = FALSE; + client->frame->close_hover = FALSE; + frame_adjust_state(client->frame); + } + break; + case OB_FRAME_CONTEXT_MAXIMIZE: + if (!client->frame->max_hover) { + client->frame->max_hover = TRUE; + frame_adjust_state(client->frame); + } + break; + case OB_FRAME_CONTEXT_ALLDESKTOPS: + if (!client->frame->desk_hover) { + client->frame->desk_hover = TRUE; + frame_adjust_state(client->frame); + } + break; + case OB_FRAME_CONTEXT_SHADE: + if (!client->frame->shade_hover) { + client->frame->shade_hover = TRUE; + frame_adjust_state(client->frame); + } + break; + case OB_FRAME_CONTEXT_ICONIFY: + if (!client->frame->iconify_hover) { + client->frame->iconify_hover = TRUE; + frame_adjust_state(client->frame); + } + break; + case OB_FRAME_CONTEXT_CLOSE: + if (!client->frame->close_hover) { + client->frame->close_hover = TRUE; + frame_adjust_state(client->frame); + } + break; + default: + break; } break; - case FocusOut: -#ifdef DEBUG_FOCUS - ob_debug("FocusOut on client for %lx\n", client->window); -#endif - frame_adjust_focus(client->frame, FALSE); - client_calc_layer(client); - break; case LeaveNotify: - con = frame_context(client, e->xcrossing.window); + con = frame_context(client, e->xcrossing.window, + e->xcrossing.x, e->xcrossing.y); switch (con) { + case OB_FRAME_CONTEXT_TITLEBAR: + case OB_FRAME_CONTEXT_TLCORNER: + case OB_FRAME_CONTEXT_TRCORNER: + /* we've left the button area inside the titlebar */ + if (client->frame->max_hover || client->frame->desk_hover || + client->frame->shade_hover || client->frame->iconify_hover || + client->frame->close_hover) + { + client->frame->max_hover = FALSE; + client->frame->desk_hover = FALSE; + client->frame->shade_hover = FALSE; + client->frame->iconify_hover = FALSE; + client->frame->close_hover = FALSE; + frame_adjust_state(client->frame); + } + break; case OB_FRAME_CONTEXT_MAXIMIZE: client->frame->max_hover = FALSE; frame_adjust_state(client->frame); @@ -708,12 +900,29 @@ static void event_handle_client(ObClient *client, XEvent *e) frame_adjust_state(client->frame); break; case OB_FRAME_CONTEXT_FRAME: - /* - if (config_focus_follow && config_focus_delay) + /* When the mouse leaves an animating window, don't use the + corresponding enter events. Pretend like the animating window + doesn't even exist..! */ + if (frame_iconify_animating(client->frame)) + event_ignore_all_queued_enters(); + + ob_debug_type(OB_DEBUG_FOCUS, + "%sNotify mode %d detail %d on %lx\n", + (e->type == EnterNotify ? "Enter" : "Leave"), + e->xcrossing.mode, + e->xcrossing.detail, (client?client->window:0)); + if (keyboard_interactively_grabbed()) + break; + if (config_focus_follow && config_focus_delay && + /* leave inferior events can happen when the mouse goes onto + the window's border and then into the window before the + delay is up */ + e->xcrossing.detail != NotifyInferior) + { ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func, - client); - */ + client, FALSE); + } break; default: break; @@ -721,14 +930,8 @@ static void event_handle_client(ObClient *client, XEvent *e) break; case EnterNotify: { - gboolean nofocus = FALSE; - - if (ignore_enter_focus) { - ignore_enter_focus--; - nofocus = TRUE; - } - - con = frame_context(client, e->xcrossing.window); + con = frame_context(client, e->xcrossing.window, + e->xcrossing.x, e->xcrossing.y); switch (con) { case OB_FRAME_CONTEXT_MAXIMIZE: client->frame->max_hover = TRUE; @@ -751,24 +954,28 @@ static void event_handle_client(ObClient *client, XEvent *e) frame_adjust_state(client->frame); break; case OB_FRAME_CONTEXT_FRAME: + if (keyboard_interactively_grabbed()) + break; if (e->xcrossing.mode == NotifyGrab || - e->xcrossing.mode == NotifyUngrab) + e->xcrossing.mode == NotifyUngrab || + /*ignore enters when we're already in the window */ + e->xcrossing.detail == NotifyInferior || + is_enter_focus_event_ignored(e)) { -#ifdef DEBUG_FOCUS - ob_debug("%sNotify mode %d detail %d on %lx IGNORED\n", - (e->type == EnterNotify ? "Enter" : "Leave"), - e->xcrossing.mode, - e->xcrossing.detail, client?client->window:0); -#endif - } else { -#ifdef DEBUG_FOCUS - ob_debug("%sNotify mode %d detail %d on %lx, " - "focusing window\n", - (e->type == EnterNotify ? "Enter" : "Leave"), - e->xcrossing.mode, - e->xcrossing.detail, client?client->window:0); -#endif - if (!nofocus && config_focus_follow) + ob_debug_type(OB_DEBUG_FOCUS, + "%sNotify mode %d detail %d on %lx IGNORED\n", + (e->type == EnterNotify ? "Enter" : "Leave"), + e->xcrossing.mode, + e->xcrossing.detail, client?client->window:0); + } + else { + ob_debug_type(OB_DEBUG_FOCUS, + "%sNotify mode %d detail %d on %lx, " + "focusing window\n", + (e->type == EnterNotify ? "Enter" : "Leave"), + e->xcrossing.mode, + e->xcrossing.detail, (client?client->window:0)); + if (config_focus_follow) event_enter_client(client); } break; @@ -778,133 +985,165 @@ static void event_handle_client(ObClient *client, XEvent *e) break; } case ConfigureRequest: - /* compress these */ - while (XCheckTypedWindowEvent(ob_display, client->window, - ConfigureRequest, &ce)) { - ++i; - /* XXX if this causes bad things.. we can compress config req's - with the same mask. */ - e->xconfigurerequest.value_mask |= - ce.xconfigurerequest.value_mask; - if (ce.xconfigurerequest.value_mask & CWX) - e->xconfigurerequest.x = ce.xconfigurerequest.x; - if (ce.xconfigurerequest.value_mask & CWY) - e->xconfigurerequest.y = ce.xconfigurerequest.y; - if (ce.xconfigurerequest.value_mask & CWWidth) - e->xconfigurerequest.width = ce.xconfigurerequest.width; - if (ce.xconfigurerequest.value_mask & CWHeight) - e->xconfigurerequest.height = ce.xconfigurerequest.height; - if (ce.xconfigurerequest.value_mask & CWBorderWidth) - e->xconfigurerequest.border_width = - ce.xconfigurerequest.border_width; - if (ce.xconfigurerequest.value_mask & CWStackMode) - e->xconfigurerequest.detail = ce.xconfigurerequest.detail; - } - - /* if we are iconic (or shaded (fvwm does this)) ignore the event */ - if (client->iconic || client->shaded) return; - - /* resize, then move, as specified in the EWMH section 7.7 */ - if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight | - CWX | CWY | - CWBorderWidth)) { - int x, y, w, h; - ObCorner corner; - - if (e->xconfigurerequest.value_mask & CWBorderWidth) + { + /* dont compress these unless you're going to watch for property + notifies in between (these can change what the configure would + do to the window). + also you can't compress stacking events + */ + + gint x, y, w, h; + + /* if nothing is changed, then a configurenotify is needed */ + gboolean config = TRUE; + + x = client->area.x; + y = client->area.y; + w = client->area.width; + h = client->area.height; + + ob_debug("ConfigureRequest desktop %d wmstate %d visibile %d\n", + screen_desktop, client->wmstate, client->frame->visible); + + if (e->xconfigurerequest.value_mask & CWBorderWidth) + if (client->border_width != e->xconfigurerequest.border_width) { client->border_width = e->xconfigurerequest.border_width; + /* if only the border width is changing, then it's not needed*/ + config = FALSE; + } - x = (e->xconfigurerequest.value_mask & CWX) ? - e->xconfigurerequest.x : client->area.x; - y = (e->xconfigurerequest.value_mask & CWY) ? - e->xconfigurerequest.y : client->area.y; - w = (e->xconfigurerequest.value_mask & CWWidth) ? - e->xconfigurerequest.width : client->area.width; - h = (e->xconfigurerequest.value_mask & CWHeight) ? - e->xconfigurerequest.height : client->area.height; - { - int newx = x; - int newy = y; - int fw = w + - client->frame->size.left + client->frame->size.right; - int fh = h + - client->frame->size.top + client->frame->size.bottom; - client_find_onscreen(client, &newx, &newy, fw, fh, - client_normal(client)); - if (e->xconfigurerequest.value_mask & CWX) - x = newx; - if (e->xconfigurerequest.value_mask & CWY) - y = newy; + if (e->xconfigurerequest.value_mask & CWStackMode) { + ObClient *sibling = NULL; + + /* get the sibling */ + if (e->xconfigurerequest.value_mask & CWSibling) { + ObWindow *win; + win = g_hash_table_lookup(window_map, + &e->xconfigurerequest.above); + if (WINDOW_IS_CLIENT(win) && WINDOW_AS_CLIENT(win) != client) + sibling = WINDOW_AS_CLIENT(win); } - - switch (client->gravity) { - case NorthEastGravity: - case EastGravity: - corner = OB_CORNER_TOPRIGHT; - break; - case SouthWestGravity: - case SouthGravity: - corner = OB_CORNER_BOTTOMLEFT; - break; - case SouthEastGravity: - corner = OB_CORNER_BOTTOMRIGHT; - break; - default: /* NorthWest, Static, etc */ - corner = OB_CORNER_TOPLEFT; - } - - client_configure_full(client, corner, x, y, w, h, FALSE, TRUE, - TRUE); - } - - if (e->xconfigurerequest.value_mask & CWStackMode) { - switch (e->xconfigurerequest.detail) { - case Below: - case BottomIf: - client_lower(client); - break; - case Above: - case TopIf: - default: - client_raise(client); - break; - } - } - break; + /* activate it rather than just focus it */ + stacking_restack_request(client, sibling, + e->xconfigurerequest.detail, TRUE); + + /* if a stacking change is requested then it is needed */ + config = TRUE; + } + + /* don't allow clients to move shaded windows (fvwm does this) */ + if (client->shaded && (e->xconfigurerequest.value_mask & CWX || + e->xconfigurerequest.value_mask & CWY)) + { + e->xconfigurerequest.value_mask &= ~CWX; + e->xconfigurerequest.value_mask &= ~CWY; + + /* if the client tried to move and we aren't letting it then a + synthetic event is needed */ + config = TRUE; + } + + if (e->xconfigurerequest.value_mask & CWX || + e->xconfigurerequest.value_mask & CWY || + e->xconfigurerequest.value_mask & CWWidth || + e->xconfigurerequest.value_mask & CWHeight) + { + if (e->xconfigurerequest.value_mask & CWX) + x = e->xconfigurerequest.x; + if (e->xconfigurerequest.value_mask & CWY) + y = e->xconfigurerequest.y; + if (e->xconfigurerequest.value_mask & CWWidth) + w = e->xconfigurerequest.width; + if (e->xconfigurerequest.value_mask & CWHeight) + h = e->xconfigurerequest.height; + + /* if a new position or size is requested, then a configure is + needed */ + config = TRUE; + } + + ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d\n", + e->xconfigurerequest.value_mask & CWX, x, + e->xconfigurerequest.value_mask & CWY, y, + e->xconfigurerequest.value_mask & CWWidth, w, + e->xconfigurerequest.value_mask & CWHeight, h); + + /* check for broken apps moving to their root position + + XXX remove this some day...that would be nice. right now all + kde apps do this when they try activate themselves on another + desktop. eg. open amarok window on desktop 1, switch to desktop + 2, click amarok tray icon. it will move by its decoration size. + */ + if (x != client->area.x && + x == (client->frame->area.x + client->frame->size.left - + (gint)client->border_width) && + y != client->area.y && + y == (client->frame->area.y + client->frame->size.top - + (gint)client->border_width)) + { + ob_debug_type(OB_DEBUG_APP_BUGS, + "Application %s is trying to move via " + "ConfigureRequest to it's root window position " + "but it is not using StaticGravity\n", + client->title); + /* don't move it */ + x = client->area.x; + y = client->area.y; + } + + if (config) { + client_find_onscreen(client, &x, &y, w, h, FALSE); + client_configure(client, x, y, w, h, FALSE, TRUE); + + /* ignore enter events caused by these like ob actions do */ + event_ignore_all_queued_enters(); + } + break; + } case UnmapNotify: - if (client->ignore_unmaps) { - client->ignore_unmaps--; - break; - } - client_unmanage(client); - break; + if (client->ignore_unmaps) { + client->ignore_unmaps--; + break; + } + ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d " + "ignores left %d\n", + client->window, e->xunmap.event, e->xunmap.from_configure, + client->ignore_unmaps); + client_unmanage(client); + break; case DestroyNotify: - client_unmanage(client); - break; + ob_debug("DestroyNotify for window 0x%x\n", client->window); + client_unmanage(client); + break; case ReparentNotify: - /* this is when the client is first taken captive in the frame */ - if (e->xreparent.parent == client->frame->plate) break; - - /* - This event is quite rare and is usually handled in unmapHandler. - However, if the window is unmapped when the reparent event occurs, - the window manager never sees it because an unmap event is not sent - to an already unmapped window. - */ - - /* we don't want the reparent event, put it back on the stack for the - X server to deal with after we unmanage the window */ - XPutBackEvent(ob_display, e); + /* this is when the client is first taken captive in the frame */ + if (e->xreparent.parent == client->frame->plate) break; + + /* + This event is quite rare and is usually handled in unmapHandler. + However, if the window is unmapped when the reparent event occurs, + the window manager never sees it because an unmap event is not sent + to an already unmapped window. + */ + + /* we don't want the reparent event, put it back on the stack for the + X server to deal with after we unmanage the window */ + XPutBackEvent(ob_display, e); - client_unmanage(client); - break; + ob_debug("ReparentNotify for window 0x%x\n", client->window); + client_unmanage(client); + break; case MapRequest: ob_debug("MapRequest for 0x%lx\n", client->window); if (!client->iconic) break; /* this normally doesn't happen, but if it - does, we don't want it! */ - client_activate(client, FALSE); + does, we don't want it! + it can happen now when the window is on + another desktop, but we still don't + want it! */ + client_activate(client, FALSE, TRUE); break; case ClientMessage: /* validate cuz we query stuff off the client here */ @@ -954,14 +1193,30 @@ static void event_handle_client(ObClient *client, XEvent *e) client->window); client_set_state(client, e->xclient.data.l[0], e->xclient.data.l[1], e->xclient.data.l[2]); + + /* ignore enter events caused by these like ob actions do */ + event_ignore_all_queued_enters(); } else if (msgtype == prop_atoms.net_close_window) { ob_debug("net_close_window for 0x%lx\n", client->window); client_close(client); } else if (msgtype == prop_atoms.net_active_window) { - ob_debug("net_active_window for 0x%lx\n", client->window); - client_activate(client, FALSE); + ob_debug("net_active_window for 0x%lx source=%s\n", + client->window, + (e->xclient.data.l[0] == 0 ? "unknown" : + (e->xclient.data.l[0] == 1 ? "application" : + (e->xclient.data.l[0] == 2 ? "user" : "INVALID")))); + /* XXX make use of data.l[2] !? */ + event_curtime = e->xclient.data.l[1]; + if (event_curtime == 0) + ob_debug_type(OB_DEBUG_APP_BUGS, + "_NET_ACTIVE_WINDOW message for window %s is " + "missing a timestamp\n", client->title); + client_activate(client, FALSE, + (e->xclient.data.l[0] == 0 || + e->xclient.data.l[0] == 2)); } else if (msgtype == prop_atoms.net_wm_moveresize) { - ob_debug("net_wm_moveresize for 0x%lx\n", client->window); + ob_debug("net_wm_moveresize for 0x%lx direction %d\n", + client->window, e->xclient.data.l[2]); if ((Atom)e->xclient.data.l[2] == prop_atoms.net_wm_moveresize_size_topleft || (Atom)e->xclient.data.l[2] == @@ -991,14 +1246,16 @@ static void event_handle_client(ObClient *client, XEvent *e) e->xclient.data.l[1], e->xclient.data.l[3], e->xclient.data.l[2]); } + else if ((Atom)e->xclient.data.l[2] == + prop_atoms.net_wm_moveresize_cancel) + moveresize_end(TRUE); } else if (msgtype == prop_atoms.net_moveresize_window) { - int oldg = client->gravity; - int tmpg, x, y, w, h; + gint grav, x, y, w, h; if (e->xclient.data.l[0] & 0xff) - tmpg = e->xclient.data.l[0] & 0xff; - else - tmpg = oldg; + grav = e->xclient.data.l[0] & 0xff; + else + grav = client->gravity; if (e->xclient.data.l[0] & 1 << 8) x = e->xclient.data.l[1]; @@ -1016,27 +1273,57 @@ static void event_handle_client(ObClient *client, XEvent *e) h = e->xclient.data.l[4]; else h = client->area.height; - client->gravity = tmpg; - { - int newx = x; - int newy = y; - int fw = w + - client->frame->size.left + client->frame->size.right; - int fh = h + - client->frame->size.top + client->frame->size.bottom; - client_find_onscreen(client, &newx, &newy, fw, fh, - client_normal(client)); - if (e->xclient.data.l[0] & 1 << 8) - x = newx; - if (e->xclient.data.l[0] & 1 << 9) - y = newy; + ob_debug("MOVERESIZE x %d %d y %d %d\n", + e->xclient.data.l[0] & 1 << 8, x, + e->xclient.data.l[0] & 1 << 9, y); + client_convert_gravity(client, grav, &x, &y, w, h); + client_find_onscreen(client, &x, &y, w, h, FALSE); + + client_configure(client, x, y, w, h, FALSE, TRUE); + + /* ignore enter events caused by these like ob actions do */ + event_ignore_all_queued_enters(); + } else if (msgtype == prop_atoms.net_restack_window) { + if (e->xclient.data.l[0] != 2) { + ob_debug_type(OB_DEBUG_APP_BUGS, + "_NET_RESTACK_WINDOW sent for window %s with " + "invalid source indication %ld\n", + client->title, e->xclient.data.l[0]); + } else { + ObClient *sibling = NULL; + if (e->xclient.data.l[1]) { + ObWindow *win = g_hash_table_lookup + (window_map, &e->xclient.data.l[1]); + if (WINDOW_IS_CLIENT(win) && + WINDOW_AS_CLIENT(win) != client) + { + sibling = WINDOW_AS_CLIENT(win); + } + if (sibling == NULL) + ob_debug_type(OB_DEBUG_APP_BUGS, + "_NET_RESTACK_WINDOW sent for window %s " + "with invalid sibling 0x%x\n", + client->title, e->xclient.data.l[1]); + } + if (e->xclient.data.l[2] == Below || + e->xclient.data.l[2] == BottomIf || + e->xclient.data.l[2] == Above || + e->xclient.data.l[2] == TopIf || + e->xclient.data.l[2] == Opposite) + { + /* just raise, don't activate */ + stacking_restack_request(client, sibling, + e->xclient.data.l[2], FALSE); + /* send a synthetic ConfigureNotify, cuz this is supposed + to be like a ConfigureRequest. */ + client_reconfigure(client); + } else + ob_debug_type(OB_DEBUG_APP_BUGS, + "_NET_RESTACK_WINDOW sent for window %s " + "with invalid detail %d\n", + client->title, e->xclient.data.l[2]); } - - client_configure(client, OB_CORNER_TOPLEFT, - x, y, w, h, FALSE, TRUE); - - client->gravity = oldg; } break; case PropertyNotify: @@ -1067,11 +1354,8 @@ static void event_handle_client(ObClient *client, XEvent *e) b == prop_atoms.wm_icon_name)) { continue; } - if ((a == prop_atoms.net_wm_icon || - a == prop_atoms.kwm_win_icon) - && - (b == prop_atoms.net_wm_icon || - b == prop_atoms.kwm_win_icon)) + if (a == prop_atoms.net_wm_icon && + b == prop_atoms.net_wm_icon) continue; XPutBackEvent(ob_display, &ce); @@ -1087,7 +1371,7 @@ static void event_handle_client(ObClient *client, XEvent *e) client_update_wmhints(client); } else if (msgtype == XA_WM_TRANSIENT_FOR) { client_update_transient_for(client); - client_get_type(client); + client_get_type_and_transientness(client); /* type may have changed, so update the layer */ client_calc_layer(client); client_setup_decor_and_functions(client); @@ -1096,8 +1380,6 @@ static void event_handle_client(ObClient *client, XEvent *e) msgtype == prop_atoms.net_wm_icon_name || msgtype == prop_atoms.wm_icon_name) { client_update_title(client); - } else if (msgtype == prop_atoms.wm_class) { - client_update_class(client); } else if (msgtype == prop_atoms.wm_protocols) { client_update_protocols(client); client_setup_decor_and_functions(client); @@ -1105,13 +1387,26 @@ static void event_handle_client(ObClient *client, XEvent *e) else if (msgtype == prop_atoms.net_wm_strut) { client_update_strut(client); } - else if (msgtype == prop_atoms.net_wm_icon || - msgtype == prop_atoms.kwm_win_icon) { + else if (msgtype == prop_atoms.net_wm_icon) { client_update_icons(client); } - else if (msgtype == prop_atoms.sm_client_id) { - client_update_sm_client_id(client); + else if (msgtype == prop_atoms.net_wm_icon_geometry) { + client_update_icon_geometry(client); + } + else if (msgtype == prop_atoms.net_wm_user_time) { + client_update_user_time(client); + } + else if (msgtype == prop_atoms.net_wm_user_time_window) { + client_update_user_time_window(client); } +#ifdef SYNC + else if (msgtype == prop_atoms.net_wm_sync_request_counter) { + client_update_sync_request_counter(client); + } +#endif + case ColormapNotify: + client_update_colormap(client, e->xcolormap.colormap); + break; default: ; #ifdef SHAPE @@ -1127,7 +1422,10 @@ static void event_handle_dock(ObDock *s, XEvent *e) { switch (e->type) { case ButtonPress: - stacking_raise(DOCK_AS_WINDOW(s)); + if (e->xbutton.button == 1) + stacking_raise(DOCK_AS_WINDOW(s)); + else if (e->xbutton.button == 2) + stacking_lower(DOCK_AS_WINDOW(s)); break; case EnterNotify: dock_hide(FALSE); @@ -1163,77 +1461,240 @@ static void event_handle_dockapp(ObDockApp *app, XEvent *e) } } -ObMenuFrame* find_active_menu() +static ObMenuFrame* find_active_menu() { GList *it; - ObMenuFrame *f; + ObMenuFrame *ret = NULL; for (it = menu_frame_visible; it; it = g_list_next(it)) { - f = it->data; - if (f->selected) + ret = it->data; + if (ret->selected) break; + ret = NULL; + } + return ret; +} + +static ObMenuFrame* find_active_or_last_menu() +{ + ObMenuFrame *ret = NULL; + + ret = find_active_menu(); + if (!ret && menu_frame_visible) + ret = menu_frame_visible->data; + return ret; +} + +static gboolean event_handle_menu_keyboard(XEvent *ev) +{ + guint keycode, state; + gunichar unikey; + ObMenuFrame *frame; + gboolean ret = TRUE; + + keycode = ev->xkey.keycode; + state = ev->xkey.state; + unikey = translate_unichar(keycode); + + frame = find_active_or_last_menu(); + if (frame == NULL) + ret = FALSE; + + else if (keycode == ob_keycode(OB_KEY_ESCAPE) && state == 0) { + /* Escape goes to the parent menu or closes the last one */ + if (frame->parent) + menu_frame_select(frame, NULL, TRUE); + else + menu_frame_hide_all(); + } + + else if (keycode == ob_keycode(OB_KEY_RETURN) && (state == 0 || + state == ControlMask)) + { + /* Enter runs the active item or goes into the submenu. + Control-Enter runs it without closing the menu. */ + if (frame->child) + menu_frame_select_next(frame->child); + else + menu_entry_frame_execute(frame->selected, state, ev->xkey.time); + } + + else if (keycode == ob_keycode(OB_KEY_LEFT) && ev->xkey.state == 0) { + /* Left goes to the parent menu */ + menu_frame_select(frame, NULL, TRUE); + } + + else if (keycode == ob_keycode(OB_KEY_RIGHT) && ev->xkey.state == 0) { + /* Right goes to the selected submenu */ + if (frame->child) menu_frame_select_next(frame->child); + } + + else if (keycode == ob_keycode(OB_KEY_UP) && state == 0) { + menu_frame_select_previous(frame); + } + + else if (keycode == ob_keycode(OB_KEY_DOWN) && state == 0) { + menu_frame_select_next(frame); + } + + /* keyboard accelerator shortcuts. */ + else if (ev->xkey.state == 0 && + /* was it a valid key? */ + unikey != 0 && + /* don't bother if the menu is empty. */ + frame->entries) + { + GList *start; + GList *it; + ObMenuEntryFrame *found = NULL; + guint num_found = 0; + + /* start after the selected one */ + start = frame->entries; + if (frame->selected) { + for (it = start; frame->selected != it->data; it = g_list_next(it)) + g_assert(it != NULL); /* nothing was selected? */ + /* next with wraparound */ + start = g_list_next(it); + if (start == NULL) start = frame->entries; + } + + it = start; + do { + ObMenuEntryFrame *e = it->data; + gunichar entrykey = 0; + + if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) + entrykey = e->entry->data.normal.shortcut; + else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) + entrykey = e->entry->data.submenu.submenu->shortcut; + + if (unikey == entrykey) { + if (found == NULL) found = e; + ++num_found; + } + + /* next with wraparound */ + it = g_list_next(it); + if (it == NULL) it = frame->entries; + } while (it != start); + + if (found) { + if (found->entry->type == OB_MENU_ENTRY_TYPE_NORMAL && + num_found == 1) + { + menu_frame_select(frame, found, TRUE); + usleep(50000); /* highlight the item for a short bit so the + user can see what happened */ + menu_entry_frame_execute(found, state, ev->xkey.time); + } else { + menu_frame_select(frame, found, TRUE); + if (num_found == 1) + menu_frame_select_next(frame->child); + } + } else + ret = FALSE; } - return it ? it->data : NULL; + else + ret = FALSE; + + return ret; } -static void event_handle_menu(XEvent *ev) +static gboolean event_handle_menu(XEvent *ev) { ObMenuFrame *f; ObMenuEntryFrame *e; + gboolean ret = TRUE; switch (ev->type) { case ButtonRelease: - if ((e = menu_entry_frame_under(ev->xbutton.x_root, - ev->xbutton.y_root))) - menu_entry_frame_execute(e, ev->xbutton.state); - else if (menu_can_hide) - menu_frame_hide_all(); + if ((ev->xbutton.button < 4 || ev->xbutton.button > 5) + && menu_can_hide) + { + if ((e = menu_entry_frame_under(ev->xbutton.x_root, + ev->xbutton.y_root))) + menu_entry_frame_execute(e, ev->xbutton.state, + ev->xbutton.time); + else + menu_frame_hide_all(); + } break; - case MotionNotify: - if ((f = menu_frame_under(ev->xmotion.x_root, - ev->xmotion.y_root))) { - menu_frame_move_on_screen(f); - if ((e = menu_entry_frame_under(ev->xmotion.x_root, - ev->xmotion.y_root))) - menu_frame_select(f, e); + case EnterNotify: + if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) { + if (e->ignore_enters) + --e->ignore_enters; + else + menu_frame_select(e->frame, e, FALSE); } - { - ObMenuFrame *a; + break; + case LeaveNotify: + /*ignore leaves when we're already in the window */ + if (ev->xcrossing.detail == NotifyInferior) + break; - a = find_active_menu(); - if (a && a != f && - a->selected->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU) - { - menu_frame_select(a, NULL); - } + if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) && + (f = find_active_menu()) && f->selected == e && + e->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU) + { + menu_frame_select(e->frame, NULL, FALSE); } break; + case MotionNotify: + if ((e = menu_entry_frame_under(ev->xmotion.x_root, + ev->xmotion.y_root))) + menu_frame_select(e->frame, e, FALSE); + break; case KeyPress: - if (ev->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) - menu_frame_hide_all(); - else if (ev->xkey.keycode == ob_keycode(OB_KEY_RETURN)) { - ObMenuFrame *f; - if ((f = find_active_menu())) - menu_entry_frame_execute(f->selected, ev->xkey.state); - } else if (ev->xkey.keycode == ob_keycode(OB_KEY_LEFT)) { - ObMenuFrame *f; - if ((f = find_active_menu()) && f->parent) - menu_frame_select(f, NULL); - } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) { - ObMenuFrame *f; - if ((f = find_active_menu()) && f->child) - menu_frame_select_next(f->child); - } else if (ev->xkey.keycode == ob_keycode(OB_KEY_UP)) { - ObMenuFrame *f; - if ((f = find_active_menu())) - menu_frame_select_previous(f); - } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) { - ObMenuFrame *f; - if ((f = find_active_menu())) - menu_frame_select_next(f); - } + ret = event_handle_menu_keyboard(ev); break; } + return ret; +} + +static void event_handle_user_input(ObClient *client, XEvent *e) +{ + g_assert(e->type == ButtonPress || e->type == ButtonRelease || + e->type == MotionNotify || e->type == KeyPress || + e->type == KeyRelease); + + if (menu_frame_visible) { + if (event_handle_menu(e)) + /* don't use the event if the menu used it, but if the menu + didn't use it and it's a keypress that is bound, it will + close the menu and be used */ + return; + } + + /* if the keyboard interactive action uses the event then dont + use it for bindings. likewise is moveresize uses the event. */ + if (!keyboard_process_interactive_grab(e, &client) && + !(moveresize_in_progress && moveresize_event(e))) + { + if (moveresize_in_progress) + /* make further actions work on the client being + moved/resized */ + client = moveresize_client; + + menu_can_hide = FALSE; + ob_main_loop_timeout_add(ob_main_loop, + config_menu_hide_delay * 1000, + menu_hide_delay_func, + NULL, g_direct_equal, NULL); + + if (e->type == ButtonPress || + e->type == ButtonRelease || + e->type == MotionNotify) + { + /* the frame may not be "visible" but they can still click on it + in the case where it is animating before disappearing */ + if (!client || !frame_iconify_animating(client->frame)) + mouse_event(client, e); + } else if (e->type == KeyPress) { + keyboard_event((focus_cycle_target ? focus_cycle_target : + (client ? client : focus_client)), e); + } + } } static gboolean menu_hide_delay_func(gpointer data) @@ -1242,45 +1703,110 @@ static gboolean menu_hide_delay_func(gpointer data) return FALSE; /* no repeat */ } +static void focus_delay_dest(gpointer data) +{ + g_free(data); +} + +static gboolean focus_delay_cmp(gconstpointer d1, gconstpointer d2) +{ + const ObFocusDelayData *f1 = d1; + return f1->client == d2; +} + static gboolean focus_delay_func(gpointer data) { - ObClient *c = data; + ObFocusDelayData *d = data; + Time old = event_curtime; - client_focus(c); - if (config_focus_raise) - client_raise(c); + event_curtime = d->time; + if (focus_client != d->client) { + if (client_focus(d->client) && config_focus_raise) + stacking_raise(CLIENT_AS_WINDOW(d->client)); + } + event_curtime = old; return FALSE; /* no repeat */ } -static void focus_delay_client_dest(gpointer data) +static void focus_delay_client_dest(ObClient *client, gpointer data) { - ObClient *c = data; + ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func, + client, FALSE); +} - ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func, c); +void event_halt_focus_delay() +{ + ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func); } -void event_ignore_queued_enters() +static Bool event_look_for_enters(Display *d, XEvent *e, XPointer arg) { - GSList *saved = NULL, *it; - XEvent *e; - - XSync(ob_display, FALSE); + if (e->type == EnterNotify && + /* these types aren't used for focusing */ + !(e->xcrossing.mode == NotifyGrab || + e->xcrossing.mode == NotifyUngrab || + e->xcrossing.detail == NotifyInferior)) + { + ObWindow *win; - /* count the events */ - while (TRUE) { - e = g_new(XEvent, 1); - if (XCheckTypedEvent(ob_display, EnterNotify, e)) { - saved = g_slist_append(saved, e); + /* found an enter for that leave, ignore it if it's going to + another window */ + win = g_hash_table_lookup(window_map, &e->xany.window); + if (win && WINDOW_IS_CLIENT(win)) ++ignore_enter_focus; - } else { - g_free(e); - break; - } } - /* put the events back */ - for (it = saved; it; it = g_slist_next(it)) { - XPutBackEvent(ob_display, it->data); - g_free(it->data); + return False; /* don't disrupt the queue order, just count them */ +} + +void event_ignore_all_queued_enters() +{ + XEvent e; + + XSync(ob_display, FALSE); + + /* count the events without disrupting them */ + ignore_enter_focus = 0; + XCheckIfEvent(ob_display, &e, event_look_for_enters, NULL); +} + +static gboolean is_enter_focus_event_ignored(XEvent *e) +{ + g_assert(e->type == EnterNotify && + !(e->xcrossing.mode == NotifyGrab || + e->xcrossing.mode == NotifyUngrab || + e->xcrossing.detail == NotifyInferior)); + + ob_debug_type(OB_DEBUG_FOCUS, "# enters ignored: %d\n", + ignore_enter_focus); + + if (ignore_enter_focus) { + --ignore_enter_focus; + return TRUE; } - g_slist_free(saved); + return FALSE; +} + +gboolean event_time_after(Time t1, Time t2) +{ + g_assert(t1 != CurrentTime); + g_assert(t2 != CurrentTime); + + /* + Timestamp values wrap around (after about 49.7 days). The server, given + its current time is represented by timestamp T, always interprets + timestamps from clients by treating half of the timestamp space as being + later in time than T. + - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html + */ + + /* TIME_HALF is half of the number space of a Time type variable */ +#define TIME_HALF (Time)(1 << (sizeof(Time)*8-1)) + + if (t2 >= TIME_HALF) + /* t2 is in the second half so t1 might wrap around and be smaller than + t2 */ + return t1 >= t2 || t1 < (t2 + TIME_HALF); + else + /* t2 is in the first half so t1 has to come after it */ + return t1 >= t2 && t1 < (t2 + TIME_HALF); }