diff options
| author | sl0p <sl0p@sl0p.foo> | 2026-08-13 23:39:20 +0200 |
|---|---|---|
| committer | sl0p <sl0p@sl0p.foo> | 2026-08-13 23:39:20 +0200 |
| commit | 80425ad3683510f016a954315902c78bb1f77379 (patch) | |
| tree | ba9e8553cecacadff960571298c44fe28b2c15be /src | |
| parent | sl0ptty: M8 — pane sizes as weights, resize and drag-to-reorder (diff) | |
| download | slosh-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.c | 20 | ||||
| -rw-r--r-- | src/client.c | 4 | ||||
| -rw-r--r-- | src/config.c | 3 | ||||
| -rw-r--r-- | src/config.h | 1 | ||||
| -rw-r--r-- | src/pane.c | 5 |
5 files changed, 29 insertions, 4 deletions
@@ -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; @@ -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); |
