From baf55e4616fe50563ec9f46fbba05bac959912e5 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 06:57:08 +0000 Subject: [PATCH 1/9] Add a default set of features for WezTerm. --- tty-features.c | 71 ++++++++++++++++++++++++++++---------------------- tty-keys.c | 2 ++ 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/tty-features.c b/tty-features.c index 623e8f20..ce560b88 100644 --- a/tty-features.c +++ b/tty-features.c @@ -465,45 +465,54 @@ tty_default_features(int *feat, const char *name, u_int version) "256,RGB,bpaste,clipboard,mouse,strikethrough,title" { .name = "mintty", .features = TTY_FEATURES_BASE_MODERN_XTERM "," - "ccolour," - "cstyle," - "extkeys," - "margins," - "overline," - "usstyle" + "ccolour," + "cstyle," + "extkeys," + "margins," + "overline," + "usstyle" }, { .name = "tmux", .features = TTY_FEATURES_BASE_MODERN_XTERM "," - "ccolour," - "cstyle," - "extkeys," - "focus," - "overline," - "usstyle," - "hyperlinks" + "ccolour," + "cstyle," + "extkeys," + "focus," + "overline," + "usstyle," + "hyperlinks" }, { .name = "rxvt-unicode", .features = "256," - "bpaste," - "ccolour," - "cstyle," - "mouse," - "title," - "ignorefkeys" + "bpaste," + "ccolour," + "cstyle," + "mouse," + "title," + "ignorefkeys" }, { .name = "iTerm2", .features = TTY_FEATURES_BASE_MODERN_XTERM "," - "cstyle," - "extkeys," - "margins," - "usstyle," - "sync," - "osc7,hyperlinks" + "cstyle," + "extkeys," + "margins," + "usstyle," + "sync," + "osc7," + "hyperlinks" }, { .name = "foot", .features = TTY_FEATURES_BASE_MODERN_XTERM "," - "cstyle," - "extkeys" + "cstyle," + "extkeys" + }, + { .name = "WezTerm", + .features = TTY_FEATURES_BASE_MODERN_XTERM "," + "ccolour," + "cstyle," + "extkeys," + "focus," + "usstyle" }, { .name = "XTerm", /* @@ -512,10 +521,10 @@ tty_default_features(int *feat, const char *name, u_int version) * secondary DA shows VT420. */ .features = TTY_FEATURES_BASE_MODERN_XTERM "," - "ccolour," - "cstyle," - "extkeys," - "focus" + "ccolour," + "cstyle," + "extkeys," + "focus" } }; u_int i; diff --git a/tty-keys.c b/tty-keys.c index 05ec63c8..1336188d 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1640,6 +1640,8 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, tty_default_features(features, "mintty", 0); else if (strncmp(tmp, "foot(", 5) == 0) tty_default_features(features, "foot", 0); + else if (strncmp(tmp, "WezTerm ", 7) == 0) + tty_default_features(features, "WezTerm", 0); log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); free(c->term_type); From 0835980ef16d09483d80ab286c82fb39edd9fc32 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 06:58:59 +0000 Subject: [PATCH 2/9] Remove no longer accurate statement from tmux.1, reported by dkuettel at gmail dot com. --- tmux.1 | 1 - 1 file changed, 1 deletion(-) diff --git a/tmux.1 b/tmux.1 index ae86c1dc..4c3744bb 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7265,7 +7265,6 @@ Display a popup running when omitted) on .Ar target\-client . A popup is a rectangular box drawn over the top of any panes. -Panes are not updated while a popup is present. If the command is run inside an existing popup, that popup is modified. Only the .Fl b , From 314f0ae9649134ad508306199c75b2f4a7e4a2bb Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:03:06 +0000 Subject: [PATCH 3/9] Do not leak hyperlinks in copy mode, from Barrett Ruth in GitHub issue 5020. --- window-copy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/window-copy.c b/window-copy.c index 16626563..ac89dc56 100644 --- a/window-copy.c +++ b/window-copy.c @@ -463,8 +463,10 @@ window_copy_init(struct window_mode_entry *wme, data->scroll_exit = args_has(args, 'e'); data->hide_position = args_has(args, 'H'); - if (base->hyperlinks != NULL) + if (base->hyperlinks != NULL) { + hyperlinks_free(data->screen.hyperlinks); data->screen.hyperlinks = hyperlinks_copy(base->hyperlinks); + } data->screen.cx = data->cx; data->screen.cy = data->cy; data->mx = data->cx; From 303edb71bda9515605e41bdb323203bc24d15c90 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:05:03 +0000 Subject: [PATCH 4/9] Add a fairly low time limit to format evaluation to stop absurdly nested formats from making tmux appear to hang. --- format.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/format.c b/format.c index 88fb227a..90537879 100644 --- a/format.c +++ b/format.c @@ -120,6 +120,9 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 +/* Limit on time taken (milliseconds). */ +#define FORMAT_TIME_LIMIT 100 + /* Format expand flags. */ #define FORMAT_EXPAND_TIME 0x1 #define FORMAT_EXPAND_NOJOBS 0x2 @@ -169,9 +172,11 @@ RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp); struct format_expand_state { struct format_tree *ft; u_int loop; + uint64_t start_time; + int flags; + time_t time; struct tm tm; - int flags; }; /* Format modifier. */ @@ -292,6 +297,7 @@ format_copy_state(struct format_expand_state *to, to->time = from->time; memcpy(&to->tm, &from->tm, sizeof to->tm); to->flags = from->flags|flags; + to->start_time = from->start_time; } /* Format job update callback. */ @@ -5514,10 +5520,16 @@ format_expand1(struct format_expand_state *es, const char *fmt) size_t off, len, n, outlen; int ch, brackets; char expanded[8192]; + uint64_t t = get_timer(); if (fmt == NULL || *fmt == '\0') return (xstrdup("")); + if (t - es->start_time >= FORMAT_TIME_LIMIT) { + format_log(es, "reached time limit (%llu)", + (unsigned long long)(t - es->start_time)); + return (xstrdup("")); + } if (es->loop == FORMAT_LOOP_LIMIT) { format_log(es, "reached loop limit (%u)", FORMAT_LOOP_LIMIT); return (xstrdup("")); @@ -5683,6 +5695,7 @@ format_expand_time(struct format_tree *ft, const char *fmt) memset(&es, 0, sizeof es); es.ft = ft; es.flags = FORMAT_EXPAND_TIME; + es.start_time = get_timer(); return (format_expand1(&es, fmt)); } @@ -5695,6 +5708,7 @@ format_expand(struct format_tree *ft, const char *fmt) memset(&es, 0, sizeof es); es.ft = ft; es.flags = 0; + es.start_time = get_timer(); return (format_expand1(&es, fmt)); } From fee70031f6a2f464859447a1a146bdd6a7f6ecdc Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:05:59 +0000 Subject: [PATCH 5/9] Make clock visible on terminals without colours, from Manuel Einfalt in GitHub issue 5001. --- window-clock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/window-clock.c b/window-clock.c index 81d33858..f3dfb206 100644 --- a/window-clock.c +++ b/window-clock.c @@ -282,6 +282,7 @@ window_clock_draw_screen(struct window_mode_entry *wme) memcpy(&gc, &grid_default_cell, sizeof gc); gc.flags |= GRID_FLAG_NOPALETTE; gc.bg = colour; + gc.fg = colour; for (ptr = tim; *ptr != '\0'; ptr++) { if (*ptr >= '0' && *ptr <= '9') idx = *ptr - '0'; @@ -302,7 +303,7 @@ window_clock_draw_screen(struct window_mode_entry *wme) for (i = 0; i < 5; i++) { screen_write_cursormove(&ctx, x + i, y + j, 0); if (window_clock_table[idx][j][i]) - screen_write_putc(&ctx, &gc, ' '); + screen_write_putc(&ctx, &gc, '#'); } } x += 6; From d339ab51eb1b535362d58b4a8112e5921499399e Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:10:16 +0000 Subject: [PATCH 6/9] Sanitize pane titles and window and session names more consistently and strictly, prevents C0 characters and other nonvisible characters causing problems. Reported (with a different fix) by Chris Monardo in GitHub issue 4999. --- cmd-new-session.c | 4 ++-- cmd-rename-session.c | 2 +- format.c | 4 ++-- input.c | 9 +++------ names.c | 4 +++- screen.c | 17 +++++++++++++---- session.c | 18 ------------------ spawn.c | 8 ++++++-- tmux.c | 18 ++++++++++++++++++ tmux.h | 4 ++-- window.c | 11 ++++++++--- 11 files changed, 58 insertions(+), 41 deletions(-) diff --git a/cmd-new-session.c b/cmd-new-session.c index a3e74888..a397ccf9 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -102,7 +102,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) tmp = args_get(args, 's'); if (tmp != NULL) { name = format_single(item, tmp, c, NULL, NULL, NULL); - newname = session_check_name(name); + newname = clean_name(name, "#:."); if (newname == NULL) { cmdq_error(item, "invalid session: %s", name); free(name); @@ -142,7 +142,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) else if (groupwith != NULL) prefix = xstrdup(groupwith->name); else { - prefix = session_check_name(group); + prefix = clean_name(group, "#:."); if (prefix == NULL) { cmdq_error(item, "invalid session group: %s", group); diff --git a/cmd-rename-session.c b/cmd-rename-session.c index 694f3c97..37b26100 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -52,7 +52,7 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item) char *newname, *tmp; tmp = format_single_from_target(item, args_string(args, 0)); - newname = session_check_name(tmp); + newname = clean_name(tmp, "#:."); if (newname == NULL) { cmdq_error(item, "invalid session: %s", tmp); free(tmp); diff --git a/format.c b/format.c index 90537879..9d93db1d 100644 --- a/format.c +++ b/format.c @@ -5235,7 +5235,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, value = format_search(search, wp, new); } free(new); - } else if (modifiers & FORMAT_REPEAT) { + } else if (modifiers & FORMAT_REPEAT) { /* Repeat multiple times. */ if (format_choose(es, copy, &left, &right, 1) != 0) { format_log(es, "repeat syntax error: %s", copy); @@ -5254,7 +5254,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, } free(right); free(left); - } else if (modifiers & FORMAT_NOT) { + } else if (modifiers & FORMAT_NOT) { value = format_bool_op_1(es, copy, 1); } else if (modifiers & FORMAT_NOT_NOT) { value = format_bool_op_1(es, copy, 0); diff --git a/input.c b/input.c index 11d481fa..d9c9344a 100644 --- a/input.c +++ b/input.c @@ -2626,12 +2626,9 @@ input_exit_osc(struct input_ctx *ictx) input_osc_4(ictx, p); break; case 7: - if (utf8_isvalid(p)) { - screen_set_path(sctx->s, p); - if (wp != NULL) { - server_redraw_window_borders(wp->window); - server_status_window(wp->window); - } + if (screen_set_path(sctx->s, p) && wp != NULL) { + server_redraw_window_borders(wp->window); + server_status_window(wp->window); } break; case 8: diff --git a/names.c b/names.c index aeb67338..8a04df73 100644 --- a/names.c +++ b/names.c @@ -166,7 +166,9 @@ parse_window_name(const char *in) if (*name == '/') name = basename(name); - name = xstrdup(name); + name = clean_name(name, "#"); free(copy); + if (name == NULL) + return (xstrdup("")); return (name); } diff --git a/screen.c b/screen.c index 617ac24c..b87c0aea 100644 --- a/screen.c +++ b/screen.c @@ -232,19 +232,28 @@ screen_set_cursor_colour(struct screen *s, int colour) int screen_set_title(struct screen *s, const char *title) { - if (!utf8_isvalid(title)) + char *new_title; + + new_title = clean_name(title, "#"); + if (new_title == NULL) return (0); free(s->title); - s->title = xstrdup(title); + s->title = new_title; return (1); } /* Set screen path. */ -void +int screen_set_path(struct screen *s, const char *path) { + char *new_path; + + new_path = clean_name(path, "#"); + if (new_path == NULL) + return (0); free(s->path); - utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); + s->path = new_path; + return (1); } /* Push the current title onto the stack. */ diff --git a/session.c b/session.c index 06f83a46..90894bf6 100644 --- a/session.c +++ b/session.c @@ -231,24 +231,6 @@ session_destroy(struct session *s, int notify, const char *from) session_remove_ref(s, __func__); } -/* Sanitize session name. */ -char * -session_check_name(const char *name) -{ - char *copy, *cp, *new_name; - - if (*name == '\0') - return (NULL); - copy = xstrdup(name); - for (cp = copy; *cp != '\0'; cp++) { - if (*cp == ':' || *cp == '.') - *cp = '_'; - } - utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); - free(copy); - return (new_name); -} - /* Lock session if it has timed out. */ static void session_lock_timer(__unused int fd, __unused short events, void *arg) diff --git a/spawn.c b/spawn.c index 3b5fae26..1d284144 100644 --- a/spawn.c +++ b/spawn.c @@ -84,6 +84,7 @@ spawn_window(struct spawn_context *sc, char **cause) struct winlink *wl; int idx = sc->idx; u_int sx, sy, xpixel, ypixel; + char *name; spawn_log(__func__, sc); @@ -182,8 +183,11 @@ spawn_window(struct spawn_context *sc, char **cause) if (~sc->flags & SPAWN_RESPAWN) { free(w->name); if (sc->name != NULL) { - w->name = format_single(item, sc->name, c, s, NULL, - NULL); + name = format_single(item, sc->name, c, s, NULL, NULL); + w->name = clean_name(name, "#"); + free(name); + if (w->name == NULL) + w->name = xstrdup(""); options_set_number(w->options, "automatic-rename", 0); } else w->name = default_window_name(w); diff --git a/tmux.c b/tmux.c index 261cfd57..d7ca7e8b 100644 --- a/tmux.c +++ b/tmux.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "tmux.h" @@ -285,6 +286,23 @@ get_timer(void) return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL)); } +char * +clean_name(const char *name, const char* forbid) +{ + char *copy, *cp, *new_name; + + if (*name == '\0' || !utf8_isvalid(name)) + return (NULL); + copy = xstrdup(name); + for (cp = copy; *cp != '\0'; cp++) { + if (strchr(forbid, *cp) != NULL) + *cp = '_'; + } + utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); + free(copy); + return (new_name); +} + const char * sig2name(int signo) { diff --git a/tmux.h b/tmux.h index 00a1c94a..802278d8 100644 --- a/tmux.h +++ b/tmux.h @@ -2326,6 +2326,7 @@ int checkshell(const char *); void setblocking(int, int); char *shell_argv0(const char *, int); uint64_t get_timer(void); +char *clean_name(const char *, const char *); const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); @@ -3281,7 +3282,7 @@ void screen_set_default_cursor(struct screen *, struct options *); void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *); void screen_set_cursor_colour(struct screen *, int); int screen_set_title(struct screen *, const char *); -void screen_set_path(struct screen *, const char *); +int screen_set_path(struct screen *, const char *); void screen_push_title(struct screen *); void screen_pop_title(struct screen *); void screen_set_progress_bar(struct screen *, enum progress_bar_state, int); @@ -3587,7 +3588,6 @@ struct session *session_create(const char *, const char *, const char *, void session_destroy(struct session *, int, const char *); void session_add_ref(struct session *, const char *); void session_remove_ref(struct session *, const char *); -char *session_check_name(const char *); void session_update_activity(struct session *, struct timeval *); struct session *session_next_session(struct session *, struct sort_criteria *); struct session *session_previous_session(struct session *, diff --git a/window.c b/window.c index c97a3875..92337077 100644 --- a/window.c +++ b/window.c @@ -408,9 +408,14 @@ window_remove_ref(struct window *w, const char *from) void window_set_name(struct window *w, const char *new_name) { - free(w->name); - utf8_stravis(&w->name, new_name, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); - notify_window("window-renamed", w); + char *name; + + name = clean_name(new_name, "#"); + if (name != NULL) { + free(w->name); + utf8_stravis(&w->name, new_name, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); + notify_window("window-renamed", w); + } } void From d36f6783c74f1df8d1cb645ff7c3b3aa5e234f38 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:13:26 +0000 Subject: [PATCH 7/9] Add a couple of controls (kill, zoom) to default pane-status-format. Will be more to come with floating panes. From Dane Jensen in GitHub issue 4981. --- key-bindings.c | 4 ++++ options-table.c | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/key-bindings.c b/key-bindings.c index 9bc69045..16e77f52 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -467,6 +467,10 @@ key_bindings_init(void) "bind -n MouseDown1Status { switch-client -t= }", "bind -n C-MouseDown1Status { swap-window -t@ }", + /* Mouse button 1 down on default pane-border-format */ + "bind -n MouseDown1Control9 { display-menu -t= -xM -yM -O -T 'Kill pane #{pane_index}?' 'Yes' 'y' { kill-pane -t= } 'No' 'n' {}}", + "bind -n MouseDown1Control8 { resize-pane -Z }", + /* Mouse wheel down on status line. */ "bind -n WheelDownStatus { next-window }", diff --git a/options-table.c b/options-table.c index 6d1e6552..4f1b6d2d 100644 --- a/options-table.c +++ b/options-table.c @@ -1305,7 +1305,14 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "#{?pane_active,#[reverse],}#{pane_index}#[default] " - "\"#{pane_title}\"", + "\"#{pane_title}\"" + "#{?#{mouse}," + "#[align=right]" + "#[range=control|8][" + "#{?#{window_zoomed_flag},u,z}" + "]#[norange]" + "#[range=control|9][x]#[norange]" + ",}", .text = "Format of text in the pane status lines." }, From 7a0cc03532a341026608a4991024af8452b0da67 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:15:34 +0000 Subject: [PATCH 8/9] Translate keypad keys to text in prompt input. From Barrett Ruth in GitHub issue 4996. --- status.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/status.c b/status.c index 5ecf7bf9..f1e74c91 100644 --- a/status.c +++ b/status.c @@ -972,6 +972,49 @@ status_prompt_space(const struct utf8_data *ud) return (*ud->data == ' '); } +static key_code +status_prompt_keypad_key(key_code key) +{ + if (key & KEYC_MASK_MODIFIERS) + return (key); + + switch (key) { + case KEYC_KP_SLASH: + return ('/'); + case KEYC_KP_STAR: + return ('*'); + case KEYC_KP_MINUS: + return ('-'); + case KEYC_KP_SEVEN: + return ('7'); + case KEYC_KP_EIGHT: + return ('8'); + case KEYC_KP_NINE: + return ('9'); + case KEYC_KP_PLUS: + return ('+'); + case KEYC_KP_FOUR: + return ('4'); + case KEYC_KP_FIVE: + return ('5'); + case KEYC_KP_SIX: + return ('6'); + case KEYC_KP_ONE: + return ('1'); + case KEYC_KP_TWO: + return ('2'); + case KEYC_KP_THREE: + return ('3'); + case KEYC_KP_ENTER: + return ('\r'); + case KEYC_KP_ZERO: + return ('0'); + case KEYC_KP_PERIOD: + return ('.'); + } + return (key); +} + /* * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key * as an emacs key; return 2 to append to the buffer. @@ -1383,6 +1426,9 @@ status_prompt_key(struct client *c, key_code key) } size = utf8_strlen(c->prompt_buffer); + key &= ~KEYC_MASK_FLAGS; + key = status_prompt_keypad_key(key); + if (c->prompt_flags & PROMPT_NUMERIC) { if (key >= '0' && key <= '9') goto append_key; @@ -1392,7 +1438,6 @@ status_prompt_key(struct client *c, key_code key) free(s); return (1); } - key &= ~KEYC_MASK_FLAGS; if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) From bc15723f7f888422a284efda9f77343cf25b4840 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 22 Apr 2026 07:25:17 +0000 Subject: [PATCH 9/9] Add feature for progress bar and pass to outside terminal, GitHu issue 4972 from Eric Dorland. --- server-client.c | 19 +++++++++++++++++++ tmux.1 | 4 ++++ tmux.h | 3 +++ tty-features.c | 18 ++++++++++++++++-- tty-term.c | 1 + tty.c | 7 +++++++ 6 files changed, 50 insertions(+), 2 deletions(-) diff --git a/server-client.c b/server-client.c index d6a6159e..5d090dab 100644 --- a/server-client.c +++ b/server-client.c @@ -45,6 +45,7 @@ static void server_client_check_redraw(struct client *); static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); static void server_client_set_path(struct client *); +static void server_client_set_progress_bar(struct client *); static void server_client_reset_state(struct client *); static void server_client_update_latest(struct client *); static void server_client_dispatch(struct imsg *, void *); @@ -2060,6 +2061,7 @@ server_client_check_redraw(struct client *c) server_client_set_title(c); server_client_set_path(c); } + server_client_set_progress_bar(c); screen_redraw_screen(c); } @@ -2126,6 +2128,23 @@ server_client_set_path(struct client *c) } } +/* Set client progress bar. */ +static void +server_client_set_progress_bar(struct client *c) +{ + struct session *s = c->session; + struct progress_bar *pane_pb; + + if (s->curw == NULL) + return; + pane_pb = &s->curw->window->active->base.progress_bar; + if (pane_pb->state == c->progress_bar.state && + pane_pb->progress == c->progress_bar.progress) + return; + memcpy(&c->progress_bar, pane_pb, sizeof c->progress_bar); + tty_set_progress_bar(&c->tty, &c->progress_bar); +} + /* Dispatch message from client. */ static void server_client_dispatch(struct imsg *imsg, void *arg) diff --git a/tmux.1 b/tmux.1 index 4c3744bb..86ed67dc 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4487,6 +4487,8 @@ mouse sequences. Supports the OSC 7 working directory extension. .It overline Supports the overline SGR attribute. +.It progressbar +Supports the OSC 9;4 progress bar extension. .It rectfill Supports the DECFRA rectangle fill escape sequence. .It RGB @@ -7852,6 +7854,8 @@ $ printf \[aq]\e033[4 q\[aq] If .Em Se is not set, \&Ss with argument 0 will be used to reset the cursor style instead. +.It Em \&Spb +Set the state and progress for the OSC9;4 progress bar. .It Em \&Swd Set the opening sequence for the working directory notification. The sequence is terminated using the standard diff --git a/tmux.h b/tmux.h index 802278d8..63c1e30b 100644 --- a/tmux.h +++ b/tmux.h @@ -629,6 +629,7 @@ enum tty_code_code { TTYC_SMUL, TTYC_SMULX, TTYC_SMXX, + TTYC_SPB, TTYC_SXL, TTYC_SS, TTYC_SWD, @@ -1982,6 +1983,7 @@ struct client { char *title; char *path; const char *cwd; + struct progress_bar progress_bar; char *term_name; int term_features; @@ -2621,6 +2623,7 @@ void tty_repeat_requests(struct tty *, int); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); +void tty_set_progress_bar(struct tty *, struct progress_bar *); void tty_default_attributes(struct tty *, const struct grid_cell *, struct colour_palette *, u_int, struct hyperlinks *); void tty_update_mode(struct tty *, int, struct screen *); diff --git a/tty-features.c b/tty-features.c index ce560b88..3560bbb0 100644 --- a/tty-features.c +++ b/tty-features.c @@ -347,6 +347,17 @@ static const struct tty_feature tty_feature_sixel = { TERM_SIXEL }; +/* Terminal supports the OSC 9;4 progress bar. */ +static const char *const tty_feature_progressbar_capabilities[] = { + "Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\", + NULL +}; +static const struct tty_feature tty_feature_progressbar = { + "progressbar", + tty_feature_progressbar_capabilities, + 0 +}; + /* Available terminal features. */ static const struct tty_feature *const tty_features[] = { &tty_feature_256, @@ -362,6 +373,7 @@ static const struct tty_feature *const tty_features[] = { &tty_feature_mouse, &tty_feature_osc7, &tty_feature_overline, + &tty_feature_progressbar, &tty_feature_rectfill, &tty_feature_rgb, &tty_feature_sixel, @@ -480,7 +492,8 @@ tty_default_features(int *feat, const char *name, u_int version) "focus," "overline," "usstyle," - "hyperlinks" + "hyperlinks," + "progressbar" }, { .name = "rxvt-unicode", .features = "256," @@ -499,7 +512,8 @@ tty_default_features(int *feat, const char *name, u_int version) "usstyle," "sync," "osc7," - "hyperlinks" + "hyperlinks," + "progressbar" }, { .name = "foot", .features = TTY_FEATURES_BASE_MODERN_XTERM "," diff --git a/tty-term.c b/tty-term.c index f664260a..aa9cd5ef 100644 --- a/tty-term.c +++ b/tty-term.c @@ -277,6 +277,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_SMULX] = { TTYCODE_STRING, "Smulx" }, [TTYC_SMUL] = { TTYCODE_STRING, "smul" }, [TTYC_SMXX] = { TTYCODE_STRING, "smxx" }, + [TTYC_SPB] = { TTYCODE_STRING, "Spb" }, [TTYC_SS] = { TTYCODE_STRING, "Ss" }, [TTYC_SWD] = { TTYCODE_STRING, "Swd" }, [TTYC_SYNC] = { TTYCODE_STRING, "Sync" }, diff --git a/tty.c b/tty.c index 41cb71c1..34acbd83 100644 --- a/tty.c +++ b/tty.c @@ -2931,3 +2931,10 @@ tty_clipboard_query(struct tty *tty) evtimer_add(&tty->clipboard_timer, &tv); } } + +void +tty_set_progress_bar(struct tty *tty, struct progress_bar *pb) +{ + if (tty_term_has(tty->term, TTYC_SPB)) + tty_putcode_ii(tty, TTYC_SPB, pb->state, pb->progress); +}