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 c693fca3..ef3453b4 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. */ @@ -5247,7 +5253,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); @@ -5266,7 +5272,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); @@ -5532,10 +5538,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("")); @@ -5702,6 +5714,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)); } @@ -5714,6 +5727,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)); } diff --git a/input.c b/input.c index 671f5788..07f0ce76 100644 --- a/input.c +++ b/input.c @@ -2663,12 +2663,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/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/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/options-table.c b/options-table.c index 9070d94d..15da714c 100644 --- a/options-table.c +++ b/options-table.c @@ -1304,7 +1304,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." }, diff --git a/screen.c b/screen.c index 63c6009c..c93a689b 100644 --- a/screen.c +++ b/screen.c @@ -245,19 +245,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/server-client.c b/server-client.c index 27131a89..ddc81032 100644 --- a/server-client.c +++ b/server-client.c @@ -41,6 +41,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 *); @@ -2056,6 +2057,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); } @@ -2122,6 +2124,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/session.c b/session.c index f58ec3f6..1e558977 100644 --- a/session.c +++ b/session.c @@ -229,24 +229,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 964d0334..1c3c2a14 100644 --- a/spawn.c +++ b/spawn.c @@ -82,6 +82,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); @@ -180,8 +181,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/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) diff --git a/tmux.1 b/tmux.1 index a678b1a5..3ec0457a 100644 --- a/tmux.1 +++ b/tmux.1 @@ -4489,6 +4489,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 @@ -7267,7 +7269,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 , @@ -7855,6 +7856,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.c b/tmux.c index 8d390203..251b0336 100644 --- a/tmux.c +++ b/tmux.c @@ -281,6 +281,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 6d420e09..56d96165 100644 --- a/tmux.h +++ b/tmux.h @@ -638,6 +638,7 @@ enum tty_code_code { TTYC_SMUL, TTYC_SMULX, TTYC_SMXX, + TTYC_SPB, TTYC_SXL, TTYC_SS, TTYC_SWD, @@ -2017,6 +2018,7 @@ struct client { char *title; char *path; const char *cwd; + struct progress_bar progress_bar; char *term_name; int term_features; @@ -2361,6 +2363,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); @@ -2655,6 +2658,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 *); @@ -3330,7 +3334,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); @@ -3636,7 +3640,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/tty-features.c b/tty-features.c index c620c383..74e7922f 100644 --- a/tty-features.c +++ b/tty-features.c @@ -357,6 +357,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, @@ -372,6 +383,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, @@ -475,45 +487,56 @@ 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," + "progressbar" }, { .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," + "progressbar" }, { .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", /* @@ -522,10 +545,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); diff --git a/tty-term.c b/tty-term.c index 29dbaf59..39bfd9d6 100644 --- a/tty-term.c +++ b/tty-term.c @@ -280,6 +280,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 4f23d583..1fdd5c2f 100644 --- a/tty.c +++ b/tty.c @@ -3053,3 +3053,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); +} diff --git a/window-clock.c b/window-clock.c index ecac43ec..27bde027 100644 --- a/window-clock.c +++ b/window-clock.c @@ -279,6 +279,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'; @@ -299,7 +300,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; 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; diff --git a/window.c b/window.c index 216bf351..c9a0fbfc 100644 --- a/window.c +++ b/window.c @@ -406,9 +406,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