From 4c8982ab096267c41d1fbc46026084349059f906 Mon Sep 17 00:00:00 2001
From: Jeff Abrahamson
Date: Mon, 29 Sep 2014 20:59:40 +0200
Subject: [PATCH 1/3] Implement mouse-based focus.
Implement focus-follows-mouse and sloppy focus within
ratpoison. Unlike the separate program sloppy.c, this also permits
focus-follows-mouse with correct interaction with the root window. The
sloppy.c program does not have access to ratpoison's internal frame
set, and so can't focus frames that do not contain windows.
It is hoped that this will prove more reliable than sloppy.c as well,
though sloppy.c has been reasonably good of late, to be fair.
---
src/actions.c | 44 ++++++++++++++++++++++++++++++++++++-----
src/actions.h | 1 +
src/data.h | 5 +++++
src/events.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/globals.h | 25 ++++++++++++++++++++++++
src/main.c | 1 +
src/manage.c | 4 ++--
src/screen.c | 5 +----
src/split.c | 33 +++++++++++++++++++++++++++++++
src/split.h | 2 +-
src/window.c | 28 +++++++++++++++++++++++++-
src/window.h | 1 +
12 files changed, 197 insertions(+), 15 deletions(-)
diff --git a/src/actions.c b/src/actions.c
index 7579101..1c5fc48 100644
--- a/src/actions.c
+++ b/src/actions.c
@@ -246,6 +246,8 @@ init_user_commands(void)
add_command ("swap", cmd_swap, 2, 1, 1,
"destination frame: ", arg_FRAME,
"source frame: ", arg_FRAME);
+ add_command ("focus_policy", cmd_focus_policy, 1, 1, 1,
+ "Focus policy [manual,sloppy,ffm]: ", arg_STRING);
add_command ("focuslast", cmd_focuslast, 0, 0, 0);
add_command ("focusleft", cmd_focusleft, 0, 0, 0);
add_command ("focusright", cmd_focusright, 0, 0, 0);
@@ -4493,6 +4495,39 @@ cmd_startup_message (int interactive UNUSED, struct cmdarg **args)
return cmdret_new (RET_SUCCESS, NULL);
}
+static void sync_wins (rp_screen *s);
+
+cmdret *
+cmd_focus_policy (int interactive UNUSED, struct cmdarg **args)
+{
+ if (args[0] == NULL) {
+ /* TODO(address@hidden): Fix error message with array of policy names. And below. */
+ return cmdret_new (RET_SUCCESS, "%s", defaults.focus_policy ? "on":"off");
+ }
+
+ int succeeded = 1;
+ if (!strcasecmp (ARG_STRING(0), "manual")) {
+ defaults.focus_policy = FOCUS_MANUAL;
+ } else if (!strcasecmp (ARG_STRING(0), "sloppy")) {
+ defaults.focus_policy = FOCUS_SLOPPY;
+ } else if (!strcasecmp (ARG_STRING(0), "ffm")) {
+ defaults.focus_policy = FOCUS_FOLLOWS_MOUSE;
+ } else {
+ defaults.focus_policy = FOCUS_MANUAL;
+ succeeded = 0;
+ }
+ init_focus_policy();
+
+ /* Hack. I only need to set the input mask. */
+ int i;
+ for (i=0; iroot, &dw1, &dw2, &wins, &nwins);
+ XSelectInput(dpy, RootWindow(dpy, s->screen_num), root_events);
/* Remove any windows in our cached lists that aren't in the query
tree. These windows have been destroyed. */
@@ -4759,6 +4795,7 @@ sync_wins (rp_screen *s)
}
/* We've handled the window. */
+ XSelectInput (dpy, win->w, win_events);
continue;
}
@@ -4786,6 +4823,7 @@ sync_wins (rp_screen *s)
}
/* We've handled the window. */
+ XSelectInput (dpy, win->w, win_events);
continue;
}
@@ -4799,7 +4837,6 @@ sync_wins (rp_screen *s)
&& get_state (win) == IconicState))
map_window (win);
}
-
}
static int tmpwm_error_raised = 0;
@@ -4878,10 +4915,7 @@ cmd_tmpwm (int interactive UNUSED, struct cmdarg **args)
tmpwm_error_raised = 0;
for (i=0; iwindow);
+ PRINT_DEBUG(("Mouse focus request: rp_window=%u, Xwindow=%u\n", (unsigned int)win, (unsigned int)any_event->window));
+ if (win && current_window() != win) {
+ /* Also check for transient windows. */
+ if(is_rp_window_for_screen(win->w, current_screen())) {
+ /* Don't focus ratpoison screen windows. It's not
+ meaningful, the user should never mean it, and we don't
+ mean to support it. */
+ PRINT_DEBUG((" Ignoring enter event on screen window.\n"));
+ return;
+ }
+ rp_frame *new_frame = find_frame_number(win->frame_number);
+ if(new_frame)
+ set_active_frame(new_frame, 0);
+ else
+ PRINT_DEBUG((" Ignoring focus request: frame not found.\n"));
+ } else if (defaults.focus_policy == FOCUS_FOLLOWS_MOUSE && is_a_root_window(any_event->window)) {
+ PRINT_DEBUG((" focus: root window event\n"));
+ XCrossingEvent *crossing_event = (XCrossingEvent *)ev;
+ int x = crossing_event->x_root;
+ int y = crossing_event->y_root;
+ Window root_win = crossing_event->root;
+ rp_frame *new_frame = find_frame_by_coordinates (root_win, x, y);
+ if(new_frame && new_frame->win_number < 0 && new_frame != current_frame())
+ set_active_frame(new_frame, 0);
+ else {
+ PRINT_DEBUG((" Ignoring focus request: win=%d, not_current=%d\n", new_frame->win_number, new_frame != current_frame()));
+ }
+ } else {
+ PRINT_DEBUG((" Ignoring focus request.\n"));
+ }
+ }
+ break;
+
+ case MotionNotify:
+ PRINT_DEBUG (("--- Handling MotionNotify ---\n"));
+ XPointerMovedEvent *motion_event = (XPointerMovedEvent *)ev;
+ if (defaults.focus_policy == FOCUS_FOLLOWS_MOUSE && is_a_root_window(motion_event->window)) {
+ int x = motion_event->x_root;
+ int y = motion_event->y_root;
+ Window root_win = motion_event->root;
+ rp_frame *new_frame = find_frame_by_coordinates (root_win, x, y);
+ if(new_frame && new_frame->win_number < 0 && new_frame != current_frame())
+ set_active_frame(new_frame, 0);
+ else {
+ PRINT_DEBUG((" Ignoring focus request: win=%d, not_current=%d\n", new_frame->win_number, new_frame != current_frame()));
+ }
+ }
+ break;
+
default:
PRINT_DEBUG (("--- Unknown event %d ---\n",- ev->type));
}
diff --git a/src/globals.h b/src/globals.h
index 1fb7e0f..54b757d 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -42,12 +42,35 @@
#define MAX_FONT_WIDTH(f) (rp_font_width)
+#define ROOT_EVENTS (PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | StructureNotifyMask)
+#define ROOT_EVENTS_WITH_MOVEMENT (ROOT_EVENTS | EnterWindowMask | PointerMotionMask)
#define WIN_EVENTS (StructureNotifyMask | PropertyChangeMask | ColormapChangeMask | FocusChangeMask)
+#define WIN_EVENTS_WITH_MOVEMENT (WIN_EVENTS | EnterWindowMask)
/* EMPTY is used when a frame doesn't contain a window, or a window
doesn't have a frame. Any time a field refers to the number of a
window/frame/screen/etc, Use EMPTY to denote a lack there of. */
#define EMPTY -1
+/* Focus policies */
+/* In manual focus, only the keyboard affects focus. The mouse does not. */
+#define FOCUS_MANUAL 0
+/* In sloppy focus, we focus on user (non-root) windows over which the
+ * mouse moves. This notably means that when the mouse moves to a part
+ * of the screen with no user window (i.e., the root window), the
+ * window the pointer has left remains focused until a new user window
+ * is entered. */
+#define FOCUS_SLOPPY 1
+/* Focus follows mouse causes the focus to follow the mouse
+ * strictly. This is not quite true, because ratpoison will never
+ * focus its own windows, things like the message bar (C-t m) and the
+ * window bar (C-t w). In particular, if the mouse moves over the
+ * root window, the root window itself is focussed, which the user
+ * perceives as input going to no window. Many applications leave a
+ * pixel or two at the edges where the root window is visible, for
+ * example because they restrict resize to multiples of some font
+ * dimension. */
+#define FOCUS_FOLLOWS_MOUSE 2
+
/* Possible values for defaults.window_list_style */
#define STYLE_ROW 0
#define STYLE_COLUMN 1
@@ -108,6 +131,8 @@ extern int rp_current_screen;
extern rp_screen *screens;
extern int num_screens;
+extern unsigned int win_events;
+extern unsigned int root_events;
extern XEvent rp_current_event;
extern Display *dpy;
diff --git a/src/main.c b/src/main.c
index 52dbcce..19d3abc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -592,6 +592,7 @@ init_defaults (void)
defaults.win_name = WIN_NAME_TITLE;
defaults.startup_message = 1;
defaults.warp = 0;
+ defaults.focus_policy = FOCUS_MANUAL;
defaults.window_list_style = STYLE_COLUMN;
defaults.history_size = 20;
diff --git a/src/manage.c b/src/manage.c
index 412fc29..01c0e68 100644
--- a/src/manage.c
+++ b/src/manage.c
@@ -896,9 +896,9 @@ hide_window (rp_window *win)
win->frame_number = EMPTY;
/* Ignore the unmap_notify event. */
- XSelectInput(dpy, win->w, WIN_EVENTS&~(StructureNotifyMask));
+ XSelectInput(dpy, win->w, win_events&~(StructureNotifyMask));
XUnmapWindow (dpy, win->w);
- XSelectInput (dpy, win->w, WIN_EVENTS);
+ XSelectInput (dpy, win->w, win_events);
/* Ensure that the window doesn't have the focused border
color. This is needed by remove_frame and possibly others. */
XSetWindowBorder (dpy, win->w, win->scr->bw_color);
diff --git a/src/screen.c b/src/screen.c
index 0afdee0..3178560 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -266,10 +266,7 @@ init_screen (rp_screen *s, int screen_num)
/* Select on some events on the root window, if this fails, then
there is already a WM running and the X Error handler will catch
it, terminating ratpoison. */
- XSelectInput(dpy, RootWindow (dpy, screen_num),
- PropertyChangeMask | ColormapChangeMask
- | SubstructureRedirectMask | SubstructureNotifyMask
- | StructureNotifyMask);
+ XSelectInput(dpy, RootWindow (dpy, screen_num), root_events);
XSync (dpy, False);
/* Set the numset for the frames to our global numset. */
diff --git a/src/split.c b/src/split.c
index b36b99e..bf45bc5 100644
--- a/src/split.c
+++ b/src/split.c
@@ -1085,6 +1085,39 @@ find_frame_number (int num)
return cur;
}
}
+ return NULL;
+}
+
+rp_frame *
+find_frame_by_coordinates (Window root, int x, int y)
+{
+ /* This is inefficient.
+
+ It would be better to keep an indexed structure of rectangles so
+ that we can search in sub-linear time. That would require
+ maintaining that structure with every frame operation, which
+ incurs some overhead on all frame operations (at least, if
+ defaults.focus_policy > 0). So the subject should be thought
+ through and measured carefully.
+ */
+
+ int i;
+ rp_frame *cur;
+
+ for (i=0; iframes, node)
+ {
+ if(root == s->root)
+ {
+ if (cur->x <= x && cur->y <= y && x < cur->x + cur->width && y < cur->y + cur->height) {
+ return cur;
+ }
+ }
+ }
+ }
+ PRINT_DEBUG(("No frame matches coordinates.\n"));
return NULL;
}
diff --git a/src/split.h b/src/split.h
index 87bb660..7ce5ba3 100644
--- a/src/split.h
+++ b/src/split.h
@@ -56,5 +56,5 @@ rp_frame *find_last_frame (void);
rp_frame * find_frame_number (int num);
rp_frame *current_frame (void);
-
+rp_frame *find_frame_by_coordinates (Window root, int x, int y);
#endif
diff --git a/src/window.c b/src/window.c
index de9032a..cb94600 100644
--- a/src/window.c
+++ b/src/window.c
@@ -184,7 +184,7 @@ add_to_window_list (rp_screen *s, Window w)
get_mouse_position (new_window, &new_window->mouse_x, &new_window->mouse_y);
- XSelectInput (dpy, new_window->w, WIN_EVENTS);
+ XSelectInput (dpy, new_window->w, win_events);
new_window->user_name = xstrdup ("Unnamed");
@@ -649,6 +649,7 @@ void
init_window_stuff (void)
{
rp_window_numset = numset_new ();
+ init_focus_policy();
}
void
@@ -675,6 +676,31 @@ free_window_stuff (void)
numset_free (rp_window_numset);
}
+void
+init_focus_policy(void)
+{
+ switch(defaults.focus_policy)
+ {
+ case FOCUS_MANUAL:
+ win_events = WIN_EVENTS;
+ root_events = ROOT_EVENTS;
+ break;
+ case FOCUS_SLOPPY:
+ win_events = WIN_EVENTS_WITH_MOVEMENT;
+ root_events = ROOT_EVENTS;
+ break;
+ case FOCUS_FOLLOWS_MOUSE:
+ win_events = WIN_EVENTS_WITH_MOVEMENT;
+ root_events = ROOT_EVENTS_WITH_MOVEMENT;
+ break;
+ default:
+ PRINT_DEBUG(("Unexpected focus policy (%d)! Defaulting to manual focus.\n", defaults.focus_policy));
+ defaults.focus_policy = FOCUS_MANUAL;
+ win_events = WIN_EVENTS;
+ root_events = ROOT_EVENTS;
+ }
+}
+
rp_frame *
win_get_frame (rp_window *win)
{
diff --git a/src/window.h b/src/window.h
index fdd246c..498ed4e 100644
--- a/src/window.h
+++ b/src/window.h
@@ -54,6 +54,7 @@ void get_window_list (char *fmt, char *delim, struct sbuf *buffer,
int *mark_start, int *mark_end);
void init_window_stuff (void);
void free_window_stuff (void);
+void init_focus_policy(void);
rp_frame *win_get_frame (rp_window *win);
--
1.9.1