diff options
| author | user <user@clank> | 2026-09-02 14:57:13 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-02 14:57:13 +0200 |
| commit | d698946464f25a0eb43730ffad15656f3247f56b (patch) | |
| tree | 0d040eb64e433122f982ea45912eba37ab54822a /src | |
| parent | Add competitive multiplayer support. (diff) | |
| download | slosh-d698946464f25a0eb43730ffad15656f3247f56b.tar.gz slosh-d698946464f25a0eb43730ffad15656f3247f56b.tar.xz slosh-d698946464f25a0eb43730ffad15656f3247f56b.zip | |
multi-attach: the config knobs
The patch made shared attach unconditional; these make it a policy.
multi_attach false restores the classic displacing attach
(EXIT_REPLACED, which old clients already understand). size_follows
picks whose size the shared screen takes: active (the patch's rule),
smallest (the tmux rule: filler, never a crop), or largest. The size
fold lives in apply_canvas, so any attach, resize or departure can
move the canvas under the folding policies -- including a non-active
client leaving, which under smallest may have been the very client
holding the floor down. A config reload re-applies both knobs to the
running session.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 10 | ||||
| -rw-r--r-- | src/config.c | 27 | ||||
| -rw-r--r-- | src/server.c | 98 |
3 files changed, 123 insertions, 12 deletions
@@ -88,6 +88,16 @@ size_t app_config_files(const char **out, size_t max) { return config_files(&CFG, out, max); } +bool app_cfg_multi_attach(void) { + ensure_config(); + return CFG.multi_attach; +} + +int app_cfg_size_follows(void) { + ensure_config(); + return CFG.size_follows; +} + /* ---- tree --------------------------------------------------------------- */ #include "app_internal.h" diff --git a/src/config.c b/src/config.c index 0c7af63..bfbfdf7 100644 --- a/src/config.c +++ b/src/config.c @@ -918,6 +918,8 @@ void config_defaults(config_t *c) { * than as another row of it. */ c->status_pad = 4; c->focus_follows_mouse = true; + c->multi_attach = true; + c->size_follows = SIZE_FOLLOWS_ACTIVE; c->in_band_shaders = false; /* Windows has no VEOF for the line discipline to act on, so ^D there is a * key that does nothing until we make it mean something. POSIX already has @@ -1607,6 +1609,14 @@ char *config_render(const config_t *c) { cb_add(&b, "\n// ---- behaviour ----\n"); cb_add(&b, "focus_follows_mouse %s\n", yesno(c->focus_follows_mouse)); + cb_add(&b, + "multi_attach %s // several terminals at once; off: a new " + "client displaces the old\n", + yesno(c->multi_attach)); + cb_add(&b, "size_follows \"%s\" // or \"smallest\" / \"largest\"\n", + c->size_follows == SIZE_FOLLOWS_SMALLEST ? "smallest" + : c->size_follows == SIZE_FOLLOWS_LARGEST ? "largest" + : "active"); cb_add(&b, "in_band_shaders %s\n", yesno(c->in_band_shaders)); cb_add(&b, "ctrl_d_exits %s // ^D at an idle prompt exits the shell; " @@ -1861,6 +1871,7 @@ static const char *const KNOWN_TOP[] = { "keys", "min_mark", "min_pane", + "multi_attach", "min_split", "modal_scrim", "newtab_mark", @@ -1875,6 +1886,7 @@ static const char *const KNOWN_TOP[] = { "select_scroll_ms", "shaders", "shell", + "size_follows", "splash_ms", "states", "status_bar", @@ -2081,6 +2093,21 @@ static bool load_into(config_t *c, const char *path, int depth, char *err, (uint16_t)kdl_arg_int(kdl_child(root, "status_pad"), 0, c->status_pad); c->focus_follows_mouse = kdl_arg_bool(kdl_child(root, "focus_follows_mouse"), 0, c->focus_follows_mouse); + c->multi_attach = + kdl_arg_bool(kdl_child(root, "multi_attach"), 0, c->multi_attach); + const char *sizef = kdl_arg(kdl_child(root, "size_follows"), 0, NULL); + if (sizef) { + if (strcmp(sizef, "active") == 0) + c->size_follows = SIZE_FOLLOWS_ACTIVE; + else if (strcmp(sizef, "smallest") == 0) + c->size_follows = SIZE_FOLLOWS_SMALLEST; + else if (strcmp(sizef, "largest") == 0) + c->size_follows = SIZE_FOLLOWS_LARGEST; + else + complain(c, err, errcap, 0, + "size_follows wants active, smallest or largest, not \"%s\"", + sizef); + } c->in_band_shaders = kdl_arg_bool(kdl_child(root, "in_band_shaders"), 0, c->in_band_shaders); c->ctrl_d_exits = diff --git a/src/server.c b/src/server.c index f8f3dd3..7443648 100644 --- a/src/server.c +++ b/src/server.c @@ -351,16 +351,49 @@ static bool push_clients(server_t *s) { return ok; } -static void apply_client_size(server_t *s, conn_t *c) { - bool changed = c->cols != s->screen.cols || c->rows != s->screen.rows; +/* Size the canonical screen by policy (size_follows). "active" takes the + * active client's size; "smallest"/"largest" fold every attached display, so + * any attach, resize or departure can move the canvas. The active client + * still supplies the cell pixel size either way: graphics go to terminals, + * and the active one is the best guess at what a cell is worth. */ +static void apply_canvas(server_t *s) { + conn_t *a = active_conn(s); + int policy = app_cfg_size_follows(); + uint16_t cols = 0, rows = 0; + if (policy == SIZE_FOLLOWS_ACTIVE) { + if (a) { + cols = a->cols; + rows = a->rows; + } + } else { + for (size_t i = 0; i < s->nconns; i++) { + conn_t *c = &s->conns[i]; + if (!c->display || !c->cols || !c->rows) continue; + if (!cols) { + cols = c->cols; + rows = c->rows; + } else if (policy == SIZE_FOLLOWS_SMALLEST) { + if (c->cols < cols) cols = c->cols; + if (c->rows < rows) rows = c->rows; + } else { + if (c->cols > cols) cols = c->cols; + if (c->rows > rows) rows = c->rows; + } + } + } + if (!cols || !rows) { /* nobody is looking: leave the canvas standing */ + request_paint(s, true); + return; + } + bool changed = cols != s->screen.cols || rows != s->screen.rows; uint16_t old_w = 0, old_h = 0; app_cell_px(s->app, &old_w, &old_h); - uint16_t cell_w = c->cell_w ? c->cell_w : s->fallback_cell_w; - uint16_t cell_h = c->cell_h ? c->cell_h : s->fallback_cell_h; + uint16_t cell_w = a && a->cell_w ? a->cell_w : s->fallback_cell_w; + uint16_t cell_h = a && a->cell_h ? a->cell_h : s->fallback_cell_h; bool px_changed = cell_w != old_w || cell_h != old_h; if (changed) { - screen_resize(&s->screen, c->cols, c->rows); - app_resize(s->app, c->cols, c->rows); + screen_resize(&s->screen, cols, rows); + app_resize(s->app, cols, rows); } if (px_changed) app_set_cell_px(s->app, cell_w, cell_h); if (changed || px_changed) app_compose(s->app, &s->screen); @@ -371,7 +404,7 @@ static void make_active(server_t *s, conn_t *c, bool new_activity) { if (s->active_fd != c->fd) app_cancel_client_interaction(s->app); s->active_fd = c->fd; if (new_activity) c->activity = ++s->activity_seq; - apply_client_size(s, c); + apply_canvas(s); } static void promote_latest(server_t *s) { @@ -387,15 +420,24 @@ static void promote_latest(server_t *s) { return; } s->active_fd = latest->fd; - apply_client_size(s, latest); + apply_canvas(s); } +/* A non-active departure changes nothing under "active", but under + * "smallest"/"largest" the canvas may have been sized by the very client + * that just left. */ static void close_conn(server_t *s, size_t i) { - if (conn_close(s, i)) promote_latest(s); + if (conn_close(s, i)) + promote_latest(s); + else + apply_canvas(s); } static void drop_conn(server_t *s, size_t i, uint8_t reason) { - if (conn_drop(s, i, reason)) promote_latest(s); + if (conn_drop(s, i, reason)) + promote_latest(s); + else + apply_canvas(s); } static void drop_fd(server_t *s, int fd, uint8_t reason) { @@ -451,7 +493,7 @@ static bool feed_client(server_t *s, conn_t *c, const uint8_t *data, size_t len, input_timeout(c->in, client_event, &ctx); else input_feed(c->in, data, len, client_event, &ctx); - if (ctx.activity) apply_client_size(s, c); + if (ctx.activity) apply_canvas(s); if (input_pending(c->in)) c->esc_due = now_ms() + ESC_MS; return ctx.activity; } @@ -830,6 +872,22 @@ int server_run(const char *name, const char *const argv[], uint16_t cols, bool first_viewer = fresh && display_count(&s) == 0; if (!c->display && !fresh) break; + /* multi_attach false is the classic rule: the new client displaces + * every display before it (EXIT_REPLACED, which old clients already + * understand). Dropping compacts the array, so the index -- and the + * pointer taken from it -- are re-derived afterwards. */ + if (fresh && !app_cfg_multi_attach()) { + for (size_t j = 0; j < s.nconns;) { + if (j != ci && s.conns[j].display) { + conn_drop(&s, j, EXIT_REPLACED); + if (j < ci) ci--; /* the array compacted under us */ + } else + j++; + } + c = &s.conns[ci]; + first_viewer = display_count(&s) == 0; + } + uint16_t cols = c->cols ? c->cols : s.screen.cols; uint16_t rows = c->rows ? c->rows : s.screen.rows; if (m.len >= 4) { @@ -868,7 +926,10 @@ int server_run(const char *name, const char *const argv[], uint16_t cols, c->cell_w = cell_w; c->cell_h = cell_h; if (local_resize) screen_resize(&c->view, cols, rows); - if (c->fd == s.active_fd) apply_client_size(&s, c); + /* Unconditional: under "active" a non-active resize changes + * nothing (apply_canvas reads the active client), but under + * "smallest"/"largest" every display's size is a vote. */ + apply_canvas(&s); } update_viewport(c, &s.screen); request_paint(&s, true); @@ -960,6 +1021,19 @@ int server_run(const char *name, const char *const argv[], uint16_t cols, * include becomes ten minutes of wondering. */ const char *why = app_config_complaint(); app_toast(s.app, why && *why ? why : "config reloaded"); + /* The knobs this loop lives by may have moved. multi_attach turned + * off evicts every display but the active one -- the reloaded config + * says one client, so one client it is -- and a changed size_follows + * re-sizes the canvas to the policy now in force. */ + if (!app_cfg_multi_attach()) { + for (size_t i = 0; i < s.nconns;) { + if (s.conns[i].display && s.conns[i].fd != s.active_fd) + drop_conn(&s, i, EXIT_REPLACED); + else + i++; + } + } + apply_canvas(&s); } else { app_toast(s.app, err[0] ? err : "config reload failed"); } |
