event.c for the Openbox window manager
Copyright (c) 2006 Mikael Magnusson
- Copyright (c) 2003 Ben Jansens
+ 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
#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 <X11/Xlib.h>
-#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <glib.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h> /* for usleep() */
+#endif
#ifdef XKB
# include <X11/XKBlib.h>
#endif
gboolean ignored;
} ObEventData;
+typedef struct
+{
+ ObClient *client;
+ Time time;
+} ObFocusDelayData;
+
static void event_process(const XEvent *e, gpointer data);
-static void event_client_dest(ObClient *client, 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(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)
-
-/* The most recent time at which an event with a timestamp occured. */
-static Time event_lasttime = 0;
-/* The time for the current event being processed
- (it's the event_lasttime for events without times, if this is a bug then
- use CurrentTime instead, but it seems ok) */
+/* The time for the current event being processed */
Time event_curtime = CurrentTime;
-/*! The value of the mask for the NumLock modifier */
-guint NumLockMask;
-/*! The value of the mask for the ScrollLock modifier */
-guint ScrollLockMask;
-/*! The key codes for the modifier keys */
-static XModifierKeymap *modmap;
-/*! Table of the constant modifier masks */
-static const gint mask_table[] = {
- ShiftMask, LockMask, ControlMask, Mod1Mask,
- Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
-};
-static gint mask_table_size;
-
static guint ignore_enter_focus = 0;
-
static gboolean menu_can_hide;
+static gboolean focus_left_screen = FALSE;
#ifdef USE_SM
static void ice_handler(gint fd, gpointer conn)
{
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, NULL);
- client_add_destructor(event_client_dest, NULL);
+ client_add_destroy_notify(focus_delay_client_dest, NULL);
}
void event_shutdown(gboolean reconfig)
IceRemoveConnectionWatch(ice_watch, NULL);
#endif
- client_remove_destructor(focus_delay_client_dest);
- client_remove_destructor(event_client_dest);
- XFreeModifiermap(modmap);
+ client_remove_destroy_notify(focus_delay_client_dest);
}
static Window event_get_window(XEvent *e)
window = None;
}
} else
+#endif
+#ifdef SYNC
+ if (extensions_sync &&
+ e->type == extensions_sync_event_basep + XSyncAlarmNotify)
+ {
+ window = None;
+ } else
#endif
window = e->xany.window;
}
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) {
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 = event_lasttime;
- } else if (t == 0) {
- event_curtime = event_lasttime;
- } else {
- event_curtime = 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)
{
#ifdef XKB
XkbStateRec xkb_state;
#endif
- KeyCode *kp;
- gint i, k;
switch (e->type) {
case ButtonPress:
case ButtonRelease:
- STRIP_MODS(e->xbutton.state);
+ e->xbutton.state = modkeys_only_modifier_masks(e->xbutton.state);
break;
case KeyPress:
- STRIP_MODS(e->xkey.state);
+ 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..) */
+ 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
- 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;
- }
- }
+ /* 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);
+ e->xmotion.state = modkeys_only_modifier_masks(e->xmotion.state);
/* compress events */
{
XEvent ce;
}
}
+static gboolean wanted_focusevent(XEvent *e, gboolean in_client_only)
+{
+ 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 (in_client_only) {
+ ObWindow *w = g_hash_table_lookup(window_map, &e->xfocus.window);
+ if (!w || !WINDOW_IS_CLIENT(w))
+ return FALSE;
+ }
+ else {
+ /* This means focus reverted to parent from the client (this
+ happens often during iconify animation) */
+ if (detail == NotifyInferior)
+ return TRUE;
+ }
+
+ /* 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;
+
+ /* Otherwise.. */
+ return FALSE;
+ } else {
+ g_assert(e->type == FocusOut);
+
+ /* These are ones we never want.. */
+
+ /* 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 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.
- */
- 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;
+ print_focusevent(e);
+ if (!wanted_focusevent(e, FALSE))
return TRUE;
- }
-
-#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
+ print_focusevent(e);
+ if (!wanted_focusevent(e, FALSE))
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
- {
- 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;
- }
- }
-
- {
- 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);
- }
- }
break;
}
return FALSE;
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;
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:
}
}
- event_set_lasttime(e);
+ event_set_curtime(e);
event_hack_mods(e);
if (event_ignore(e, client)) {
if (ed)
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 or"
+ " the frame window\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.
+
+ 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, but note that
+ nothing is focused now.
+ */
+ focus_set_client(NULL);
+ }
+ 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;
+
+ if (client) {
+ frame_adjust_focus(client->frame, FALSE);
+ /* focus_set_client(NULL) has already been called in this
+ section or by focus_fallback */
+ client_calc_layer(client);
+ }
+
+ /* 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 {
+ /* Focus moved, 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);
+ }
+ }
+ } else if (timewinclients)
+ event_handle_user_time_window_clients(timewinclients, e);
else if (client)
event_handle_client(client, e);
else if (dockapp)
event_handle_root(e);
else if (e->type == MapRequest)
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 */
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,
- config_menu_hide_delay * 1000,
- 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)
- keyboard_event((focus_cycle_target ? focus_cycle_target :
- (focus_hilite ? focus_hilite : 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)
msgtype = e->xclient.message_type;
if (msgtype == prop_atoms.net_current_desktop) {
guint d = e->xclient.data.l[0];
- if (d < screen_num_desktops)
- screen_set_desktop(d);
+ 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)
+ if (d > 0 && d <= 1000)
screen_set_num_desktops(d);
} else if (msgtype == prop_atoms.net_showing_desktop) {
- screen_show_desktop(e->xclient.data.l[0] != 0);
+ 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();
}
}
-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);
}
}
{
XEvent ce;
Atom msgtype;
- gint 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);
}
}
break;
- case FocusIn:
-#ifdef DEBUG_FOCUS
- ob_debug("FocusIn on client for %lx (client %lx) mode %d detail %d\n",
- e->xfocus.window, client->window,
- e->xfocus.mode, e->xfocus.detail);
-#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 (client %lx) mode %d detail %d\n",
- e->xfocus.window, client->window,
- e->xfocus.mode, e->xfocus.detail);
-#endif
- focus_hilite = NULL;
- 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);
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, TRUE);
+ client, FALSE);
+ }
break;
default:
break;
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;
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: %d\n",
- (e->type == EnterNotify ? "Enter" : "Leave"),
- e->xcrossing.mode,
- e->xcrossing.detail, (client?client->window:0),
- !nofocus);
-#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;
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;
- }
+ {
+ /* 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;
+ gboolean move = FALSE;
+ gboolean resize = FALSE;
- /* if we are iconic (or shaded (fvwm does this)) ignore the event */
- if (client->iconic || client->shaded) return;
+ /* get the current area */
+ RECT_TO_DIMS(client->area, x, y, w, h);
- /* resize, then move, as specified in the EWMH section 7.7 */
- if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
- CWX | CWY |
- CWBorderWidth)) {
- gint x, y, w, h;
- ObCorner corner;
+ ob_debug("ConfigureRequest desktop %d wmstate %d visibile %d\n",
+ screen_desktop, client->wmstate, client->frame->visible);
- if (e->xconfigurerequest.value_mask & CWBorderWidth)
+ if (e->xconfigurerequest.value_mask & CWBorderWidth)
+ if (client->border_width != e->xconfigurerequest.border_width) {
client->border_width = e->xconfigurerequest.border_width;
- 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;
+ /* if the border width is changing then that is the same
+ as requesting a resize, but we don't actually change
+ the client's border, so it will change their root
+ coordiantes (since they include the border width) and
+ we need to a notify then */
+ move = TRUE;
+ }
- {
- gint newx = x;
- gint newy = y;
- gint fw = w +
- client->frame->size.left + client->frame->size.right;
- gint fh = h +
- client->frame->size.top + client->frame->size.bottom;
- /* make this rude for size-only changes but not for position
- changes.. */
- gboolean moving = ((e->xconfigurerequest.value_mask & CWX) ||
- (e->xconfigurerequest.value_mask & CWY));
-
- client_find_onscreen(client, &newx, &newy, fw, fh,
- !moving);
- 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;
+ /* activate it rather than just focus it */
+ stacking_restack_request(client, sibling,
+ e->xconfigurerequest.detail, TRUE);
+
+ /* if a stacking change moves the window without resizing */
+ move = 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) {
+ /* don't allow clients to move shaded windows (fvwm does this)
+ */
+ if (!client->shaded)
+ x = e->xconfigurerequest.x;
+ move = TRUE;
+ }
+ if (e->xconfigurerequest.value_mask & CWY) {
+ /* don't allow clients to move shaded windows (fvwm does this)
+ */
+ if (!client->shaded)
+ y = e->xconfigurerequest.y;
+ move = TRUE;
}
- client_configure_full(client, corner, x, y, w, h, FALSE, TRUE,
- TRUE);
+ if (e->xconfigurerequest.value_mask & CWWidth) {
+ w = e->xconfigurerequest.width;
+ resize = TRUE;
+
+ /* if x was not given, then use gravity to figure out the new
+ x. the reference point should not be moved */
+ if (!(e->xconfigurerequest.value_mask & CWX))
+ client_gravity_resize_w(client, &x, client->area.width, w);
+ }
+ if (e->xconfigurerequest.value_mask & CWHeight) {
+ h = e->xconfigurerequest.height;
+ resize = TRUE;
+
+ /* if y was not given, then use gravity to figure out the new
+ y. the reference point should not be moved */
+ if (!(e->xconfigurerequest.value_mask & CWY))
+ client_gravity_resize_h(client, &y, client->area.height,h);
+ }
}
- if (e->xconfigurerequest.value_mask & CWStackMode) {
- switch (e->xconfigurerequest.detail) {
- case Below:
- case BottomIf:
- client_lower(client);
- break;
+ ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
+ "move %d resize %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,
+ move, resize);
- case Above:
- case TopIf:
- default:
- client_raise(client);
- break;
+ /* 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 (move && !resize &&
+ 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;
+
+ /* they still requested a move, so don't change whether a
+ notify is sent or not */
+ }
+
+ if (move || resize) {
+ gint lw,lh;
+
+ client_find_onscreen(client, &x, &y, w, h, FALSE);
+ client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
+ /* if they requested something that moves the window, or if
+ the window is actually being changed then configure it and
+ send a configure notify to them */
+ if (move || !RECT_EQUAL_DIMS(client->area, x, y, w, h)) {
+ ob_debug("Doing configure\n");
+ 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;
}
+ 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:
+ ob_debug("DestroyNotify for window 0x%x\n", client->window);
client_unmanage(client);
break;
case ReparentNotify:
X server to deal with after we unmanage the window */
XPutBackEvent(ob_display, e);
+ ob_debug("ReparentNotify for window 0x%x\n", client->window);
client_unmanage(client);
break;
case MapRequest:
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);
(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[1] and [2] ! */
+ /* XXX make use of data.l[2] !? */
+ if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 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);
+ } else
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "_NET_ACTIVE_WINDOW message for window %s is "
+ "missing source indication\n");
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] ==
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) {
- gint oldg = client->gravity;
- gint tmpg, x, y, w, h;
+ gint ograv, x, y, w, h;
+
+ ograv = client->gravity;
if (e->xclient.data.l[0] & 0xff)
- tmpg = e->xclient.data.l[0] & 0xff;
- else
- tmpg = oldg;
+ client->gravity = e->xclient.data.l[0] & 0xff;
if (e->xclient.data.l[0] & 1 << 8)
x = e->xclient.data.l[1];
y = e->xclient.data.l[2];
else
y = client->area.y;
- if (e->xclient.data.l[0] & 1 << 10)
+
+ if (e->xclient.data.l[0] & 1 << 10) {
w = e->xclient.data.l[3];
+
+ /* if x was not given, then use gravity to figure out the new
+ x. the reference point should not be moved */
+ if (!(e->xclient.data.l[0] & 1 << 8))
+ client_gravity_resize_w(client, &x, client->area.width, w);
+ }
else
w = client->area.width;
- if (e->xclient.data.l[0] & 1 << 11)
+
+ if (e->xclient.data.l[0] & 1 << 11) {
h = e->xclient.data.l[4];
+
+ /* if y was not given, then use gravity to figure out the new
+ y. the reference point should not be moved */
+ if (!(e->xclient.data.l[0] & 1 << 9))
+ client_gravity_resize_h(client, &y, client->area.height,h);
+ }
else
h = client->area.height;
- client->gravity = tmpg;
- {
- gint newx = x;
- gint newy = y;
- gint fw = w +
- client->frame->size.left + client->frame->size.right;
- gint 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 (gravity %d)\n",
+ e->xclient.data.l[0] & 1 << 8, x,
+ e->xclient.data.l[0] & 1 << 9, y,
+ client->gravity);
+
+ client_find_onscreen(client, &x, &y, w, h, FALSE);
+
+ client_configure(client, x, y, w, h, FALSE, TRUE);
- client_configure(client, OB_CORNER_TOPLEFT,
- x, y, w, h, FALSE, TRUE);
+ client->gravity = ograv;
- client->gravity = oldg;
+ /* 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]);
+ }
}
break;
case PropertyNotify:
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);
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);
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);
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
switch (e->type) {
case ButtonPress:
if (e->xbutton.button == 1)
- stacking_raise(DOCK_AS_WINDOW(s), FALSE);
+ stacking_raise(DOCK_AS_WINDOW(s));
else if (e->xbutton.button == 2)
- stacking_lower(DOCK_AS_WINDOW(s), FALSE);
+ stacking_lower(DOCK_AS_WINDOW(s));
break;
case EnterNotify:
dock_hide(FALSE);
}
}
-ObMenuFrame* find_active_menu()
+static ObMenuFrame* find_active_menu()
{
GList *it;
ObMenuFrame *ret = NULL;
return ret;
}
-ObMenuFrame* find_active_or_last_menu()
+static ObMenuFrame* find_active_or_last_menu()
{
ObMenuFrame *ret = NULL;
return ret;
}
-static void event_handle_menu(XEvent *ev)
+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;
+ }
+ else
+ ret = FALSE;
+
+ return ret;
+}
+
+static gboolean event_handle_menu(XEvent *ev)
{
ObMenuFrame *f;
ObMenuEntryFrame *e;
+ gboolean ret = TRUE;
switch (ev->type) {
case ButtonRelease:
- if (menu_can_hide) {
+ 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);
+ 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_or_last_menu()) && f->parent)
- menu_frame_select(f, NULL);
- } else if (ev->xkey.keycode == ob_keycode(OB_KEY_RIGHT)) {
- ObMenuFrame *f;
- if ((f = find_active_or_last_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_or_last_menu()))
- menu_frame_select_previous(f);
- } else if (ev->xkey.keycode == ob_keycode(OB_KEY_DOWN)) {
- ObMenuFrame *f;
- if ((f = find_active_or_last_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)
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;
- if (focus_client != c) {
- 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(ObClient *client, gpointer data)
{
ob_main_loop_timeout_remove_data(ob_main_loop, focus_delay_func,
- client, TRUE);
+ client, FALSE);
}
-static void event_client_dest(ObClient *client, gpointer data)
+void event_halt_focus_delay()
{
- if (client == focus_hilite)
- focus_hilite = NULL;
+ ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
}
-void event_halt_focus_delay()
+static Bool event_look_for_enters(Display *d, XEvent *e, XPointer arg)
{
- ob_main_loop_timeout_remove(ob_main_loop, focus_delay_func);
+ 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;
+
+ /* 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;
+ }
+ return False; /* don't disrupt the queue order, just count them */
}
-void event_ignore_queued_enters()
+void event_ignore_all_queued_enters()
{
- GSList *saved = NULL, *it;
- XEvent *e;
-
+ XEvent e;
+
XSync(ob_display, FALSE);
- /* count the events */
- while (TRUE) {
- e = g_new(XEvent, 1);
- if (XCheckTypedEvent(ob_display, EnterNotify, e)) {
- ObWindow *win;
-
- win = g_hash_table_lookup(window_map, &e->xany.window);
- if (win && WINDOW_IS_CLIENT(win))
- ++ignore_enter_focus;
-
- saved = g_slist_append(saved, e);
- } 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);
+ /* 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);
}