From bdd78ce38e8708006f87c424cd93437db2881abc Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 3 Apr 2026 09:14:27 +0000 Subject: [PATCH] Handle OSC 9;4 progress bar sequence and store in format variables, from Eric Dorland in GitHub issue 4954. --- format.c | 38 +++++++++++++++++++++++++++++++++++++ input.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- tmux.1 | 2 ++ tmux.h | 16 ++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/format.c b/format.c index e30c67d8..9feb0f46 100644 --- a/format.c +++ b/format.c @@ -2248,6 +2248,38 @@ format_cb_pane_pipe_pid(struct format_tree *ft) return (value); } +/* Callback for pane_pb_progress. */ +static void * +format_cb_pane_pb_progress(struct format_tree *ft) +{ + char *value = NULL; + + if (ft->wp != NULL) + xasprintf(&value, "%d", ft->wp->base.progress_bar.progress); + return (value); +} + +/* Callback for pane_pb_state. */ +static void * +format_cb_pane_pb_state(struct format_tree *ft) +{ + if (ft->wp != NULL) { + switch (ft->wp->base.progress_bar.state) { + case PROGRESS_BAR_HIDDEN: + return xstrdup("hidden"); + case PROGRESS_BAR_NORMAL: + return xstrdup("normal"); + case PROGRESS_BAR_ERROR: + return xstrdup("error"); + case PROGRESS_BAR_INDETERMINATE: + return xstrdup("indeterminate"); + case PROGRESS_BAR_PAUSED: + return xstrdup("paused"); + } + } + return (NULL); +} + /* Callback for pane_right. */ static void * format_cb_pane_right(struct format_tree *ft) @@ -3331,6 +3363,12 @@ static const struct format_table_entry format_table[] = { { "pane_path", FORMAT_TABLE_STRING, format_cb_pane_path }, + { "pane_pb_progress", FORMAT_TABLE_STRING, + format_cb_pane_pb_progress + }, + { "pane_pb_state", FORMAT_TABLE_STRING, + format_cb_pane_pb_state + }, { "pane_pid", FORMAT_TABLE_STRING, format_cb_pane_pid }, diff --git a/input.c b/input.c index 24467144..11d481fa 100644 --- a/input.c +++ b/input.c @@ -164,6 +164,7 @@ static void input_reset_cell(struct input_ctx *); static void input_report_current_theme(struct input_ctx *); static void input_osc_4(struct input_ctx *, const char *); static void input_osc_8(struct input_ctx *, const char *); +static void input_osc_9(struct input_ctx *, const char *); static void input_osc_10(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *); static void input_osc_12(struct input_ctx *, const char *); @@ -2636,6 +2637,9 @@ input_exit_osc(struct input_ctx *ictx) case 8: input_osc_8(ictx, p); break; + case 9: + input_osc_9(ictx, p); + break; case 10: input_osc_10(ictx, p); break; @@ -2904,6 +2908,57 @@ bad: free(id); } +/* Helper to handle setting the progress bar and redrawing. */ +static void +input_set_progress_bar(struct input_ctx *ictx, enum progress_bar_state state, + int p) +{ + screen_set_progress_bar(ictx->ctx.s, state, p); + if (ictx->wp != NULL) { + server_redraw_window_borders(ictx->wp->window); + server_status_window(ictx->wp->window); + } +} + +/* Handle the OSC 9;4 sequence for progress bars. */ +static void +input_osc_9(struct input_ctx *ictx, const char *p) +{ + const char *pb = p; + enum progress_bar_state state; + int progress = 0; + + if (*pb++ != '4') + return; + if (*pb == '\0' || (*pb == ';' && pb[1] == '\0')) + return; + + if (*pb++ != ';') + return; + if (*pb < '0' || *pb > '4') + goto bad; + state = *pb++ - '0'; + + if (*pb == '\0' || (*pb == ';' && pb[1] == '\0')) { + input_set_progress_bar(ictx, state, -1); + return; + } + + if (*pb++ != ';') + goto bad; + while (*pb >= '0' && *pb <= '9') { + if (progress > 100) + goto bad; + progress = progress * 10 + *pb++ - '0'; + } + if (*pb != '\0' || progress < 0 || progress > 100) + goto bad; + input_set_progress_bar(ictx, state, progress); + return; + +bad: + log_debug("bad OSC 9;4 %s", p); +} /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void @@ -3114,7 +3169,7 @@ input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out, return (0); } - len = (strlen(end) / 4) * 3; + len = ((strlen(end) + 3) / 4) * 3; if (len == 0) return (0); diff --git a/tmux.1 b/tmux.1 index 998a2cbd..80824d3b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6380,6 +6380,8 @@ The following variables are available, where appropriate: .It Li "pane_pid" Ta "" Ta "PID of first process in pane" .It Li "pane_pipe" Ta "" Ta "1 if pane is being piped" .It Li "pane_pipe_pid" Ta "" Ta "PID of pipe process, if any" +.It Li "pane_pb_state" Ta "" Ta "Pane progress bar state, one of hidden, normal, error, indeterminate, paused (can be set by application)" +.It Li "pane_pb_progress" Ta "" Ta "Pane progress bar progress percentage (can be set by application)" .It Li "pane_right" Ta "" Ta "Right of pane" .It Li "pane_search_string" Ta "" Ta "Last search string in copy mode" .It Li "pane_start_command" Ta "" Ta "Command pane started with" diff --git a/tmux.h b/tmux.h index 8d631ff9..cab5b694 100644 --- a/tmux.h +++ b/tmux.h @@ -920,6 +920,20 @@ enum screen_cursor_style { SCREEN_CURSOR_BAR }; + +/* Progress bar, OSC 9;4. */ +enum progress_bar_state { + PROGRESS_BAR_HIDDEN = 0, + PROGRESS_BAR_NORMAL = 1, + PROGRESS_BAR_ERROR = 2, + PROGRESS_BAR_INDETERMINATE = 3, + PROGRESS_BAR_PAUSED = 4 +}; +struct progress_bar { + enum progress_bar_state state; + int progress; +}; + /* Virtual screen. */ struct screen_sel; struct screen_titles; @@ -956,6 +970,7 @@ struct screen { struct screen_write_cline *write_list; struct hyperlinks *hyperlinks; + struct progress_bar progress_bar; }; /* Screen write context. */ @@ -3233,6 +3248,7 @@ int screen_set_title(struct screen *, const char *); void 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); void screen_resize(struct screen *, u_int, u_int, int); void screen_resize_cursor(struct screen *, u_int, u_int, int, int, int); void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int,