From bf187170b16664743da48fae07d4956d86499153 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 13 Jun 2026 10:32:54 +0000 Subject: [PATCH] Extend client mode so the preview can be changed to a view with a summary of the client terminal and its features, intended to make troubleshooting easier. "choose-client -i" or the "i" key in the mode. --- cmd-choose-tree.c | 4 +- mode-tree.c | 14 ++++- tmux.1 | 5 +- tmux.h | 3 +- window-client.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 6 deletions(-) diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 6bb17fc2..87c29419 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = { .name = "choose-client", .alias = NULL, - .args = { "F:f:hK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-hkNrZ] [-F format] [-f filter] [-K key-format] " + .args = { "F:f:hiK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-hikNrZ] [-F format] [-f filter] [-K key-format] " "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/mode-tree.c b/mode-tree.c index 82535497..cecf7df8 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -49,6 +49,7 @@ struct mode_tree_data { const struct menu_item *menu; struct sort_criteria sort_crit; + const char *view_name; mode_tree_build_cb buildcb; mode_tree_draw_cb drawcb; @@ -700,6 +701,12 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, return (mti); } +void +mode_tree_view_name(struct mode_tree_data *mtd, const char *name) +{ + mtd->view_name = name; +} + void mode_tree_draw_as_parent(struct mode_tree_item *mti) { @@ -884,9 +891,12 @@ mode_tree_draw(struct mode_tree_data *mtd) screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL); if (mtd->sort_crit.order_seq != NULL) { - xasprintf(&text, " %s (sort: %s%s)", mti->name, + xasprintf(&text, " %s (sort: %s%s)%s%s%s", mti->name, sort_order_to_string(mtd->sort_crit.order), - mtd->sort_crit.reversed ? ", reversed" : ""); + mtd->sort_crit.reversed ? ", reversed" : "", + mtd->view_name == NULL ? "" : " (view: ", + mtd->view_name == NULL ? "" : mtd->view_name, + mtd->view_name == NULL ? "" : ")"); } else xasprintf(&text, " %s", mti->name); if (w - 2 >= strlen(text)) { diff --git a/tmux.1 b/tmux.1 index 67319f9c..70beac52 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2864,7 +2864,7 @@ The command works only if at least one client is attached. .It Xo .Ic choose\-tree -.Op Fl GhkNrswyZ +.Op Fl GhikNrswyZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl K Ar key\-format @@ -2916,6 +2916,7 @@ The following keys may be used in tree mode: .It Li "O" Ta "Change sort order" .It Li "r" Ta "Reverse sort order" .It Li "v" Ta "Toggle preview" +.It Li "i" Ta "Change view (preview and client information)" .It Li "F1 or C\-h" Ta "Display help" .It Li "q" Ta "Exit mode" .El @@ -2955,6 +2956,8 @@ first. .Pp .Fl N starts without the preview or if given twice with the larger preview. +.Fl i +starts showing client information instead of the preview. .Fl h hides the pane containing the mode. .Fl k diff --git a/tmux.h b/tmux.h index f381af4f..ea65525b 100644 --- a/tmux.h +++ b/tmux.h @@ -3565,6 +3565,7 @@ void mode_tree_resize(struct mode_tree_data *, u_int, u_int); struct mode_tree_item *mode_tree_add(struct mode_tree_data *, struct mode_tree_item *, void *, uint64_t, const char *, const char *, int); +void mode_tree_view_name(struct mode_tree_data *, const char *); void mode_tree_draw_as_parent(struct mode_tree_item *); void mode_tree_no_tag(struct mode_tree_item *); void mode_tree_align(struct mode_tree_item *, int); @@ -3606,7 +3607,7 @@ int window_copy_get_current_offset(struct window_pane *, u_int *, char *window_copy_get_hyperlink(struct window_pane *, u_int, u_int); void window_copy_set_line_numbers(struct window_pane *, int); -/* window-option.c */ +/* window-customize.c */ extern const struct window_mode window_customize_mode; /* names.c */ diff --git a/window-client.c b/window-client.c index 519222d9..e746e20f 100644 --- a/window-client.c +++ b/window-client.c @@ -47,6 +47,93 @@ static void window_client_key(struct window_mode_entry *, "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \ "}" +#define WINDOW_CLIENT_FEATURE(f) \ + "#{?#{I/f:" #f "}," \ + "#[fg=green],#[dim]}#{p/15:#{l:" #f "}}" \ + "#[default]" +static const char *window_client_info_lines[] = { + "Client Name #[acs]x#[default] " + "#{client_name} " + "#[dim](PID #{client_pid})#[default]", + "Session #[acs]x#[default] " + "#{session_name}", + "Attach Time #[acs]x#[default] " + "#{t:client_created} " + "#[dim](#{t/r:client_created})#[default]", + "Activity Time #[acs]x#[default] " + "#{t:client_created} " + "#[dim](#{t/r:client_created})#[default]", + "Terminal Type #[acs]x#[default] " + "#{?client_termtype,#{client_termtype},Unknown}", + "TERM #[acs]x#[default] " + "#{client_termname}", + "Size #[acs]x#[default] " + "#{client_width}x#{client_height} " + "#[dim](cell #{client_cell_width}x#{client_cell_height})#[default]", + "Bytes Written #[acs]x#[default] " + "#{client_written} " + "#[dim](#{client_discarded} discarded)#[default]", + + "Features #[acs]x#[default] " + WINDOW_CLIENT_FEATURE(256) " " + WINDOW_CLIENT_FEATURE(RGB) " " + WINDOW_CLIENT_FEATURE(bpaste) " " + WINDOW_CLIENT_FEATURE(ccolour), + " #[acs]x#[default] " + WINDOW_CLIENT_FEATURE(clipboard) " " + WINDOW_CLIENT_FEATURE(cstyle) " " + WINDOW_CLIENT_FEATURE(extkeys) " " + WINDOW_CLIENT_FEATURE(focus), + " #[acs]x#[default] " + WINDOW_CLIENT_FEATURE(hyperlinks) " " + WINDOW_CLIENT_FEATURE(ignorefkeys) " " + WINDOW_CLIENT_FEATURE(margins) " " + WINDOW_CLIENT_FEATURE(mouse), + " #[acs]x#[default] " + WINDOW_CLIENT_FEATURE(osc7) " " + WINDOW_CLIENT_FEATURE(overline) " " + WINDOW_CLIENT_FEATURE(progressbar) " " + WINDOW_CLIENT_FEATURE(rectfill), + " #[acs]x#[default] " + WINDOW_CLIENT_FEATURE(sixel) " " + WINDOW_CLIENT_FEATURE(strikethrough) " " + WINDOW_CLIENT_FEATURE(sync) " " + WINDOW_CLIENT_FEATURE(title), + " #[acs]x#[default] " + WINDOW_CLIENT_FEATURE(usstyle), + "#[acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]", + + "prefix #[acs]x#[default] " + "#{prefix}", + + "mouse #[acs]x#[default] " + "#{?mouse,#{?#{I/c:kmous},,#[fg=red]}on,#[dim]off} " + "#{?#{I/c:kmous},,#[align=right]unavailable: [kmous] missing}", + + "set-clipboard #[acs]x#[default] " + "#{?#{!=:#{set-clipboard},off},#{?#{I/f:clipboard},,#[fg=red]}#{set-clipboard},#[dim]off} " + "#{?#{I/f:clipboard},,#[align=right]unavailable: [Ms] missing}", + + "get-clipboard #[acs]x#[default] " + "#{?#{!=:#{get-clipboard},off},#{?#{I/f:clipboard},,#[fg=red]}#{get-clipboard},#[dim]off} " + "#{?#{I/f:clipboard},,#[align=right]unavailable: [Ms] missing}", + + "focus-events #[acs]x#[default] " + "#{?focus-events,#{?#{I/f:focus},,#[fg=red]}on,#[dim]off} " + "#{?#{I/f:focus},,#[align=right]unavailable: [Enfcs] or [Dcfcs] missing}", + + "extended-keys #[acs]x#[default] " + "#{?#{!=:#{extended-keys},off},#{?#{I/f:extkeys},,#[fg=red]}#{extended-keys},#[dim]off} " + "#{?#{I/f:extkeys},,#[align=right]unavailable: [Eneks] or [Dseks] missing}", + + "set-titles #[acs]x#[default] " + "#{?set-titles,on,#[dim]off}", + + "escape-time #[acs]x#[default] " + "#{escape-time} ms", +}; + + static const struct menu_item window_client_menu_items[] = { { "Detach", 'd', NULL }, { "Detach Tagged", 'D', NULL }, @@ -82,7 +169,9 @@ struct window_client_modedata { char *format; char *key_format; char *command; + int hide_preview_this_pane; + int preview_is_info; struct window_client_itemdata **item_list; u_int item_size; @@ -162,6 +251,32 @@ window_client_build(void *modedata, struct sort_criteria *sort_crit, } } +static void +window_client_draw_info(__unused void *modedata, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) +{ + struct window_client_itemdata *item = itemdata; + struct client *c = item->c; + struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy, i; + struct format_tree *ft; + char *expanded; + + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); + + screen_write_cursormove(ctx, cx, cy, 0); + for (i = 0; i < nitems(window_client_info_lines); i++) { + if (i == sy) + break; + expanded = format_expand(ft, window_client_info_lines[i]); + screen_write_cursormove(ctx, cx, cy + i, 0); + format_draw(ctx, &grid_default_cell, sx, expanded, NULL, 0); + free(expanded); + } + + format_free(ft); +} + static void window_client_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, u_int sx, u_int sy) @@ -175,6 +290,10 @@ window_client_draw(void *modedata, void *itemdata, if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; + if (data->preview_is_info) { + window_client_draw_info(modedata, itemdata, ctx, sx, sy); + return; + } wp = c->session->curw->window->active; if (data->hide_preview_this_pane && wp == data->wp) { if (!TAILQ_EMPTY(&c->session->curw->window->last_panes)) @@ -250,6 +369,7 @@ window_client_sort(struct sort_criteria *sort_crit) } static const char* window_client_help_lines[] = { + "\r\033[1m i \033[0m\016x\017 \033[0mToggle info view\n", "\r\033[1m Enter \033[0m\016x\017 \033[0mChoose selected %1\n", "\r\033[1m d \033[0m\016x\017 \033[0mDetach selected %1\n", "\r\033[1m D \033[0m\016x\017 \033[0mDetach tagged %1s\n", @@ -280,6 +400,7 @@ window_client_init(struct window_mode_entry *wme, wme->data = data = xcalloc(1, sizeof *data); data->wp = wp; data->hide_preview_this_pane = args != NULL && args_has(args, 'h'); + data->preview_is_info = args != NULL && args_has(args, 'i'); if (args == NULL || !args_has(args, 'F')) data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT); @@ -300,6 +421,11 @@ window_client_init(struct window_mode_entry *wme, window_client_help, data, window_client_menu_items, &s); mode_tree_zoom(data->data, args); + if (data->preview_is_info) + mode_tree_view_name(data->data, "info"); + else + mode_tree_view_name(data->data, "preview"); + mode_tree_build(data->data); mode_tree_draw(data->data); @@ -389,6 +515,14 @@ window_client_key(struct window_mode_entry *wme, struct client *c, mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0); mode_tree_build(mtd); break; + case 'i': + data->preview_is_info = !data->preview_is_info; + if (data->preview_is_info) + mode_tree_view_name(mtd, "info"); + else + mode_tree_view_name(mtd, "preview"); + mode_tree_build(mtd); + break; case '\r': item = mode_tree_get_current(mtd); mode_tree_run_command(c, NULL, data->command, item->c->ttyname);