aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsl0p <sl0p@sl0p.foo>2026-08-13 23:39:20 +0200
committersl0p <sl0p@sl0p.foo>2026-08-13 23:39:20 +0200
commit80425ad3683510f016a954315902c78bb1f77379 (patch)
treeba9e8553cecacadff960571298c44fe28b2c15be /src
parentsl0ptty: M8 — pane sizes as weights, resize and drag-to-reorder (diff)
downloadslosh-80425ad3683510f016a954315902c78bb1f77379.tar.gz
slosh-80425ad3683510f016a954315902c78bb1f77379.tar.xz
slosh-80425ad3683510f016a954315902c78bb1f77379.zip
sl0ptty: focus follows the mouse, with the guards that make it liveable
Hovering a pane focuses it. The feature is three lines; the reason it is not obnoxious is the guards, and each one is a test: - not while the prefix is held, so a chord cannot be hijacked by a nudge of the mouse; - not through the finder, whose rows are on top of the hit list anyway; - not during a drag, which owns the mouse until the button comes up; - not onto a collapsed pane. Only 'pane:' and 'title:' targets count, never the frame rect's 'focus:', so the pointer crossing a stack of headers does not expand three panes on its way past. Off with 'focus_follows_mouse false'; clicking still focuses either way. This needed the client to ask for ?1003 (any motion) rather than ?1002 (motion while a button is held), which fixes something that was quietly broken: a pane requesting any-event tracking could never receive hover, because the outer terminal was never asked to report it. It can now, and a pane that asked only for clicks still gets none — both tested. One real bug behind that: our decoder represents 'no button held' as MBTN_UNKNOWN, and pane_send_mouse was passing it to the encoder as a concrete button. lib-vt has ghostty_mouse_event_clear_button() for exactly this, and without it bare motion encoded to nothing at all. 320 checks green.
Diffstat (limited to 'src')
-rw-r--r--src/app.c20
-rw-r--r--src/client.c4
-rw-r--r--src/config.c3
-rw-r--r--src/config.h1
-rw-r--r--src/pane.c5
5 files changed, 29 insertions, 4 deletions
diff --git a/src/app.c b/src/app.c
index 6006796..00fe474 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1450,9 +1450,24 @@ static void drag_edge(app_t *a, node_t *sp, size_t i, int cells) {
layout(a);
}
+/* Focus follows the mouse, but never at the cost of what you were doing.
+ *
+ * Hovering may not: steal focus mid-chord (the prefix is held), reach past an
+ * open finder, interrupt a drag, or expand a collapsed pane just because the
+ * pointer crossed its header — which is why only `pane:` and `title:` targets
+ * count, and the frame rect's `focus:` does not. */
+static bool hover_focus_allowed(const app_t *a) {
+ return CFG.focus_follows_mouse && !a->prefix && !a->finder &&
+ a->drag.kind == DRAG_NONE;
+}
+
static void do_action(app_t *a, const char *action, const input_event_t *ev) {
if (strncmp(action, "title:", 6) == 0) {
uint32_t id = (uint32_t)strtoul(action + 6, NULL, 10);
+ if (ev->maction == MOUSE_MOTION && hover_focus_allowed(a)) {
+ app_focus_pane(a, id);
+ return;
+ }
if (ev->maction == MOUSE_PRESS) {
a->drag.kind = DRAG_TITLE;
a->drag.src = id;
@@ -1517,7 +1532,10 @@ static void do_action(app_t *a, const char *action, const input_event_t *ev) {
} else if (strncmp(action, "close:", 6) == 0) {
close_leaf(a, n);
} else if (strncmp(action, "pane:", 5) == 0) {
- cur(a)->focus = n;
+ /* A press always focuses; a hover only when it is allowed to. The event
+ * is forwarded either way, so a pane that tracks the mouse still sees the
+ * pointer cross it whether or not focus moved. */
+ if (ev->maction != MOUSE_MOTION || hover_focus_allowed(a)) cur(a)->focus = n;
/* translate to pane-local coordinates before forwarding */
input_event_t local = *ev;
local.mx = (uint16_t)(ev->mx - n->content.x);
diff --git a/src/client.c b/src/client.c
index f50db7b..03d5387 100644
--- a/src/client.c
+++ b/src/client.c
@@ -18,12 +18,12 @@
#define TERM_ENTER \
"\x1b[?1049h\x1b[0m\x1b[2J" \
- "\x1b[?1002h\x1b[?1006h" \
+ "\x1b[?1002h\x1b[?1003h\x1b[?1006h" \
"\x1b[?1004h" \
"\x1b[?2004h" \
"\x1b[>1u"
#define TERM_LEAVE \
- "\x1b[<u\x1b[?2004l\x1b[?1004l\x1b[?1006l\x1b[?1002l" \
+ "\x1b[<u\x1b[?2004l\x1b[?1004l\x1b[?1006l\x1b[?1003l\x1b[?1002l" \
"\x1b[?25h\x1b[0m\x1b[?1049l"
static volatile sig_atomic_t g_winch = 0, g_quit = 0;
diff --git a/src/config.c b/src/config.c
index bda5bdb..b5ae284 100644
--- a/src/config.c
+++ b/src/config.c
@@ -122,6 +122,7 @@ void config_defaults(config_t *c) {
c->min_pane_cols = 24;
c->min_pane_rows = 6;
c->status_bar = true;
+ c->focus_follows_mouse = true;
c->frame_focus = rgb(0xff, 0x5f, 0xd7);
c->frame_idle = rgb(0x45, 0x45, 0x4a);
@@ -216,6 +217,8 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) {
c->pad = (uint16_t)kdl_arg_int(kdl_child(root, "padding"), 0, c->pad);
c->rounded = kdl_arg_bool(kdl_child(root, "rounded"), 0, c->rounded);
c->status_bar = kdl_arg_bool(kdl_child(root, "status_bar"), 0, c->status_bar);
+ c->focus_follows_mouse = kdl_arg_bool(kdl_child(root, "focus_follows_mouse"), 0,
+ c->focus_follows_mouse);
const char *align = kdl_arg(kdl_child(root, "title_align"), 0, NULL);
if (align) {
diff --git a/src/config.h b/src/config.h
index 852da88..246b03a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -52,6 +52,7 @@ typedef struct {
align_t title_align;
uint16_t min_pane_cols, min_pane_rows;
bool status_bar;
+ bool focus_follows_mouse;
/* theme */
color_t frame_focus, frame_idle, title_focus;
diff --git a/src/pane.c b/src/pane.c
index ac03134..38b8f41 100644
--- a/src/pane.c
+++ b/src/pane.c
@@ -339,7 +339,10 @@ void pane_send_mouse(pane_t *p, const input_event_t *ev) {
GhosttyMousePosition pos = {.x = (float)ev->mx, .y = (float)ev->my};
ghostty_mouse_event_set_position(p->mev, pos);
- ghostty_mouse_event_set_button(p->mev, (GhosttyMouseButton)ev->button);
+ /* "no button held" is its own thing, not button zero: bare motion (hover)
+ * must be encoded as none, or a pane in any-event tracking sees nothing. */
+ if (ev->button == MBTN_UNKNOWN) ghostty_mouse_event_clear_button(p->mev);
+ else ghostty_mouse_event_set_button(p->mev, (GhosttyMouseButton)ev->button);
ghostty_mouse_event_set_action(p->mev, (GhosttyMouseAction)ev->maction);
ghostty_mouse_event_set_mods(p->mev, (GhosttyMods)ev->mods);