diff --git a/Makefile.am b/Makefile.am index a45ecd515..43afc6245 100644 --- a/Makefile.am +++ b/Makefile.am @@ -254,8 +254,8 @@ if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c endif -# Enable sixel support. -if ENABLE_SIXEL +# Enable image support. +if ENABLE_IMAGES dist_tmux_SOURCES += image.c image-sixel.c endif diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 6557cef32..923229eab 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-attach-session.c,v 1.92 2026/10/02 12:48:52 nicm Exp $ */ +/* $OpenBSD: cmd-attach-session.c,v 1.93 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -35,6 +35,7 @@ static enum cmd_retval cmd_attach_session_exec(struct cmd *, const struct cmd_entry cmd_attach_session_entry = { .name = "attach-session", + .description = "Attach to an existing session.", .alias = "attach", .args = { "c:dEf:rt:x", 0, 0, NULL }, diff --git a/cmd-bind-key.c b/cmd-bind-key.c index 0d7636ff7..6d4d21a8c 100644 --- a/cmd-bind-key.c +++ b/cmd-bind-key.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-bind-key.c,v 1.47 2025/04/09 07:03:04 nicm Exp $ */ +/* $OpenBSD: cmd-bind-key.c,v 1.48 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -34,6 +34,7 @@ static enum cmd_retval cmd_bind_key_exec(struct cmd *, const struct cmd_entry cmd_bind_key_entry = { .name = "bind-key", + .description = "Bind a key to one or more commands.", .alias = "bind", .args = { "nrN:T:", 1, -1, cmd_bind_key_args_parse }, diff --git a/cmd-break-pane.c b/cmd-break-pane.c index f2f1aff25..357d1c6c1 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-break-pane.c,v 1.76 2026/08/17 07:04:45 nicm Exp $ */ +/* $OpenBSD: cmd-break-pane.c,v 1.77 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_break_pane_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_break_pane_entry = { .name = "break-pane", + .description = "Move a pane into its own window or make it floating.", .alias = "breakp", .args = { "abdPF:n:s:t:Wx:X:y:Y:", 0, 0, NULL }, diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 6c78e9cd8..85ff5c2e5 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-capture-pane.c,v 1.69 2026/09/08 07:31:59 nicm Exp $ */ +/* $OpenBSD: cmd-capture-pane.c,v 1.70 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Jonathan Alvarado @@ -40,6 +40,7 @@ static char *cmd_capture_pane_hyperlinks(struct grid *, struct screen *, const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", + .description = "Capture pane content.", .alias = "capturep", .args = { "ab:CeE:FHIJLMNpPqRS:Tt:", 0, 0, NULL }, @@ -54,6 +55,7 @@ const struct cmd_entry cmd_capture_pane_entry = { const struct cmd_entry cmd_clear_history_entry = { .name = "clear-history", + .description = "Clear a pane's history.", .alias = "clearhist", .args = { "Ht:", 0, 0, NULL }, diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 3ca85aa14..62df6ac6c 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-choose-tree.c,v 1.59 2026/10/07 13:13:49 nicm Exp $ */ +/* $OpenBSD: cmd-choose-tree.c,v 1.60 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2012 Thomas Adam @@ -31,6 +31,7 @@ static enum cmd_retval cmd_choose_tree_exec(struct cmd *, const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", + .description = "Browse sessions and windows interactively.", .alias = NULL, .args = { "F:f:GhK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse }, @@ -45,6 +46,7 @@ const struct cmd_entry cmd_choose_tree_entry = { const struct cmd_entry cmd_choose_client_entry = { .name = "choose-client", + .description = "Choose a client interactively.", .alias = NULL, .args = { "F:f:hiK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, @@ -59,6 +61,7 @@ const struct cmd_entry cmd_choose_client_entry = { const struct cmd_entry cmd_choose_buffer_entry = { .name = "choose-buffer", + .description = "Choose a paste buffer interactively.", .alias = NULL, .args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse }, @@ -73,6 +76,7 @@ const struct cmd_entry cmd_choose_buffer_entry = { const struct cmd_entry cmd_customize_mode_entry = { .name = "customize-mode", + .description = "Browse and edit options and key bindings.", .alias = NULL, .args = { "F:f:kNt:yZ", 0, 0, NULL }, @@ -86,6 +90,7 @@ const struct cmd_entry cmd_customize_mode_entry = { const struct cmd_entry cmd_switch_mode_entry = { .name = "switch-mode", + .description = "Switch the active pane mode.", .alias = NULL, .args = { "F:kO:rst:wZ", 0, 1, cmd_choose_tree_args_parse }, @@ -100,6 +105,7 @@ const struct cmd_entry cmd_switch_mode_entry = { const struct cmd_entry cmd_display_panes_entry = { .name = "display-panes", + .description = "Display pane numbers.", .alias = "displayp", .args = { "d:kNs:t:Z", 0, 1, cmd_choose_tree_args_parse }, diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 1d135b834..1c487d818 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-command-prompt.c,v 1.76 2026/08/25 06:04:33 nicm Exp $ */ +/* $OpenBSD: cmd-command-prompt.c,v 1.77 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -40,6 +40,7 @@ static void cmd_command_prompt_free(void *); const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", + .description = "Open an interactive command prompt.", .alias = NULL, .args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse }, diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index fda3af772..f462d27a1 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-confirm-before.c,v 1.61 2026/06/24 10:55:39 nicm Exp $ */ +/* $OpenBSD: cmd-confirm-before.c,v 1.62 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -39,6 +39,7 @@ static void cmd_confirm_before_free(void *); const struct cmd_entry cmd_confirm_before_entry = { .name = "confirm-before", + .description = "Ask for confirmation before a command.", .alias = "confirm", .args = { "bc:p:t:y", 1, 1, cmd_confirm_before_args_parse }, diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index 28a1a742e..fd40f2ca4 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-copy-mode.c,v 1.55 2026/10/02 12:12:51 nicm Exp $ */ +/* $OpenBSD: cmd-copy-mode.c,v 1.56 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -28,6 +28,7 @@ static enum cmd_retval cmd_copy_mode_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_copy_mode_entry = { .name = "copy-mode", + .description = "Enter copy mode.", .alias = NULL, .args = { "dekHMqSs:t:u", 0, 0, NULL }, @@ -42,6 +43,7 @@ const struct cmd_entry cmd_copy_mode_entry = { const struct cmd_entry cmd_clock_mode_entry = { .name = "clock-mode", + .description = "Show a large clock in a pane.", .alias = NULL, .args = { "t:", 0, 0, NULL }, diff --git a/cmd-detach-client.c b/cmd-detach-client.c index 82ce9a00b..fe69b4bd8 100644 --- a/cmd-detach-client.c +++ b/cmd-detach-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-detach-client.c,v 1.39 2026/05/22 15:22:43 nicm Exp $ */ +/* $OpenBSD: cmd-detach-client.c,v 1.40 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_detach_client_exec(struct cmd *, const struct cmd_entry cmd_detach_client_entry = { .name = "detach-client", + .description = "Detach one or more clients.", .alias = "detach", .args = { "aE:s:t:P", 0, 0, NULL }, @@ -45,6 +46,7 @@ const struct cmd_entry cmd_detach_client_entry = { const struct cmd_entry cmd_suspend_client_entry = { .name = "suspend-client", + .description = "Suspend a client.", .alias = "suspendc", .args = { "t:", 0, 0, NULL }, diff --git a/cmd-display-menu.c b/cmd-display-menu.c index f9627ca67..98026329c 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-display-menu.c,v 1.55 2026/09/21 12:14:32 nicm Exp $ */ +/* $OpenBSD: cmd-display-menu.c,v 1.56 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -36,6 +36,7 @@ static enum cmd_retval cmd_display_popup_exec(struct cmd *, const struct cmd_entry cmd_display_menu_entry = { .name = "display-menu", + .description = "Display an interactive menu.", .alias = "menu", .args = { "b:c:C:H:s:S:MOt:T:x:y:", 1, -1, cmd_display_menu_args_parse }, @@ -52,6 +53,7 @@ const struct cmd_entry cmd_display_menu_entry = { const struct cmd_entry cmd_display_popup_entry = { .name = "display-popup", + .description = "Create a floating pane (compatibility command).", .alias = "popup", .args = { "Bb:Cc:d:e:Eh:ks:S:t:T:w:x:y:", 0, -1, NULL }, diff --git a/cmd-display-message.c b/cmd-display-message.c index ca56574f7..fe6d70bc6 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-display-message.c,v 1.67 2026/10/02 15:13:07 nicm Exp $ */ +/* $OpenBSD: cmd-display-message.c,v 1.68 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -37,6 +37,7 @@ static enum cmd_retval cmd_display_message_exec(struct cmd *, const struct cmd_entry cmd_display_message_entry = { .name = "display-message", + .description = "Display a message or expand formats.", .alias = "display", .args = { "aCc:d:jlINpt:F:v", 0, 1, NULL }, diff --git a/cmd-find-window.c b/cmd-find-window.c index 0091ae907..9239f89b3 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-find-window.c,v 1.57 2026/07/14 17:17:17 nicm Exp $ */ +/* $OpenBSD: cmd-find-window.c,v 1.58 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -30,6 +30,7 @@ static enum cmd_retval cmd_find_window_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_find_window_entry = { .name = "find-window", + .description = "Find windows by content or name.", .alias = "findw", .args = { "CiNrt:TZ", 1, 1, NULL }, diff --git a/cmd-if-shell.c b/cmd-if-shell.c index 59d34f9d6..cafbde877 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-if-shell.c,v 1.87 2026/08/25 06:04:33 nicm Exp $ */ +/* $OpenBSD: cmd-if-shell.c,v 1.88 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -40,6 +40,7 @@ static void cmd_if_shell_free(void *); const struct cmd_entry cmd_if_shell_entry = { .name = "if-shell", + .description = "Run a command conditionally.", .alias = "if", .args = { "bFt:", 2, 3, cmd_if_shell_args_parse }, diff --git a/cmd-join-pane.c b/cmd-join-pane.c index ea7dceeb0..4d06cebd3 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.77 2026/10/07 12:02:21 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.78 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -36,6 +36,7 @@ static void cmd_join_pane_mouse_move(struct client *, const struct cmd_entry cmd_join_pane_entry = { .name = "join-pane", + .description = "Move a pane into another window.", .alias = "joinp", .args = { "bdfhvp:l:s:t:", 0, 0, NULL }, @@ -50,6 +51,7 @@ const struct cmd_entry cmd_join_pane_entry = { const struct cmd_entry cmd_move_pane_entry = { .name = "move-pane", + .description = "Move a pane or reposition a floating pane.", .alias = "movep", .args = { "bdD::fhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL }, diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c index f634a7cd7..bd842cbc3 100644 --- a/cmd-kill-pane.c +++ b/cmd-kill-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-kill-pane.c,v 1.34 2026/06/09 21:22:22 nicm Exp $ */ +/* $OpenBSD: cmd-kill-pane.c,v 1.35 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -34,6 +34,7 @@ static int cmd_kill_pane_filter(struct cmdq_item *, const struct cmd_entry cmd_kill_pane_entry = { .name = "kill-pane", + .description = "Destroy one or more panes.", .alias = "killp", .args = { "af:t:", 0, 0, NULL }, diff --git a/cmd-kill-server.c b/cmd-kill-server.c index 8b70be3d8..d0d94e782 100644 --- a/cmd-kill-server.c +++ b/cmd-kill-server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-kill-server.c,v 1.20 2021/08/21 10:22:39 nicm Exp $ */ +/* $OpenBSD: cmd-kill-server.c,v 1.21 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_kill_server_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_kill_server_entry = { .name = "kill-server", + .description = "Stop the tmux server.", .alias = NULL, .args = { "", 0, 0, NULL }, @@ -42,6 +43,7 @@ const struct cmd_entry cmd_kill_server_entry = { const struct cmd_entry cmd_start_server_entry = { .name = "start-server", + .description = "Start the tmux server.", .alias = "start", .args = { "", 0, 0, NULL }, diff --git a/cmd-kill-session.c b/cmd-kill-session.c index 7edd2a30a..031066c8a 100644 --- a/cmd-kill-session.c +++ b/cmd-kill-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-kill-session.c,v 1.31 2026/06/09 12:57:40 nicm Exp $ */ +/* $OpenBSD: cmd-kill-session.c,v 1.32 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -36,6 +36,7 @@ static int cmd_kill_session_filter(struct cmdq_item *, const struct cmd_entry cmd_kill_session_entry = { .name = "kill-session", + .description = "Destroy one or more sessions.", .alias = NULL, .args = { "aCgf:t:", 0, 0, NULL }, diff --git a/cmd-kill-window.c b/cmd-kill-window.c index e7a6ec32e..242c7b4f1 100644 --- a/cmd-kill-window.c +++ b/cmd-kill-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-kill-window.c,v 1.30 2026/06/09 12:57:40 nicm Exp $ */ +/* $OpenBSD: cmd-kill-window.c,v 1.31 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -33,6 +33,7 @@ static int cmd_kill_window_filter(struct cmdq_item *, const struct cmd_entry cmd_kill_window_entry = { .name = "kill-window", + .description = "Destroy one or more windows.", .alias = "killw", .args = { "af:t:", 0, 0, NULL }, @@ -46,6 +47,7 @@ const struct cmd_entry cmd_kill_window_entry = { const struct cmd_entry cmd_unlink_window_entry = { .name = "unlink-window", + .description = "Unlink a window from a session.", .alias = "unlinkw", .args = { "kt:", 0, 0, NULL }, diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c index 82ee58947..cbb7a2429 100644 --- a/cmd-list-buffers.c +++ b/cmd-list-buffers.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-buffers.c,v 1.41 2026/02/27 08:25:12 nicm Exp $ */ +/* $OpenBSD: cmd-list-buffers.c,v 1.42 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -34,6 +34,7 @@ static enum cmd_retval cmd_list_buffers_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_list_buffers_entry = { .name = "list-buffers", + .description = "List paste buffers.", .alias = "lsb", .args = { "F:f:O:r", 0, 0, NULL }, diff --git a/cmd-list-clients.c b/cmd-list-clients.c index 77f3912ca..aef752d58 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-clients.c,v 1.42 2026/02/27 08:25:12 nicm Exp $ */ +/* $OpenBSD: cmd-list-clients.c,v 1.43 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -39,6 +39,7 @@ static enum cmd_retval cmd_list_clients_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_list_clients_entry = { .name = "list-clients", + .description = "List connected clients.", .alias = "lsc", .args = { "F:f:O:rt:", 0, 0, NULL }, diff --git a/cmd-list-commands.c b/cmd-list-commands.c index 5e9c4430c..b68386013 100644 --- a/cmd-list-commands.c +++ b/cmd-list-commands.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-commands.c,v 1.15 2026/02/24 08:22:13 nicm Exp $ */ +/* $OpenBSD: cmd-list-commands.c,v 1.16 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,20 +31,43 @@ "#{command_list_name}" \ "#{?command_list_alias, (#{command_list_alias}),} " \ "#{command_list_usage}" +#define LIST_COMMANDS_HELP_TEMPLATE \ + "#{p|#{command_list_name_width}:command_list_name} " \ + "#{command_list_description}" +#define LIST_COMMANDS_VERBOSE_TEMPLATE \ + LIST_COMMANDS_TEMPLATE "\n #{command_list_description}" static enum cmd_retval cmd_list_commands(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_list_commands_entry = { .name = "list-commands", + .description = "List commands or show command syntax.", .alias = "lscm", - .args = { "F:", 0, 1, NULL }, - .usage = "[-F format] [command]", + .args = { "F:h", 0, 1, NULL }, + .usage = "[-h] [-F format] [command]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, .exec = cmd_list_commands }; +static u_int +cmd_list_commands_get_width(const struct cmd_entry *only) +{ + const struct cmd_entry **entryp; + u_int width, largest = 0; + + if (only != NULL) + return (utf8_cstrwidth(only->name)); + + for (entryp = cmd_table; *entryp != NULL; entryp++) { + width = utf8_cstrwidth((*entryp)->name); + if (width > largest) + largest = width; + } + return (largest); +} + static void cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft, const char *template, struct cmdq_item *item) @@ -63,6 +86,11 @@ cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft, else s = ""; format_add(ft, "command_list_usage", "%s", s); + if (entry->description != NULL) + s = entry->description; + else + s = ""; + format_add(ft, "command_list_description", "%s", s); line = format_expand(ft, template); if (*line != '\0') @@ -75,33 +103,44 @@ cmd_list_commands(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); const struct cmd_entry **entryp; - const struct cmd_entry *entry; + const struct cmd_entry *entry = NULL; struct format_tree *ft; const char *template, *command; char *cause; + int help; + u_int width; - if ((template = args_get(args, 'F')) == NULL) - template = LIST_COMMANDS_TEMPLATE; - - ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); - format_defaults(ft, NULL, NULL, NULL, NULL); + if ((template = args_get(args, 'F')) == NULL) { + help = args_has(args, 'h'); + if (help > 1) + template = LIST_COMMANDS_VERBOSE_TEMPLATE; + else if (help == 1) + template = LIST_COMMANDS_HELP_TEMPLATE; + else + template = LIST_COMMANDS_TEMPLATE; + } command = args_string(args, 0); - if (command == NULL) { - for (entryp = cmd_table; *entryp != NULL; entryp++) - cmd_list_single_command(*entryp, ft, template, item); - } else { + if (command != NULL) { entry = cmd_find(command, &cause); - if (entry != NULL) - cmd_list_single_command(entry, ft, template, item); - else { + if (entry == NULL) { cmdq_error(item, "%s", cause); free(cause); - format_free(ft); return (CMD_RETURN_ERROR); } } + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); + format_defaults(ft, NULL, NULL, NULL, NULL); + width = cmd_list_commands_get_width(entry); + format_add(ft, "command_list_name_width", "%u", width); + + if (entry == NULL) { + for (entryp = cmd_table; *entryp != NULL; entryp++) + cmd_list_single_command(*entryp, ft, template, item); + } else + cmd_list_single_command(entry, ft, template, item); + format_free(ft); return (CMD_RETURN_NORMAL); } diff --git a/cmd-list-keys.c b/cmd-list-keys.c index e8617c2e3..ccaa4a0f9 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-keys.c,v 1.78 2026/07/01 13:12:17 nicm Exp $ */ +/* $OpenBSD: cmd-list-keys.c,v 1.79 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -42,6 +42,7 @@ static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_list_keys_entry = { .name = "list-keys", + .description = "List key bindings.", .alias = "lsk", .args = { "1aF:NO:P:rT:", 0, 1, NULL }, diff --git a/cmd-list-panes.c b/cmd-list-panes.c index 5bc996c2f..412679c4d 100644 --- a/cmd-list-panes.c +++ b/cmd-list-panes.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-panes.c,v 1.41 2026/09/08 10:20:08 nicm Exp $ */ +/* $OpenBSD: cmd-list-panes.c,v 1.42 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -36,6 +36,7 @@ static void cmd_list_panes_window(struct cmd *, struct session *, const struct cmd_entry cmd_list_panes_entry = { .name = "list-panes", + .description = "List panes and their state.", .alias = "lsp", .args = { "aF:f:O:rst:", 0, 0, NULL }, diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c index 30f2178ad..87e7106ed 100644 --- a/cmd-list-sessions.c +++ b/cmd-list-sessions.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-sessions.c,v 1.37 2026/09/08 10:20:08 nicm Exp $ */ +/* $OpenBSD: cmd-list-sessions.c,v 1.38 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -40,6 +40,7 @@ static enum cmd_retval cmd_list_sessions_exec(struct cmd *, const struct cmd_entry cmd_list_sessions_entry = { .name = "list-sessions", + .description = "List sessions and their state.", .alias = "ls", .args = { "F:f:O:r", 0, 0, NULL }, diff --git a/cmd-list-windows.c b/cmd-list-windows.c index 2cbccc1b8..1f49a1f17 100644 --- a/cmd-list-windows.c +++ b/cmd-list-windows.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-list-windows.c,v 1.51 2026/09/08 10:20:08 nicm Exp $ */ +/* $OpenBSD: cmd-list-windows.c,v 1.52 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -43,6 +43,7 @@ static enum cmd_retval cmd_list_windows_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_list_windows_entry = { .name = "list-windows", + .description = "List windows and their state.", .alias = "lsw", .args = { "aF:f:O:rt:", 0, 0, NULL }, diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index e9b016071..96cc4e62e 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-load-buffer.c,v 1.67 2026/10/08 07:06:26 nicm Exp $ */ +/* $OpenBSD: cmd-load-buffer.c,v 1.68 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -35,6 +35,7 @@ static enum cmd_retval cmd_load_buffer_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_load_buffer_entry = { .name = "load-buffer", + .description = "Load a file into a paste buffer.", .alias = "loadb", .args = { "b:t:w", 1, 1, NULL }, diff --git a/cmd-lock-server.c b/cmd-lock-server.c index 1feac03f8..ce7c69078 100644 --- a/cmd-lock-server.c +++ b/cmd-lock-server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-lock-server.c,v 1.31 2021/08/21 10:22:39 nicm Exp $ */ +/* $OpenBSD: cmd-lock-server.c,v 1.32 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -28,6 +28,7 @@ static enum cmd_retval cmd_lock_server_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_lock_server_entry = { .name = "lock-server", + .description = "Lock every client.", .alias = "lock", .args = { "", 0, 0, NULL }, @@ -39,6 +40,7 @@ const struct cmd_entry cmd_lock_server_entry = { const struct cmd_entry cmd_lock_session_entry = { .name = "lock-session", + .description = "Lock every client attached to a session.", .alias = "locks", .args = { "t:", 0, 0, NULL }, @@ -52,6 +54,7 @@ const struct cmd_entry cmd_lock_session_entry = { const struct cmd_entry cmd_lock_client_entry = { .name = "lock-client", + .description = "Lock a client.", .alias = "lockc", .args = { "t:", 0, 0, NULL }, diff --git a/cmd-move-window.c b/cmd-move-window.c index 1cd4f10a8..a8f1383c9 100644 --- a/cmd-move-window.c +++ b/cmd-move-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-move-window.c,v 1.34 2021/08/21 10:22:39 nicm Exp $ */ +/* $OpenBSD: cmd-move-window.c,v 1.35 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -30,6 +30,7 @@ static enum cmd_retval cmd_move_window_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_move_window_entry = { .name = "move-window", + .description = "Move a window between sessions.", .alias = "movew", .args = { "abdkrs:t:", 0, 0, NULL }, @@ -44,6 +45,7 @@ const struct cmd_entry cmd_move_window_entry = { const struct cmd_entry cmd_link_window_entry = { .name = "link-window", + .description = "Link a window into another session.", .alias = "linkw", .args = { "abdks:t:", 0, 0, NULL }, diff --git a/cmd-new-session.c b/cmd-new-session.c index 56d4f9779..83a027de9 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-new-session.c,v 1.152 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-new-session.c,v 1.154 2026/10/12 07:52:05 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -37,6 +37,7 @@ static enum cmd_retval cmd_new_session_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_new_session_entry = { .name = "new-session", + .description = "Create a new session.", .alias = "new", .args = { "Ac:dDe:EF:f:n:Ps:t:x:Xy:", 0, -1, NULL }, @@ -53,6 +54,7 @@ const struct cmd_entry cmd_new_session_entry = { const struct cmd_entry cmd_has_session_entry = { .name = "has-session", + .description = "Check whether a session exists.", .alias = "has", .args = { "t:", 0, 0, NULL }, @@ -80,7 +82,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) char *cause, *cwd = NULL, *cp, *ename; char *wname = NULL, *sname = NULL, *prefix = NULL; int detached, already_attached, is_control = 0; - u_int sx, sy, dsx, dsy, count = args_count(args); + u_int sx, sy, dsx, dsy, idx; + u_int count = args_count(args); struct spawn_context sc = { 0 }; enum cmd_retval retval; struct cmd_find_state fs; @@ -314,12 +317,17 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) * so add it to the group and synchronize. */ if (group != NULL) { - if (sg == NULL) { - if (groupwith != NULL) { - sg = session_group_new(groupwith->name); - session_group_add(sg, groupwith); - } else - sg = session_group_new(group); + if (sg == NULL && groupwith == NULL) + sg = session_group_new(group); + else if (sg == NULL) { + cp = xstrdup(groupwith->name); + for (idx = 1; session_group_find(cp) != NULL; idx++) { + free(cp); + xasprintf(&cp, "%s-%u", groupwith->name, idx); + } + sg = session_group_new(cp); + free(cp); + session_group_add(sg, groupwith); } session_group_add(sg, s); session_group_synchronize_to(s); diff --git a/cmd-new-window.c b/cmd-new-window.c index 6ec55fe64..60000a67d 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-new-window.c,v 1.104 2026/09/03 21:04:11 nicm Exp $ */ +/* $OpenBSD: cmd-new-window.c,v 1.105 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -36,6 +36,7 @@ static enum cmd_retval cmd_new_window_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_new_window_entry = { .name = "new-window", + .description = "Create a window.", .alias = "neww", .args = { "abc:de:EF:kn:PSt:", 0, -1, NULL }, diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c index feed94403..2fa0f986c 100644 --- a/cmd-paste-buffer.c +++ b/cmd-paste-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-paste-buffer.c,v 1.44 2026/03/04 07:19:32 tb Exp $ */ +/* $OpenBSD: cmd-paste-buffer.c,v 1.45 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_paste_buffer_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_paste_buffer_entry = { .name = "paste-buffer", + .description = "Paste a buffer into a pane.", .alias = "pasteb", .args = { "db:prSs:t:", 0, 0, NULL }, diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c index 1041c2199..1272fc88e 100644 --- a/cmd-pipe-pane.c +++ b/cmd-pipe-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-pipe-pane.c,v 1.64 2026/07/27 14:25:46 nicm Exp $ */ +/* $OpenBSD: cmd-pipe-pane.c,v 1.65 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -41,6 +41,7 @@ static void cmd_pipe_pane_error_callback(struct bufferevent *, short, void *); const struct cmd_entry cmd_pipe_pane_entry = { .name = "pipe-pane", + .description = "Pipe pane input or output to a command.", .alias = "pipep", .args = { "IOot:", 0, 1, NULL }, diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 11b43bf22..cd631e914 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-refresh-client.c,v 1.55 2026/07/17 08:37:29 nicm Exp $ */ +/* $OpenBSD: cmd-refresh-client.c,v 1.56 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_refresh_client_exec(struct cmd *, const struct cmd_entry cmd_refresh_client_entry = { .name = "refresh-client", + .description = "Refresh or resize a client.", .alias = "refresh", .args = { "A:B:cC:Df:r:F:lLRSt:U", 0, 1, NULL }, diff --git a/cmd-rename-session.c b/cmd-rename-session.c index 35719c564..d11bccc12 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-rename-session.c,v 1.39 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-rename-session.c,v 1.40 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_rename_session_exec(struct cmd *, const struct cmd_entry cmd_rename_session_entry = { .name = "rename-session", + .description = "Rename a session.", .alias = "rename", .args = { "t:", 1, 1, NULL }, diff --git a/cmd-rename-window.c b/cmd-rename-window.c index f55f8a18f..2cb7bf265 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-rename-window.c,v 1.30 2026/06/29 18:17:28 nicm Exp $ */ +/* $OpenBSD: cmd-rename-window.c,v 1.31 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_rename_window_exec(struct cmd *, const struct cmd_entry cmd_rename_window_entry = { .name = "rename-window", + .description = "Rename a window.", .alias = "renamew", .args = { "t:", 1, 1, NULL }, diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 90ebff4c1..9096563f9 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-resize-pane.c,v 1.70 2026/10/02 12:48:52 nicm Exp $ */ +/* $OpenBSD: cmd-resize-pane.c,v 1.71 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -39,6 +39,7 @@ static void cmd_resize_pane_mouse_resize_tiled(struct client *, const struct cmd_entry cmd_resize_pane_entry = { .name = "resize-pane", + .description = "Resize or zoom a pane.", .alias = "resizep", .args = { "D::L::MR::Tt:U::x:y:Z", 0, 1, NULL }, diff --git a/cmd-resize-window.c b/cmd-resize-window.c index a89750607..6b5b5522a 100644 --- a/cmd-resize-window.c +++ b/cmd-resize-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-resize-window.c,v 1.10 2023/06/30 13:19:32 nicm Exp $ */ +/* $OpenBSD: cmd-resize-window.c,v 1.11 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2018 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_resize_window_exec(struct cmd *, const struct cmd_entry cmd_resize_window_entry = { .name = "resize-window", + .description = "Resize a window.", .alias = "resizew", .args = { "aADLRt:Ux:y:", 0, 1, NULL }, diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c index 66a5a0be4..bc7ff4e7c 100644 --- a/cmd-respawn-pane.c +++ b/cmd-respawn-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-respawn-pane.c,v 1.40 2026/07/03 16:09:49 nicm Exp $ */ +/* $OpenBSD: cmd-respawn-pane.c,v 1.41 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_respawn_pane_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_respawn_pane_entry = { .name = "respawn-pane", + .description = "Restart a pane's command.", .alias = "respawnp", .args = { "c:e:Ekt:", 0, -1, NULL }, diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c index e10469649..246b0de0d 100644 --- a/cmd-respawn-window.c +++ b/cmd-respawn-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-respawn-window.c,v 1.51 2026/07/03 16:09:49 nicm Exp $ */ +/* $OpenBSD: cmd-respawn-window.c,v 1.52 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_respawn_window_exec(struct cmd *, const struct cmd_entry cmd_respawn_window_entry = { .name = "respawn-window", + .description = "Restart every pane in a window.", .alias = "respawnw", .args = { "c:e:Ekt:", 0, -1, NULL }, diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c index d62fd0fa4..27d17ee3b 100644 --- a/cmd-rotate-window.c +++ b/cmd-rotate-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-rotate-window.c,v 1.34 2026/06/22 08:47:45 nicm Exp $ */ +/* $OpenBSD: cmd-rotate-window.c,v 1.35 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -29,6 +29,7 @@ static enum cmd_retval cmd_rotate_window_exec(struct cmd *, const struct cmd_entry cmd_rotate_window_entry = { .name = "rotate-window", + .description = "Rotate panes in a window.", .alias = "rotatew", .args = { "Dt:UZ", 0, 0, NULL }, diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 4834cd9f7..029a76155 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-run-shell.c,v 1.95 2026/09/20 07:59:55 nicm Exp $ */ +/* $OpenBSD: cmd-run-shell.c,v 1.96 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -42,6 +42,7 @@ static void cmd_run_shell_print(struct job *, const char *); const struct cmd_entry cmd_run_shell_entry = { .name = "run-shell", + .description = "Run a shell command.", .alias = "run", .args = { "bd:Ct:Es:c:", 0, -1, cmd_run_shell_args_parse }, diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 6dd844cdd..861153a29 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-save-buffer.c,v 1.57 2025/10/28 07:32:26 nicm Exp $ */ +/* $OpenBSD: cmd-save-buffer.c,v 1.58 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha @@ -35,6 +35,7 @@ static enum cmd_retval cmd_save_buffer_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_save_buffer_entry = { .name = "save-buffer", + .description = "Save a paste buffer to a file.", .alias = "saveb", .args = { "ab:", 1, 1, NULL }, @@ -46,6 +47,7 @@ const struct cmd_entry cmd_save_buffer_entry = { const struct cmd_entry cmd_show_buffer_entry = { .name = "show-buffer", + .description = "Print a paste buffer.", .alias = "showb", .args = { "b:", 0, 0, NULL }, diff --git a/cmd-select-layout.c b/cmd-select-layout.c index 95f60e6ca..9e9e02e5d 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-select-layout.c,v 1.45 2026/09/09 07:03:39 nicm Exp $ */ +/* $OpenBSD: cmd-select-layout.c,v 1.46 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_select_layout_exec(struct cmd *, const struct cmd_entry cmd_select_layout_entry = { .name = "select-layout", + .description = "Choose or apply a layout.", .alias = "selectl", .args = { "Enopt:", 0, 1, NULL }, @@ -44,6 +45,7 @@ const struct cmd_entry cmd_select_layout_entry = { const struct cmd_entry cmd_next_layout_entry = { .name = "next-layout", + .description = "Select the next layout.", .alias = "nextl", .args = { "t:", 0, 0, NULL }, @@ -57,6 +59,7 @@ const struct cmd_entry cmd_next_layout_entry = { const struct cmd_entry cmd_previous_layout_entry = { .name = "previous-layout", + .description = "Select the previous layout.", .alias = "prevl", .args = { "t:", 0, 0, NULL }, diff --git a/cmd-select-pane.c b/cmd-select-pane.c index e23ca0adc..c51e4ea31 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-select-pane.c,v 1.78 2026/08/20 09:19:24 nicm Exp $ */ +/* $OpenBSD: cmd-select-pane.c,v 1.79 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_select_pane_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_select_pane_entry = { .name = "select-pane", + .description = "Select or mark a pane.", .alias = "selectp", .args = { "DdegLlMmP:RT:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */ @@ -44,6 +45,7 @@ const struct cmd_entry cmd_select_pane_entry = { const struct cmd_entry cmd_last_pane_entry = { .name = "last-pane", + .description = "Select the previously active pane.", .alias = "lastp", .args = { "det:Z", 0, 0, NULL }, diff --git a/cmd-select-window.c b/cmd-select-window.c index 32b4821bf..1e0c89a87 100644 --- a/cmd-select-window.c +++ b/cmd-select-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-select-window.c,v 1.30 2021/08/21 10:22:39 nicm Exp $ */ +/* $OpenBSD: cmd-select-window.c,v 1.31 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_select_window_exec(struct cmd *, const struct cmd_entry cmd_select_window_entry = { .name = "select-window", + .description = "Select a window.", .alias = "selectw", .args = { "lnpTt:", 0, 0, NULL }, @@ -44,6 +45,7 @@ const struct cmd_entry cmd_select_window_entry = { const struct cmd_entry cmd_next_window_entry = { .name = "next-window", + .description = "Select the next window.", .alias = "next", .args = { "at:", 0, 0, NULL }, @@ -57,6 +59,7 @@ const struct cmd_entry cmd_next_window_entry = { const struct cmd_entry cmd_previous_window_entry = { .name = "previous-window", + .description = "Select the previous window.", .alias = "prev", .args = { "at:", 0, 0, NULL }, @@ -70,6 +73,7 @@ const struct cmd_entry cmd_previous_window_entry = { const struct cmd_entry cmd_last_window_entry = { .name = "last-window", + .description = "Select the previously active window.", .alias = "last", .args = { "t:", 0, 0, NULL }, diff --git a/cmd-send-keys.c b/cmd-send-keys.c index 06043bddb..f9f0360e4 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-send-keys.c,v 1.81 2026/06/11 19:13:34 nicm Exp $ */ +/* $OpenBSD: cmd-send-keys.c,v 1.82 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_send_keys_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_send_keys_entry = { .name = "send-keys", + .description = "Send keys to a pane or client.", .alias = "send", .args = { "c:FHKlMN:Rt:X", 0, -1, NULL }, @@ -46,6 +47,7 @@ const struct cmd_entry cmd_send_keys_entry = { const struct cmd_entry cmd_send_prefix_entry = { .name = "send-prefix", + .description = "Send the prefix key to a pane.", .alias = NULL, .args = { "2t:", 0, 0, NULL }, diff --git a/cmd-server-access.c b/cmd-server-access.c index e85a278a8..1f07c7214 100644 --- a/cmd-server-access.c +++ b/cmd-server-access.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-server-access.c,v 1.6 2026/06/09 12:58:40 nicm Exp $ */ +/* $OpenBSD: cmd-server-access.c,v 1.7 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2021 Dallas Lyons @@ -36,6 +36,7 @@ static enum cmd_retval cmd_server_access_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_server_access_entry = { .name = "server-access", + .description = "Manage access to the tmux server.", .alias = NULL, .args = { "adglrw", 0, 1, NULL }, diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c index 3e540f706..e57a8ae6c 100644 --- a/cmd-set-buffer.c +++ b/cmd-set-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-set-buffer.c,v 1.38 2026/08/24 07:05:23 nicm Exp $ */ +/* $OpenBSD: cmd-set-buffer.c,v 1.39 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -31,6 +31,7 @@ static enum cmd_retval cmd_set_buffer_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_set_buffer_entry = { .name = "set-buffer", + .description = "Create or modify a paste buffer.", .alias = "setb", .args = { "ab:t:n:w", 0, 1, NULL }, @@ -43,6 +44,7 @@ const struct cmd_entry cmd_set_buffer_entry = { const struct cmd_entry cmd_delete_buffer_entry = { .name = "delete-buffer", + .description = "Delete a paste buffer.", .alias = "deleteb", .args = { "b:", 0, 0, NULL }, diff --git a/cmd-set-environment.c b/cmd-set-environment.c index 9a6552cb5..d15ccdec5 100644 --- a/cmd-set-environment.c +++ b/cmd-set-environment.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-set-environment.c,v 1.29 2025/04/09 06:27:43 nicm Exp $ */ +/* $OpenBSD: cmd-set-environment.c,v 1.30 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_set_environment_exec(struct cmd *, const struct cmd_entry cmd_set_environment_entry = { .name = "set-environment", + .description = "Set or remove an environment variable.", .alias = "setenv", .args = { "Fhgrt:u", 1, 2, NULL }, diff --git a/cmd-set-option.c b/cmd-set-option.c index 335a54760..3db645e24 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-set-option.c,v 1.146 2026/07/10 15:20:06 nicm Exp $ */ +/* $OpenBSD: cmd-set-option.c,v 1.147 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -38,6 +38,7 @@ static enum cmd_retval cmd_set_hook_monitor_exec(struct cmdq_item *, const struct cmd_entry cmd_set_option_entry = { .name = "set-option", + .description = "Set a server, session, or pane option.", .alias = "set", .args = { "aFgopqst:uUw", 1, 2, cmd_set_option_args_parse }, @@ -51,6 +52,7 @@ const struct cmd_entry cmd_set_option_entry = { const struct cmd_entry cmd_set_window_option_entry = { .name = "set-window-option", + .description = "Set a window option.", .alias = "setw", .args = { "aFgoqt:u", 1, 2, cmd_set_option_args_parse }, @@ -64,6 +66,7 @@ const struct cmd_entry cmd_set_window_option_entry = { const struct cmd_entry cmd_set_hook_entry = { .name = "set-hook", + .description = "Set or remove a hook.", .alias = NULL, .args = { "agpERTt:uB:w", 0, 2, cmd_set_option_args_parse }, diff --git a/cmd-show-environment.c b/cmd-show-environment.c index 692f09c32..d7757e0df 100644 --- a/cmd-show-environment.c +++ b/cmd-show-environment.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-show-environment.c,v 1.29 2025/04/09 06:27:43 nicm Exp $ */ +/* $OpenBSD: cmd-show-environment.c,v 1.30 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -36,6 +36,7 @@ static void cmd_show_environment_print(struct cmd *, struct cmdq_item *, const struct cmd_entry cmd_show_environment_entry = { .name = "show-environment", + .description = "Show environment variables.", .alias = "showenv", .args = { "hgst:", 0, 1, NULL }, diff --git a/cmd-show-messages.c b/cmd-show-messages.c index bdb433714..1d26d3f09 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-show-messages.c,v 1.37 2025/11/18 08:37:54 nicm Exp $ */ +/* $OpenBSD: cmd-show-messages.c,v 1.38 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -36,6 +36,7 @@ static enum cmd_retval cmd_show_messages_exec(struct cmd *, const struct cmd_entry cmd_show_messages_entry = { .name = "show-messages", + .description = "Show server and client messages.", .alias = "showmsgs", .args = { "JTt:", 0, 0, NULL }, diff --git a/cmd-show-options.c b/cmd-show-options.c index cba601ad8..45c325531 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-show-options.c,v 1.76 2026/07/27 19:15:58 nicm Exp $ */ +/* $OpenBSD: cmd-show-options.c,v 1.77 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -49,6 +49,7 @@ static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *, const struct cmd_entry cmd_show_options_entry = { .name = "show-options", + .description = "Show server, session, or pane options.", .alias = "show", .args = { "AgF:Hpqst:vw", 0, 1, NULL }, @@ -62,6 +63,7 @@ const struct cmd_entry cmd_show_options_entry = { const struct cmd_entry cmd_show_window_options_entry = { .name = "show-window-options", + .description = "Show window options.", .alias = "showw", .args = { "F:gvt:", 0, 1, NULL }, @@ -75,6 +77,7 @@ const struct cmd_entry cmd_show_window_options_entry = { const struct cmd_entry cmd_show_hooks_entry = { .name = "show-hooks", + .description = "Show hooks.", .alias = NULL, .args = { "BF:gpt:w", 0, 1, NULL }, diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c index 5b6af76fb..ecf1045c0 100644 --- a/cmd-show-prompt-history.c +++ b/cmd-show-prompt-history.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-show-prompt-history.c,v 1.5 2026/06/25 11:39:11 nicm Exp $ */ +/* $OpenBSD: cmd-show-prompt-history.c,v 1.6 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2021 Anindya Mukherjee @@ -29,6 +29,7 @@ static enum cmd_retval cmd_show_prompt_history_exec(struct cmd *, const struct cmd_entry cmd_show_prompt_history_entry = { .name = "show-prompt-history", + .description = "Show interactive prompt history.", .alias = "showphist", .args = { "T:", 0, 0, NULL }, @@ -40,6 +41,7 @@ const struct cmd_entry cmd_show_prompt_history_entry = { const struct cmd_entry cmd_clear_prompt_history_entry = { .name = "clear-prompt-history", + .description = "Clear interactive prompt history.", .alias = "clearphist", .args = { "T:", 0, 0, NULL }, diff --git a/cmd-source-file.c b/cmd-source-file.c index 771b03663..b509d7595 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-source-file.c,v 1.63 2026/08/18 08:05:05 nicm Exp $ */ +/* $OpenBSD: cmd-source-file.c,v 1.64 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2008 Tiago Cunha @@ -37,6 +37,7 @@ static enum cmd_retval cmd_source_file_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_source_file_entry = { .name = "source-file", + .description = "Load commands from a configuration file.", .alias = "source", .args = { "t:Fnqv", 1, -1, NULL }, diff --git a/cmd-split-window.c b/cmd-split-window.c index 4949b1298..2db840b6b 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-split-window.c,v 1.152 2026/10/02 12:48:52 nicm Exp $ */ +/* $OpenBSD: cmd-split-window.c,v 1.153 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -38,6 +38,7 @@ static void cmd_split_window_mouse_resize(struct client *, const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", + .description = "Create a floating pane.", .alias = "newp", .args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, @@ -57,6 +58,7 @@ const struct cmd_entry cmd_new_pane_entry = { const struct cmd_entry cmd_split_window_entry = { .name = "split-window", + .description = "Create a new pane by splitting a window.", .alias = "splitw", .args = { "bB:c:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL }, diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 1d0664cd6..76d2928f2 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-pane.c,v 1.56 2026/10/02 12:23:44 nicm Exp $ */ +/* $OpenBSD: cmd-swap-pane.c,v 1.57 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -30,6 +30,7 @@ static enum cmd_retval cmd_swap_pane_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_swap_pane_entry = { .name = "swap-pane", + .description = "Swap two panes.", .alias = "swapp", .args = { "dDs:t:UZ", 0, 0, NULL }, diff --git a/cmd-swap-window.c b/cmd-swap-window.c index 0141e6a57..f5f5bdd16 100644 --- a/cmd-swap-window.c +++ b/cmd-swap-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-window.c,v 1.30 2026/07/27 08:03:01 nicm Exp $ */ +/* $OpenBSD: cmd-swap-window.c,v 1.31 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -30,6 +30,7 @@ static enum cmd_retval cmd_swap_window_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_swap_window_entry = { .name = "swap-window", + .description = "Swap two windows.", .alias = "swapw", .args = { "ds:t:", 0, 0, NULL }, diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 44ea43cbc..19a04cf39 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-switch-client.c,v 1.75 2026/08/20 09:19:24 nicm Exp $ */ +/* $OpenBSD: cmd-switch-client.c,v 1.76 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -33,6 +33,7 @@ static enum cmd_retval cmd_switch_client_exec(struct cmd *, const struct cmd_entry cmd_switch_client_entry = { .name = "switch-client", + .description = "Switch a client to another session.", .alias = "switchc", .args = { "c:EFlnO:pt:rT:Z", 0, 0, NULL }, diff --git a/cmd-unbind-key.c b/cmd-unbind-key.c index f537fb576..63663c6c7 100644 --- a/cmd-unbind-key.c +++ b/cmd-unbind-key.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-unbind-key.c,v 1.34 2021/08/21 10:22:39 nicm Exp $ */ +/* $OpenBSD: cmd-unbind-key.c,v 1.35 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -30,6 +30,7 @@ static enum cmd_retval cmd_unbind_key_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_unbind_key_entry = { .name = "unbind-key", + .description = "Remove key bindings.", .alias = "unbind", .args = { "anqT:", 0, 1, NULL }, diff --git a/cmd-wait-for.c b/cmd-wait-for.c index 7326c7519..77c4bd6eb 100644 --- a/cmd-wait-for.c +++ b/cmd-wait-for.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-wait-for.c,v 1.24 2026/09/22 06:46:50 nicm Exp $ */ +/* $OpenBSD: cmd-wait-for.c,v 1.25 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2013 Nicholas Marriott @@ -32,6 +32,7 @@ static enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_wait_for_entry = { .name = "wait-for", + .description = "Wait for or signal named channels and events.", .alias = "wait", .args = { "EF:LSUlvw:", 1, 1, NULL }, diff --git a/configure.ac b/configure.ac index 8ce5ef9da..b72b84bec 100644 --- a/configure.ac +++ b/configure.ac @@ -541,15 +541,24 @@ if test "x$enable_cgroups" = xyes; then fi fi -# Enable sixel support. +# Enable image support. +AC_ARG_ENABLE( + images, + AS_HELP_STRING(--enable-images, enable image support) +) AC_ARG_ENABLE( sixel, - AS_HELP_STRING(--enable-sixel, enable sixel images) + AS_HELP_STRING(--enable-sixel, alias for --enable-images), + [ + if test "x$enable_images" = x; then + enable_images=$enable_sixel + fi + ] ) -if test "x$enable_sixel" = xyes; then - AC_DEFINE(ENABLE_SIXEL) +if test "x$enable_images" = xyes; then + AC_DEFINE(ENABLE_IMAGES) fi -AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes]) +AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes]) # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) @@ -1163,10 +1172,10 @@ if test "x$enable_asan" = xyes; then else AC_MSG_NOTICE([ASAN: off]) fi -if test "x$enable_sixel" = xyes; then - AC_MSG_NOTICE([SIXEL: on]) +if test "x$enable_images" = xyes; then + AC_MSG_NOTICE([images: on]) else - AC_MSG_NOTICE([SIXEL: off]) + AC_MSG_NOTICE([images: off]) fi if test "x$enable_debug" = xyes; then AC_MSG_NOTICE([debug: on]) diff --git a/format.c b/format.c index 26a64f964..2e4b4057d 100644 --- a/format.c +++ b/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.425 2026/10/05 10:59:37 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.427 2026/10/12 12:22:42 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -3070,11 +3070,11 @@ format_cb_version(__unused struct format_tree *ft) return (xstrdup(getversion())); } -/* Callback for sixel_support. */ +/* Callback for image_support. */ static void * -format_cb_sixel_support(__unused struct format_tree *ft) +format_cb_image_support(__unused struct format_tree *ft) { -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES return (xstrdup("1")); #else return (xstrdup("0")); @@ -3753,6 +3753,9 @@ static const struct format_table_entry format_table[] = { { "host_short", FORMAT_TABLE_STRING, format_cb_host_short }, + { "image_support", FORMAT_TABLE_STRING, + format_cb_image_support + }, { "insert_flag", FORMAT_TABLE_STRING, format_cb_insert_flag }, @@ -4090,7 +4093,7 @@ static const struct format_table_entry format_table[] = { format_cb_session_windows }, { "sixel_support", FORMAT_TABLE_STRING, - format_cb_sixel_support + format_cb_image_support }, { "socket_path", FORMAT_TABLE_STRING, format_cb_socket_path @@ -5803,6 +5806,17 @@ format_loop_clients(struct format_expand_state *es, const char *fmt) return (value); } +/* Truncate a value to a long long, failing if it is NaN or out of range. */ +static int +format_truncate_integer(double *v) +{ + /* (double)LLONG_MAX is 2^63, which does not fit; -2^63 does. */ + if (isnan(*v) || *v >= (double)LLONG_MAX || *v < (double)LLONG_MIN) + return (-1); + *v = (long long)*v; + return (0); +} + static char * format_replace_expression(struct format_modifier *mexp, struct format_expand_state *es, const char *copy) @@ -5888,9 +5902,11 @@ format_replace_expression(struct format_modifier *mexp, goto fail; } - if (!use_fp) { - mleft = (long long)mleft; - mright = (long long)mright; + if (!use_fp && + (format_truncate_integer(&mleft) != 0 || + format_truncate_integer(&mright) != 0)) { + format_log(es, "expression side is out of range"); + goto fail; } format_log(es, "expression left side is: %.*f", prec, mleft); format_log(es, "expression right side is: %.*f", prec, mright); @@ -5930,10 +5946,11 @@ format_replace_expression(struct format_modifier *mexp, result = (mleft <= mright); break; } - if (use_fp) - xasprintf(&value, "%.*f", prec, result); - else - xasprintf(&value, "%.*f", prec, (double)(long long)result); + if (!use_fp && format_truncate_integer(&result) != 0) { + format_log(es, "expression result is out of range"); + goto fail; + } + xasprintf(&value, "%.*f", prec, result); format_log(es, "expression result is %s", value); free(right); diff --git a/grid.c b/grid.c index 28a82cb2c..abb8ee6e7 100644 --- a/grid.c +++ b/grid.c @@ -1,4 +1,4 @@ -/* $OpenBSD: grid.c,v 1.159 2026/10/08 07:50:05 nicm Exp $ */ +/* $OpenBSD: grid.c,v 1.161 2026/10/12 15:40:17 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -18,9 +18,6 @@ #include -#ifdef __APPLE__ -#include -#endif #include #include @@ -45,7 +42,7 @@ const struct grid_cell grid_default_cell = { /* * Padding grid cell data. Padding cells are the only zero width cell that - * appears in the grid - because of this, they are always extended cells. + * appears in the grid. */ static const struct grid_cell grid_padding_cell = { { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 8, 0 @@ -59,42 +56,6 @@ static const struct grid_cell_entry grid_cleared_entry = { { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED }; -#ifdef __APPLE__ -void -grid_check_is_clear(struct grid *gd) -{ - struct grid_line *gl; - u_int yy, ny; - - assert(gd != NULL); - - if (gd->sy == 0) { - assert(gd->linedata == NULL); - return; - } - - assert(gd->linedata != NULL); - - ny = gd->hsize + gd->sy; - for (yy = 0; yy < ny; yy++) { - gl = &gd->linedata[yy]; - - assert(gl->celldata == NULL); - assert(gl->cellused == 0); - assert(gl->cellsize == 0); - assert(gl->extddata == NULL); - assert(gl->extdsize == 0); - assert(gl->flags == 0); - assert(gl->time == 0); - } -} -#else -void -grid_check_is_clear(__unused struct grid *gd) -{ -} -#endif - /* Store cell in entry. */ static void grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, @@ -347,12 +308,6 @@ grid_free_line(struct grid *gd, u_int py) { struct grid_line *gl = &gd->linedata[py]; -#ifdef __APPLE__ - assert(gl->cellused <= gl->cellsize); - assert(gl->extdsize == 0 || gl->extddata != NULL); - assert(gl->cellsize == 0 || gl->celldata != NULL); -#endif - free(gl->celldata); free(gl->extddata); memset(gl, 0, sizeof *gl); @@ -384,11 +339,6 @@ grid_create(u_int sx, u_int sy, u_int hlimit) if (gd->sy != 0) gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata); - -#ifdef __APPLE__ - assert(gd->hsize == 0); -#endif - grid_check_is_clear(gd); return (gd); } @@ -642,7 +592,10 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) if (gce->flags & GRID_FLAG_BG256) gc->bg |= COLOUR_FLAG_256; gc->us = 8; - utf8_set(&gc->data, gce->data.data); + if (gc->flags & GRID_FLAG_PADDING) + memcpy(&gc->data, &grid_padding_cell.data, sizeof gc->data); + else + utf8_set(&gc->data, gce->data.data); gc->link = 0; } @@ -1500,8 +1453,7 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy, /* Remove the lines that were completely consumed. */ for (i = yy + 1; i < yy + 1 + lines; i++) { - free(gd->linedata[i].celldata); - free(gd->linedata[i].extddata); + grid_free_line(gd, i); grid_reflow_dead(&gd->linedata[i]); } diff --git a/image-sixel.c b/image-sixel.c index a004d78f8..0b7fa7512 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -18,6 +18,7 @@ #include +#include #include #include @@ -133,6 +134,21 @@ sixel_parse_write(struct sixel_image *si, u_int ch) return (0); } +static int +sixel_parse_number(const char *cp, char **endptr, u_int *value) +{ + unsigned long long number; + + if (*cp < '0' || *cp > '9') + return (1); + errno = 0; + number = strtoull(cp, endptr, 10); + if (errno == ERANGE || number > UINT_MAX) + return (1); + *value = number; + return (0); +} + static const char * sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) { @@ -157,7 +173,10 @@ sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) return (NULL); } - x = strtoul(endptr + 1, &endptr, 10); + if (sixel_parse_number(endptr + 1, &endptr, &x) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); @@ -166,7 +185,10 @@ sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) log_debug("%s: image is too wide", __func__); return (NULL); } - y = strtoul(endptr + 1, &endptr, 10); + if (sixel_parse_number(endptr + 1, &endptr, &y) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (endptr != last) { log_debug("%s: extra ;", __func__); return (NULL); @@ -200,7 +222,10 @@ sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) last++; } - c = strtoul(cp, &endptr, 10); + if (sixel_parse_number(cp, &endptr, &c) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (c > SIXEL_COLOUR_REGISTERS) { log_debug("%s: too many colours", __func__); return (NULL); @@ -211,22 +236,34 @@ sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) if (endptr == last || *endptr != ';') return (last); - type = strtoul(endptr + 1, &endptr, 10); + if (sixel_parse_number(endptr + 1, &endptr, &type) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } - c1 = strtoul(endptr + 1, &endptr, 10); + if (sixel_parse_number(endptr + 1, &endptr, &c1) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } - c2 = strtoul(endptr + 1, &endptr, 10); + if (sixel_parse_number(endptr + 1, &endptr, &c2) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } - c3 = strtoul(endptr + 1, &endptr, 10); + if (sixel_parse_number(endptr + 1, &endptr, &c3) != 0) { + log_debug("%s: invalid number", __func__); + return (NULL); + } if (endptr != last) { log_debug("%s: missing ;", __func__); return (NULL); @@ -481,7 +518,7 @@ static void sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, size_t slen) { - while (*used + slen >= *len + 1) { + while (*used + slen + 1 > *len) { *buf = xreallocarray(*buf, 2, *len); (*len) *= 2; } diff --git a/input-keys.c b/input-keys.c index 4cade4e53..aa2b8abbb 100644 --- a/input-keys.c +++ b/input-keys.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input-keys.c,v 1.114 2026/06/15 21:47:01 nicm Exp $ */ +/* $OpenBSD: input-keys.c,v 1.115 2026/10/12 09:37:07 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -620,7 +620,7 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) key = '\011' | (key & ~KEYC_MASK_KEY) | KEYC_SHIFT; } else { /* Otherwise clear modifiers. */ - key &= ~KEYC_MASK_MODIFIERS; + key &= ~(KEYC_MASK_MODIFIERS|KEYC_IMPLIED_META); } } diff --git a/input.c b/input.c index c38d12560..d83da9f0e 100644 --- a/input.c +++ b/input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: input.c,v 1.273 2026/10/08 07:50:05 nicm Exp $ */ +/* $OpenBSD: input.c,v 1.274 2026/10/12 17:04:48 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1582,7 +1582,7 @@ input_csi_dispatch(struct input_ctx *ictx) case -1: break; case 0: -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES input_reply(ictx, 1, "\033[?1;2;4c"); #else input_reply(ictx, 1, "\033[?1;2c"); @@ -2020,6 +2020,7 @@ static void input_csi_dispatch_sm_private(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; struct grid_cell *gc = &ictx->cell.cell; u_int i; @@ -2081,13 +2082,13 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) break; case 2031: screen_write_mode_set(sctx, MODE_THEME_UPDATES); - if (ictx->wp != NULL) { - ictx->wp->last_theme = window_pane_get_theme(ictx->wp); - ictx->wp->flags &= ~PANE_THEMECHANGED; + if (wp != NULL) { + wp->last_theme = window_pane_get_theme(wp); + wp->flags &= ~PANE_THEMECHANGED; } break; case 2026: - screen_write_start_sync(ictx->wp); + screen_write_start_sync(wp); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -2100,7 +2101,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) static void input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx) { -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES int n, m, o; if (ictx->param_list_len > 3) @@ -2625,7 +2626,7 @@ input_dcs_dispatch(struct input_ctx *ictx) const char prefix[] = "tmux;"; const u_int prefixlen = (sizeof prefix) - 1; long long allow_passthrough = 0; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct window *w; struct sixel_image *si; int p2; @@ -2641,7 +2642,7 @@ input_dcs_dispatch(struct input_ctx *ictx) return (0); } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (wp != NULL && buf[0] == 'q' && ictx->interm_len == 0) { w = wp->window; if (input_split(ictx) != 0) diff --git a/menu.c b/menu.c index cb087296e..b7917fa72 100644 --- a/menu.c +++ b/menu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: menu.c,v 1.71 2026/09/21 12:14:32 nicm Exp $ */ +/* $OpenBSD: menu.c,v 1.72 2026/10/12 10:46:12 nicm Exp $ */ /* * Copyright (c) 2019 Nicholas Marriott @@ -194,10 +194,19 @@ static void menu_reapply_styles(struct menu_data *md) { struct options *o = md->w->options; + struct session *s = NULL; + struct winlink *wl = NULL; + struct window_pane *wp = NULL; struct format_tree *ft; struct style sytmp; - ft = format_create_defaults(NULL, NULL, md->fs.s, md->fs.wl, md->fs.wp); + /* The pane (or session) the menu was opened for may be gone. */ + if (cmd_find_valid_state(&md->fs)) { + s = md->fs.s; + wl = md->fs.wl; + wp = md->fs.wp; + } + ft = format_create_defaults(NULL, NULL, s, wl, wp); /* Reapply menu style from options. */ memcpy(&md->style_gc, &grid_default_cell, sizeof md->style_gc); diff --git a/options-table.c b/options-table.c index a65694973..15179d87a 100644 --- a/options-table.c +++ b/options-table.c @@ -1,4 +1,4 @@ -/* $OpenBSD: options-table.c,v 1.251 2026/10/07 12:59:50 nicm Exp $ */ +/* $OpenBSD: options-table.c,v 1.252 2026/10/09 13:12:14 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -329,7 +329,8 @@ const struct options_table_entry options_table[] = { "server-info=show-messages -JT," "info=show-messages -JT," "choose-window=choose-tree -w," - "choose-session=choose-tree -s", + "choose-session=choose-tree -s," + "help=list-commands -h", .separator = ",", .text = "Array of command aliases. " "Each entry is an alias and a command separated by '='." @@ -743,6 +744,15 @@ const struct options_table_entry options_table[] = { "'User0', 'User1' and so on." }, +#ifdef HAVE_UTEMPTER + { .name = "utmp", + .type = OPTIONS_TABLE_FLAG, + .scope = OPTIONS_TABLE_SERVER, + .default_num = 1, + .text = "Whether to maintain utmp records." + }, +#endif + { .name = "variation-selector-always-wide", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, diff --git a/options.c b/options.c index fa10d8a7d..946469ed2 100644 --- a/options.c +++ b/options.c @@ -1406,6 +1406,14 @@ options_push_changes(const char *name) tty_keys_build(&loop->tty); } } + if (strcmp(name, "utmp") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) { + if (options_get_number(global_options, name)) + window_pane_utmp_add(wp); + else + window_pane_utmp_remove(wp); + } + } if (strcmp(name, "status") == 0 || strcmp(name, "status-interval") == 0) status_timer_start_all(); diff --git a/regress/alt-screen-saved-cursor.sh b/regress/alt-screen-saved-cursor.sh new file mode 100644 index 000000000..26ef20fef --- /dev/null +++ b/regress/alt-screen-saved-cursor.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +# 1049h saves the cursor when it enters the alternate screen, and neither 47l +# nor 1049l clears it. If the screen was resized after it was saved, 1049l can +# restore a row below the saved grid it reflows. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" +DIR=$(mktemp -d) +trap '$TMUX kill-server 2>/dev/null; rm -rf $DIR' 0 1 15 + +exit_status=0 + +# Wait until the pane's program has set its path (OSC 7) to $1. +wait_path() { + _i=0 + while [ "$($TMUX display -p '#{pane_path}' 2>/dev/null)" != "$1" ]; do + if ! $TMUX has 2>/dev/null; then + echo "FAIL: $2: the server died" + exit 1 + fi + _i=$((_i + 1)) + if [ $_i -ge 400 ]; then + echo "FAIL: $2: timed out waiting for $1" + exit_status=1 + return 1 + fi + sleep 0.05 + done +} + +# Respawn the pane, $2 by $3, running $1. Where the program waits for a file +# in $DIR, the test resizes the window and then creates it. +run() { + rm -f $DIR/go* + $TMUX resize-window -x $2 -y $3 || exit 1 + $TMUX respawn-pane -k "$1; exec sleep 1000" || exit 1 +} + +check() { + if [ "$2" != "$3" ]; then + echo "FAIL: $1: '$2', not '$3'" + exit_status=1 + fi +} + +$TMUX new -d -x 10 -y 5 'sleep 1000' || exit 1 +$TMUX set -g window-size manual || exit 1 + +# Outside the alternate screen (47l): the cursor after a full line goes to +# the last column, as with no 47l. Unchanged. +run "printf 'abcdefghij\\033[?1049h\\033[?47l\\033[?1049lX\\033]7;done\\007'" 10 5 +if wait_path done "1049l outside the alternate screen"; then + check "1049l outside the alternate screen" \ + "$($TMUX capture-pane -p | head -1)" abcdefghiX +fi + +# A full line, 1049h, a wider screen, 1049l: the cursor is after the line's +# last character and X follows it. Unchanged. +run "printf 'abcdefghij\\033[?1049h\\033]7;in\\007'; while [ ! -e $DIR/go ]; do sleep 0.05; done; printf '\\033[?1049lX\\033]7;done\\007'" 10 5 +if wait_path in "full line, then wider"; then + $TMUX resize-window -x 20 || exit 1 + touch $DIR/go + wait_path done "full line, then wider" && + check "full line, then wider" \ + "$($TMUX capture-pane -p | head -1)" abcdefghijX +fi + +# cy 8 saved 10 high; 47l, 20x4, 47h, 40x4, 1049l. The cursor goes to the +# last row of the saved grid, which is 4 high. +run "printf '\\033[9;1Hrow\\033[?1049h\\033[?47l\\033]7;one\\007'; while [ ! -e $DIR/go1 ]; do sleep 0.05; done; printf '\\033[?47h\\033]7;two\\007'; while [ ! -e $DIR/go2 ]; do sleep 0.05; done; printf '\\033[?1049lX\\033]7;done\\007'" 40 10 +if wait_path one "saved taller"; then + $TMUX resize-window -x 20 -y 4 || exit 1 + touch $DIR/go1 + if wait_path two "saved taller"; then + $TMUX resize-window -x 40 -y 4 || exit 1 + touch $DIR/go2 + wait_path done "saved taller" && + check "saved taller" \ + "$($TMUX capture-pane -p | grep row)" rowX + fi +fi + +exit $exit_status diff --git a/regress/choose-tree.sh b/regress/choose-tree.sh index 4656963e6..92325ed33 100644 --- a/regress/choose-tree.sh +++ b/regress/choose-tree.sh @@ -378,4 +378,23 @@ done [ "$i" -lt 50 ] || fail "tagged windows were not killed" exit_mode q +# --- killing the mode's own session ----------------------------------------- +# Killing the attached session also destroys the pane hosting the tree. The +# confirmation callback must finish without redrawing the destroyed mode. +$TMUX set-option -g detach-on-destroy off || exit 1 +$TMUX choose-tree -s -t aaa:0 -F 'G7' -O name || exit 1 +wait_count ': G7' 2 +$TMUX send-keys -t aaa:0 g x || fail "send-keys session kill failed" +wait_for 'Kill session aaa' +$TMUX send-keys -t aaa:0 y || fail "session kill confirmation failed" +i=0 +while [ "$i" -lt 50 ]; do + sessions=$($TMUX list-sessions -F '#{session_name}' 2>/dev/null) + client_session=$($TMUX list-clients -F '#{client_session}' 2>/dev/null) + [ "$sessions" = 'zzz' ] && [ "$client_session" = 'zzz' ] && break + sleep 0.2 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "killing the tree session killed the server" + exit 0 diff --git a/regress/client-exit-resize.sh b/regress/client-exit-resize.sh new file mode 100755 index 000000000..a1dfab023 --- /dev/null +++ b/regress/client-exit-resize.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +# A client that has sent MSG_EXITING can still send MSG_RESIZE. The server +# must not use the tty it closed for MSG_EXITING. Here the client is stopped +# and sent SIGTERM and then SIGWINCH for a size with no pixel size, so the +# server would ask the terminal for one. + +PATH=/bin:/usr/bin:/usr/local/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" +trap '$TMUX kill-server 2>/dev/null' 0 1 15 + +$TMUX new -d 'sleep 1000' || exit 1 +$TMUX set-hook -g client-resized 'set -g @resized 1' || exit 1 +python3 - $TMUX <<'EOF2' || exit 1 +import fcntl, os, pty, signal, struct, subprocess, sys, termios, time + +tmux = sys.argv[1:] + +def size(fd, rows, cols, xpixel, ypixel): + fcntl.ioctl(fd, termios.TIOCSWINSZ, + struct.pack("HHHH", rows, cols, cols * xpixel, rows * ypixel)) + +master, slave = pty.openpty() +size(slave, 20, 60, 8, 16) +client = os.fork() +if client == 0: + os.setsid() + fcntl.ioctl(slave, termios.TIOCSCTTY, 0) + for fd in (0, 1, 2): + os.dup2(slave, fd) + os.environ["TERM"] = "xterm" + os.execvp(tmux[0], tmux + ["attach"]) +os.set_blocking(master, False) + +# Wait until the server has handled the resize the client sends when it +# attaches. +for i in range(200): + try: + os.read(master, 65536) + except OSError: + pass + out = subprocess.run(tmux + ["show", "-gv", "@resized"], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout + if out.strip() == b"1": + break + time.sleep(0.05) +else: + print("the client did not attach") + sys.exit(1) + +# SIGTERM is sent before SIGWINCH, so the client handles it first whether +# it handles pending signals in signal number order or in the order sent. +os.kill(client, signal.SIGSTOP) +os.waitpid(client, os.WUNTRACED) +os.kill(client, signal.SIGTERM) +size(master, 23, 61, 0, 0) +os.kill(client, signal.SIGWINCH) +os.kill(client, signal.SIGCONT) +for i in range(200): + if os.waitpid(client, os.WNOHANG)[0] != 0: + break + time.sleep(0.05) +else: + print("the client did not exit") + sys.exit(1) +EOF2 +sleep 1 +$TMUX has || { echo "the server died"; exit 1; } +exit 0 diff --git a/regress/format-modifiers.sh b/regress/format-modifiers.sh index 3fdfb59bd..648c19232 100644 --- a/regress/format-modifiers.sh +++ b/regress/format-modifiers.sh @@ -226,10 +226,24 @@ test_format "#{e|/|f|2:10,3}" "3.33" # Default number of decimal places for float is two. test_format "#{e|*|f:2.5,2}" "5.00" -# Division by zero must not crash the server (result is unspecified). -$TMUX display-message -p "#{e|/|:5,0}" >/dev/null 2>&1 -$TMUX display-message -p "#{e|/|f:5,0}" >/dev/null 2>&1 -assert_alive "division by zero" +# An integer expression with a side or result that is NaN or out of the range +# of a long long expands to empty: division or modulus by zero, nan or inf, or +# 1e30. LLONG_MAX written out is 2^63 as a double, which is out of range; the +# largest double below 2^63 and -2^63 still convert. Comparisons are integer +# expressions too. Floating point gives inf. +test_format "#{e|/|:5,0}" "" +test_format "#{e|m|:0,0}" "" +test_format "#{e|m|:7,0}" "" +test_format "#{e|+|:nan,0}" "" +test_format "#{e|+|:-inf,1}" "" +test_format "#{e|-|:1e30,1e30}" "" +test_format "#{e|*|:1e30,1}" "" +test_format "#{e|+|:9223372036854775807,0}" "" +test_format "#{e|<|:nan,1}" "" +test_format "#{e|+|:9223372036854774784,0}" "9223372036854774784" +test_format "#{e|-|:-9223372036854775808,0}" "-9223372036854775808" +test_format "#{e|/|f:5,0}" "inf" +assert_alive "expressions with no integer value" # --- ASCII and repeat ---------------------------------------------------- diff --git a/regress/format-variables.sh b/regress/format-variables.sh index 6908ad956..dced1a711 100644 --- a/regress/format-variables.sh +++ b/regress/format-variables.sh @@ -79,6 +79,7 @@ history_limit history_size host host_short +image_support insert_flag keypad_cursor_flag keypad_flag diff --git a/regress/input-combine-width.sh b/regress/input-combine-width.sh new file mode 100644 index 000000000..4584d6b71 --- /dev/null +++ b/regress/input-combine-width.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +. ./input-common.inc + +# Exercise expansion by VS16, a skin tone modifier and a regional indicator. +for kind in variation modifier flag; do + case "$kind" in + variation) + combine_seq='\342\234\224\357\270\217' + text='✔️' + ;; + modifier) + combine_seq='\342\230\235\360\237\217\273' + text='☝🏻' + ;; + flag) + combine_seq='\360\237\207\254\360\237\207\247' + text='🇬🇧' + ;; + esac + + start_pane "$kind-overwrite" 10 3 "A\347\225\214BC\r${combine_seq}X" + check_capture "$kind-overwrite" "${text}XBC" + check_cursor "$kind-overwrite" '3,0' + check_raw_matches "$kind-overwrite" \ + "C 0,0 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \ + 'C 0,1 .*flags=PADDING\[[0-9a-f]+\]' \ + 'C 0,2 data=\(1,1,X\) flags=NONE\[0\]' + + start_pane "$kind-edge" 5 3 "abcd${combine_seq}X" + check_capture "$kind-edge" "abcd +${text}X" + check_cursor "$kind-edge" '3,1' + check_raw_matches "$kind-edge" \ + 'L 0 \(0\) flags=.*WRAPPED' \ + 'C 0,4 data=\(1,1, \)' \ + "C 1,0 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \ + 'C 1,1 .*flags=PADDING\[[0-9a-f]+\]' \ + 'C 1,2 data=\(1,1,X\) flags=NONE\[0\]' + + start_pane "$kind-fit" 5 3 "abc${combine_seq}X" + check_capture "$kind-fit" "abc${text} +X" + check_cursor "$kind-fit" '1,1' + + start_pane "$kind-scroll" 5 2 "11111\r\nabcd${combine_seq}X" + check_capture "$kind-scroll" "abcd +${text}X" + check_cursor "$kind-scroll" '3,1' + + start_pane "$kind-region" 5 4 \ + "top\033[2;1Hold\033[4;1Hend\033[2;3r\033[3;1Habcd${combine_seq}X" + check_capture "$kind-region" "top +abcd +${text}X +end" + check_cursor "$kind-region" '3,2' + + start_pane "$kind-insert" 10 3 "ABCDE\r\033[4h${combine_seq}X" + check_capture "$kind-insert" "${text}XABCDE" + check_cursor "$kind-insert" '3,0' + + start_pane "$kind-insert-edge" 5 3 \ + "\033[2;1HABCDE\033[1;1Habcd\033[4h${combine_seq}X" + check_capture "$kind-insert-edge" "abcd +${text}XAB" + check_cursor "$kind-insert-edge" '3,1' + + start_pane "$kind-nowrap" 5 3 "\033[?7labc${combine_seq}" + check_capture "$kind-nowrap" "abc${text}" + check_cursor "$kind-nowrap" '4,0' + check_raw_matches "$kind-nowrap" \ + "C 0,3 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \ + 'C 0,4 .*flags=PADDING\[[0-9a-f]+\]' +done + +# A two-column cluster cannot fit in a one-column pane. Keep its narrow base. +start_pane one-column 1 3 '\342\234\224\357\270\217' +check_capture one-column '✔' +check_cursor one-column '1,0' +check_raw_no_matches one-column 'data=\(2,' + +# Without wrapping, even a wide cell filling the pane leaves the cursor inside. +start_pane two-columns-nowrap 2 3 '\033[?7l\342\234\224\357\270\217' +check_capture two-columns-nowrap '✔️' +check_cursor two-columns-nowrap '1,0' + +exit $exit_status diff --git a/regress/input-raw-edit.sh b/regress/input-raw-edit.sh index 6d78cc3c4..e6e8938c8 100644 --- a/regress/input-raw-edit.sh +++ b/regress/input-raw-edit.sh @@ -40,7 +40,7 @@ start_pane tabs 12 3 'A\tB\033[2g\r\033[IC' check_raw_matches tabs \ 'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \ 'C 0,1 data=\(7,7, \) flags=TAB\[[0-9a-f]+\]' \ - 'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\]' \ + 'C 0,2 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\]' \ 'C 0,8 data=\(1,1,B\) flags=NONE\[0\]' \ 'C 0,0 data=\(1,1,C\) flags=NONE\[0\]' diff --git a/regress/input-raw-unicode.sh b/regress/input-raw-unicode.sh index 76e2f5512..99530f151 100644 --- a/regress/input-raw-unicode.sh +++ b/regress/input-raw-unicode.sh @@ -7,7 +7,7 @@ check_capture wide 'AあB' check_raw_matches wide \ 'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \ 'C 0,1 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \ - 'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ + 'C 0,2 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ 'C 0,3 data=\(1,1,B\) flags=NONE\[0\]' start_pane combining 8 3 'e\314\201x' @@ -20,21 +20,21 @@ start_pane emoji 10 3 '\360\237\230\200Z' check_raw_matches emoji \ 'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \ 'C 0,0 data=\(2,4,😀\) flags=NONE\[0\] attr=NONE\[0\]' \ - 'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ + 'C 0,1 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ 'C 0,2 data=\(1,1,Z\) flags=NONE\[0\]' start_pane flag 10 3 '\360\237\207\254\360\237\207\247!' check_raw_matches flag \ 'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \ 'C 0,0 data=\(2,8,🇬🇧\) flags=NONE\[0\] attr=NONE\[0\]' \ - 'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ + 'C 0,1 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ 'C 0,2 data=\(1,1,!\) flags=NONE\[0\]' start_pane variation 10 3 '*\357\270\217!' check_raw_matches variation \ 'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \ 'C 0,0 data=\(2,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \ - 'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ + 'C 0,1 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ 'C 0,2 data=\(1,1,!\) flags=NONE\[0\]' start_pane invalid 10 3 '\377A' @@ -69,14 +69,14 @@ check_raw_matches overwrite-wide-pad \ start_pane overwrite-wide-with-wide 10 3 'A\343\201\202B\r\033[1C\347\225\214' check_raw_matches overwrite-wide-with-wide \ 'C 0,1 data=\(2,3,界\) flags=NONE\[0\] attr=NONE\[0\]' \ - 'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ + 'C 0,2 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ 'C 0,3 data=\(1,1,B\) flags=NONE\[0\]' start_pane wide-right-edge 4 3 'ABC\343\201\202Z' check_raw_matches wide-right-edge \ 'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \ 'C 1,0 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \ - 'C 1,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ + 'C 1,1 data=\(0,0,\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \ 'C 1,2 data=\(1,1,Z\) flags=NONE\[0\]' exit $exit_status diff --git a/regress/input-reflow-wide.sh b/regress/input-reflow-wide.sh new file mode 100644 index 000000000..a5646d25e --- /dev/null +++ b/regress/input-reflow-wide.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +# Wide-character padding must not count towards the reflowed line width. + +. ./input-common.inc + +check_reflow() +{ + # Resizing can move the first physical line into history. + $TMUX capture-pane -pN"$2" -t "$1:" -S - -E - | + normalize_capture >"$TMP" + printf '%s\n' "${3:-$text}" >"$EXP" + cmp "$TMP" "$EXP" || fail "$1 reflow (width $width, flags -pN$2)" +} + +text='中文文档甲 中文文档乙 中文文档丙 中文文档丁 中文文档戊 中文文档己' + +# Default and RGB backgrounds exercise compact and extended padding cells. +for padding in compact extended; do + style='' + if [ "$padding" = extended ]; then + style='\033[48;2;1;2;3m' + fi + width=96 + start_pane_history "wide-$padding" 96 18 "${style}${text}\\n" + check_reflow "wide-$padding" '' + for width in 47 96 47 96; do + $TMUX resize-window -t "wide-$padding:" -x "$width" -y 18 || + exit 1 + check_reflow "wide-$padding" J + if [ "$width" = 96 ]; then + check_reflow "wide-$padding" '' + fi + done +done + +# Include both a split between wide characters and an odd-width boundary. +text='中文文档' +start_pane_history boundary 8 18 "${text}\\n" +for width in 4 8 3 8; do + $TMUX resize-window -t boundary: -x "$width" -y 18 || exit 1 + check_reflow boundary J + case "$width" in + 4) + check_reflow boundary '' '中文 +文档' + ;; + 3) + check_reflow boundary '' '中 +文 +文 +档' + ;; + 8) + check_reflow boundary '' + ;; + esac +done + +exit $exit_status diff --git a/regress/input-replies.sh b/regress/input-replies.sh index 5fc77f843..1f260ca00 100644 --- a/regress/input-replies.sh +++ b/regress/input-replies.sh @@ -60,7 +60,11 @@ query_timeout() query "dsr-ok" '^[[0n' '\033[5n' 4 '' query "dsr-cursor" '^[[1;1R' '\033[6n' 6 '' -query "da-primary" '^[[?1;2c' '\033[c' 7 '' +if [ "$($TMUX display-message -p '#{image_support}')" = 1 ]; then + query "da-primary" '^[[?1;2;4c' '\033[c' 9 '' +else + query "da-primary" '^[[?1;2c' '\033[c' 7 '' +fi query "da-secondary" '^[[>84;0;0c' '\033[>c' 10 '' query "decrqm-irm-reset" '^[[4;2$y' '\033[4$p' 7 '' query "decrqm-irm-set" '^[[4;1$y' '\033[4$p' 7 '\033[4h' diff --git a/regress/menu-pane-killed.sh b/regress/menu-pane-killed.sh new file mode 100755 index 000000000..4290ab6a6 --- /dev/null +++ b/regress/menu-pane-killed.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# A menu opened for a pane is drawn again after the pane is killed. Drawing it +# must not use the pane. The terminal is a pane of a second server. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" +TMUX2="$TEST_TMUX -Ltest2$$ -f/dev/null" +trap '$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null' 0 1 15 + +$TMUX new -d 'sleep 1000' || exit 1 +$TMUX2 new -d "unset TMUX; exec $TMUX attach" || exit 1 +n=0 +while [ -z "$($TMUX lsc 2>/dev/null)" ]; do + n=$((n + 1)) + [ $n -gt 100 ] && exit 1 + sleep 0.1 +done + +P=$($TMUX splitw -d -P -F '#{pane_id}' 'sleep 1000') || exit 1 +$TMUX display-menu -c "$($TMUX lsc -F '#{client_name}')" -t $P one 1 '' || + exit 1 +$TMUX kill-pane -t $P +sleep 1 +$TMUX has || { echo "the server died"; exit 1; } +exit 0 diff --git a/regress/ncv.sh b/regress/ncv.sh new file mode 100644 index 000000000..c1aafeee7 --- /dev/null +++ b/regress/ncv.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +# Check that attributes listed in ncv are not used with colours. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null" + +fail() { + echo "$*" >&2 + exit 1 +} + +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null + +TMP=$(mktemp) +trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15 + +$TMUX2 new -d -x20 -y5 \ + "printf '\033[2;4mPLAIN\033[0m'; exec sleep 100" || exit 1 +$TMUX2 set -g status-left '' || exit 1 +$TMUX2 set -g status-right '' || exit 1 +$TMUX2 set -g status-format[0] \ + '#[fg=black,bg=green,underscore,dim]MARK' || exit 1 +$TMUX2 set -as terminal-overrides ',screen:ncv=18' || exit 1 + +$TMUX new -d -x20 -y5 || exit 1 +$TMUX set -g status off || exit 1 +$TMUX send -l "$TMUX2 attach" || exit 1 +$TMUX send Enter || exit 1 +sleep 1 + +$TMUX capturep -pe >$TMP || exit 1 +ESC=$(printf '\033') +grep -q "${ESC}\\[30m${ESC}\\[42mMARK" $TMP || + fail "coloured status text missing" +grep -q "${ESC}\\[2;4mPLAIN" $TMP || + fail "attributes without colours were filtered" +tail -1 $TMP | grep -q "${ESC}\\[4m" && fail "underline not filtered" +tail -1 $TMP | grep -q "${ESC}\\[2m" && fail "dim not filtered" + +exit 0 diff --git a/regress/session-group-name.sh b/regress/session-group-name.sh new file mode 100644 index 000000000..979c04dd5 --- /dev/null +++ b/regress/session-group-name.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +# Reusing a session name must not reuse an unrelated session group. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +trap '$TMUX kill-server 2>/dev/null' 0 1 15 + +$TMUX new -d -s a || exit 1 +$TMUX new -d -t a -s b || exit 1 +$TMUX kill-session -t a || exit 1 + +# Occupy the first suffix too, without creating a session of that name. +$TMUX new -d -t a-1 -s other || exit 1 +$TMUX new -d -s a || exit 1 +AW=$($TMUX display -p -t a: '#{window_id}') +BW=$($TMUX display -p -t b: '#{window_id}') +$TMUX new -d -t a -s c || exit 1 + +[ "$($TMUX display -p -t a: '#{session_group}')" = a-2 ] || exit 1 +[ "$($TMUX display -p -t c: '#{session_group}')" = a-2 ] || exit 1 +[ "$($TMUX display -p -t b: '#{session_group_list}')" = b ] || exit 1 +[ "$($TMUX display -p -t a: '#{window_id}')" = "$AW" ] || exit 1 +[ "$($TMUX display -p -t c: '#{window_id}')" = "$AW" ] || exit 1 +[ "$AW" != "$BW" ] || exit 1 + +# Window changes propagate only within the new group. +$TMUX new-window -d -t a || exit 1 +[ "$($TMUX display -p -t c: '#{session_windows}')" = 2 ] || exit 1 +[ "$($TMUX display -p -t b: '#{session_windows}')" = 1 ] || exit 1 + +# An existing session still joins its group; a group-only target still works. +$TMUX new -d -t c -s d || exit 1 +[ "$($TMUX display -p -t d: '#{session_group}')" = a-2 ] || exit 1 +$TMUX new -d -t a-1 -s e || exit 1 +[ "$($TMUX display -p -t e: '#{session_group}')" = a-1 ] || exit 1 +[ "$($TMUX display -p -t e: '#{window_id}')" = \ + "$($TMUX display -p -t other: '#{window_id}')" ] || exit 1 + +exit 0 diff --git a/regress/tty-keys-backtab.sh b/regress/tty-keys-backtab.sh new file mode 100644 index 000000000..b0d447533 --- /dev/null +++ b/regress/tty-keys-backtab.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +# Extended Shift+Tab must remain BackTab when the pane does not request +# modifyOtherKeys=2, including when the input carries implied Meta. + +PATH=/bin:/usr/bin +TERM=screen +export PATH TERM + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +INNER="$TEST_TMUX -LtestI$$ -f/dev/null" +OUTER="$TEST_TMUX -LtestO$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $OUTER kill-server 2>/dev/null + $INNER kill-server 2>/dev/null +} +trap cleanup 0 +trap 'exit 1' 1 2 15 + +$INNER new-session -d -s inner -x80 -y24 \ + 'stty raw -echo; exec cat -tv' || exit 1 +$INNER set-option -g status off || exit 1 +$INNER set-option -g assume-paste-time 0 || exit 1 +$INNER set-option -s extended-keys on || exit 1 +$INNER set-option -s escape-time 1000 || exit 1 +$INNER unbind-key -a -T root || exit 1 +$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t inner" || + exit 1 +$OUTER set-option -g assume-paste-time 0 || exit 1 + +i=0 +while [ -z "$($INNER list-clients -F '#{client_name}')" ]; do + [ "$i" -lt 100 ] || fail 'inner client did not attach' + sleep 0.05 + i=$((i + 1)) +done + +expected_output= + +assert_key() +{ + name=$1 + expected_output=$expected_output$2 + bytes=$(printf '%s' "$3" | od -An -v -tx1) + $OUTER send-keys -H $bytes || exit 1 + i=0 + while [ "$i" -lt 100 ]; do + actual=$($INNER capture-pane -p | tr -d '\n') + [ "$actual" = "$expected_output" ] && return 0 + sleep 0.05 + i=$((i + 1)) + done + fail "$name: got '$actual', expected '$expected_output'" +} + +pane_state='#{pane_key_mode}:#{pane_current_command}' +for mode in 0 1 2; do + for format in xterm csi-u; do + $INNER new-window \ + "printf '\033[>4;${mode}m'; stty raw -echo; exec cat -tv" || + exit 1 + key_mode="Ext $mode" + [ "$mode" -eq 0 ] && key_mode=VT10x + i=0 + while [ "$($INNER display-message -p "$pane_state")" != \ + "$key_mode:cat" ]; do + [ "$i" -lt 100 ] || fail "pane did not enter mode $mode" + sleep 0.05 + i=$((i + 1)) + done + $INNER set-option -s extended-keys-format "$format" || exit 1 + expected_output= + # Every modifier combination containing Shift. + for modifier in 2 4 6 8; do + if [ "$mode" -eq 2 ]; then + if [ "$format" = xterm ]; then + expected="^[[27;${modifier};9~" + else + expected="^[[9;${modifier}u" + fi + else + expected='^[[Z' + fi + case_name="mode $mode, $format, modifier $modifier" + assert_key "$case_name, CSI u input" "$expected" \ + "$(printf '\033[9;%su' "$modifier")" + assert_key "$case_name, xterm input" "$expected" \ + "$(printf '\033[27;%s;9~' "$modifier")" + done + done +done + +exit 0 diff --git a/regress/tty-keys-replies.sh b/regress/tty-keys-replies.sh index e67717c7a..b47e6a8a9 100755 --- a/regress/tty-keys-replies.sh +++ b/regress/tty-keys-replies.sh @@ -43,6 +43,22 @@ def matches(name, value): return "ignorefkeys" in features if name == "XDA": return kind == "ReplyTest" + if name in ("XDA-kitty", "XDA-kitty-nosync"): + required = { + "256", "RGB", "bpaste", "clipboard", "mouse", + "strikethrough", "title", "ccolour", "cstyle", "extkeys", + "focus", "hyperlinks", "osc7", "usstyle", "kittygraphics"} + if name == "XDA-kitty": + required.add("sync") + elif "sync" in features: + return False + return kind == "kitty(0.43.0)" and required <= features + if name == "XDA-ghostty": + return kind == "ghostty 1.2.0" and { + "kittygraphics", "margins", "sync", "usstyle", "progressbar"} <= features + if name == "XDA-WezTerm": + return kind == "WezTerm 20240203-110809-5046fc22" and { + "sixel", "margins", "usstyle"} <= features if name == "sync": return "sync" in features if name == "characters": @@ -54,6 +70,10 @@ reports = [ ("DA", b"\033[?65;4;21;28;52c"), ("DA2", b"\033[>85;1;0c"), ("XDA", b"\033P>|ReplyTest\033\\"), + ("XDA-kitty", b"\033P>|kitty(0.43.0)\033\\"), + ("XDA-kitty-nosync", b"\033P>|kitty(0.43.0)\033\\"), + ("XDA-ghostty", b"\033P>|ghostty 1.2.0\033\\"), + ("XDA-WezTerm", b"\033P>|WezTerm 20240203-110809-5046fc22\033\\"), ("sync", b"\033[?2026;2$y"), ("characters", b"\033[8;17;61t"), ("pixels", b"\033[4;480;800t"), @@ -80,6 +100,8 @@ for name, report in reports: else: run("set-option", "-s", "escape-time", "60000") run("set-option", "-g", "@seen", "") + if name == "XDA-kitty-nosync": + run("set-option", "-as", "terminal-features", ",*:sync@") run("bind-key", "-n", "Escape", "set-option", "-gF", "@seen", "#{@seen}E") pid, fd = os.forkpty() @@ -140,6 +162,12 @@ for name, report in reports: assert actual == baseline, (context, "unexpected effect", actual) else: assert matches(name, actual), (context, "missing effect", actual) + if name in ("XDA-kitty", "XDA-kitty-nosync", "XDA-ghostty"): + assert run("display-message", "-p", "#{I/f:kittygraphics}") == "1", ( + context, "kittygraphics feature lookup failed") + if name == "XDA-kitty-nosync": + assert run("display-message", "-p", "#{I/f:sync}") == "0", ( + context, "kitty enabled synchronized updates") assert run("show-option", "-gv", "@seen") == ( "E" if prefixed else ""), (context, "Escape count") cases += 1 diff --git a/resize.c b/resize.c index 67562522d..108f7c318 100644 --- a/resize.c +++ b/resize.c @@ -1,4 +1,4 @@ -/* $OpenBSD: resize.c,v 1.59 2026/08/20 09:19:24 nicm Exp $ */ +/* $OpenBSD: resize.c,v 1.60 2026/10/12 13:08:19 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -152,7 +152,13 @@ clients_calculate_size(int type, int current, struct client *c, *sx = UINT_MAX; *sy = UINT_MAX; } - *xpixel = *ypixel = 0; + if (w != NULL && type == WINDOW_SIZE_MANUAL) { + *xpixel = w->xpixel; + *ypixel = w->ypixel; + } else { + *xpixel = 0; + *ypixel = 0; + } /* * For latest, count the number of clients with this window. We only @@ -380,16 +386,30 @@ recalculate_size(struct window *w, int now) /* Look for a suitable client and get the new size. */ changed = clients_calculate_size(type, current, NULL, NULL, w, recalculate_size_skip_client, &sx, &sy, &xpixel, &ypixel); + if (xpixel == 0) + xpixel = DEFAULT_XPIXEL; + if (ypixel == 0) + ypixel = DEFAULT_YPIXEL; /* * Make sure the size has actually changed. If the window has already * got a resize scheduled, then use the new size; otherwise the old. */ if (w->flags & WINDOW_RESIZE) { - if (!now && changed && w->new_sx == sx && w->new_sy == sy) + if (!now && + changed && + w->new_sx == sx && + w->new_sy == sy && + w->new_xpixel == xpixel && + w->new_ypixel == ypixel) changed = 0; } else { - if (!now && changed && w->sx == sx && w->sy == sy) + if (!now && + changed && + w->sx == sx && + w->sy == sy && + w->xpixel == xpixel && + w->ypixel == ypixel) changed = 0; } diff --git a/screen-redraw.c b/screen-redraw.c index e4fb93281..9d9704f98 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1921,7 +1921,7 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) tty_reset(tty); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (wp != NULL) tty_draw_images(c, wp); else { diff --git a/screen-write.c b/screen-write.c index 754fba547..bc2cedd2c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1471,7 +1471,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) memcpy(&gc, &grid_default_cell, sizeof gc); utf8_set(&gc.data, 'E'); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1518,7 +1518,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1559,7 +1559,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1600,7 +1600,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1635,7 +1635,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) ny = 1; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1703,7 +1703,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) ny = 1; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1776,7 +1776,7 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) return; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1815,7 +1815,7 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg))) return; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1842,7 +1842,7 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) return; } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1896,7 +1896,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) return; } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -1948,7 +1948,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) struct screen *s = ctx->s; struct grid *gd = s->grid; struct grid_line *gl; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES int redraw = 0; #endif u_int rupper = s->rupper, rlower = s->rlower; @@ -1971,7 +1971,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) return; } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (rlower == screen_size_y(s) - 1) redraw = image_scroll_up(s, 1); else @@ -2003,7 +2003,7 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) ctx->bg = bg; } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_scroll_up(s, lines) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -2032,7 +2032,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) else if (lines > s->rlower - s->rupper + 1) lines = s->rlower - s->rupper + 1; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -2073,7 +2073,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) struct visible_ranges *r; struct visible_range *ri; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -2154,7 +2154,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) struct visible_ranges *r; struct visible_range *ri; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -2227,7 +2227,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) struct visible_ranges *r; struct visible_range *ri; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -2735,7 +2735,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx) } } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif @@ -3239,7 +3239,7 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len, tty_write(tty_cmd_rawstring, &ttyctx); } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* Write a SIXEL image. */ void screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, diff --git a/screen.c b/screen.c index f9ca89af5..e8d90d595 100644 --- a/screen.c +++ b/screen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen.c,v 1.107 2026/07/26 09:20:54 nicm Exp $ */ +/* $OpenBSD: screen.c,v 1.108 2026/10/12 10:50:06 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -93,7 +93,7 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->tabs = NULL; s->sel = NULL; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES TAILQ_INIT(&s->images); TAILQ_INIT(&s->saved_images); #endif @@ -101,12 +101,12 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->write_list = NULL; s->hyperlinks = NULL; - screen_reinit(s, 1); + screen_reinit(s); } /* Reinitialise screen. */ void -screen_reinit(struct screen *s, int check) +screen_reinit(struct screen *s) { s->cx = 0; s->cy = 0; @@ -125,14 +125,12 @@ screen_reinit(struct screen *s, int check) s->saved_cy = UINT_MAX; screen_reset_tabs(s); - if (check) - grid_check_is_clear(s->grid); grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); screen_clear_selection(s); screen_free_titles(s); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES image_free_all(s); #endif @@ -154,7 +152,7 @@ screen_reset_hyperlinks(struct screen *s) void screen_free(struct screen *s) { -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct image *im; #endif @@ -174,7 +172,7 @@ screen_free(struct screen *s) hyperlinks_free(s->hyperlinks); screen_free_titles(s); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* * Images saved when entering the alternate screen stay linked in the * global list; move them back so they are freed and unlinked here, or @@ -377,7 +375,7 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, if (sy != screen_size_y(s)) screen_resize_y(s, sy, eat_empty, &cy); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES image_free_all(s); #endif @@ -692,7 +690,7 @@ int screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx, sy; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct image *im; #endif @@ -709,7 +707,7 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) } memcpy(&s->saved_cell, gc, sizeof s->saved_cell); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES TAILQ_CONCAT(&s->saved_images, &s->images, entry); TAILQ_FOREACH(im, &s->saved_images, entry) im->list = &s->saved_images; @@ -728,7 +726,7 @@ int screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx = screen_size_x(s), sy = screen_size_y(s); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct image *im; #endif @@ -759,6 +757,10 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) return 0; } + /* Clamp cursor to the current screen size. */ + if (s->cy > screen_size_y(s) - 1) + s->cy = screen_size_y(s) - 1; + /* Restore the saved grid. */ grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0, s->saved_grid->sy); @@ -774,7 +776,7 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) grid_destroy(s->saved_grid); s->saved_grid = NULL; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES image_free_all(s); TAILQ_CONCAT(&s->images, &s->saved_images, entry); TAILQ_FOREACH(im, &s->images, entry) diff --git a/server-client.c b/server-client.c index 2659a665b..6f824662d 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.518 2026/10/06 10:55:55 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.520 2026/10/12 10:48:05 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -399,6 +399,9 @@ server_client_lost(struct client *c) if (c->name != NULL && (c->flags & (CLIENT_CONTROL|CLIENT_TERMINAL))) events_fire_client("client-closed", c); + c->session = NULL; + c->last_session = NULL; + if (c->flags & CLIENT_CONTROL) control_stop(c); if (c->flags & CLIENT_TERMINAL) @@ -2596,7 +2599,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) if (datalen != 0) goto bad; - if (c->flags & CLIENT_CONTROL) + if ((c->flags & CLIENT_CONTROL) || (~c->tty.flags & TTY_OPENED)) break; server_client_update_latest(c); old_sx = c->tty.sx; diff --git a/server-fn.c b/server-fn.c index e9db5a887..45809ee96 100644 --- a/server-fn.c +++ b/server-fn.c @@ -363,10 +363,7 @@ server_destroy_pane(struct window_pane *wp, int notify) u_int sy = screen_size_y(&wp->base); if (wp->fd != -1) { -#ifdef HAVE_UTEMPTER - utempter_remove_record(wp->fd); - kill(getpid(), SIGCHLD); -#endif + window_pane_utmp_remove(wp); bufferevent_free(wp->event); wp->event = NULL; close(wp->fd); diff --git a/spawn.c b/spawn.c index ab4a196c2..4717b55c1 100644 --- a/spawn.c +++ b/spawn.c @@ -322,11 +322,12 @@ spawn_pane(struct spawn_context *sc, char **cause) sc->wp0->event = NULL; } if (sc->wp0->fd != -1) { + window_pane_utmp_remove(sc->wp0); close(sc->wp0->fd); sc->wp0->fd = -1; } window_pane_reset_mode_all(sc->wp0); - screen_reinit(&sc->wp0->base, 0); + screen_reinit(&sc->wp0->base); if (sc->wp0->ictx != NULL) { input_free(sc->wp0->ictx); sc->wp0->ictx = NULL; @@ -579,16 +580,8 @@ spawn_pane(struct spawn_context *sc, char **cause) _exit(1); complete: -#ifdef HAVE_UTEMPTER - if (~new_wp->flags & PANE_EMPTY) { - xasprintf(&cp, "tmux(%lu).%%%u", (long)getpid(), new_wp->id); - utempter_add_record(new_wp->fd, cp); - kill(getpid(), SIGCHLD); - free(cp); - } -#endif - new_wp->flags &= ~PANE_EXITED; + window_pane_utmp_add(new_wp); sigprocmask(SIG_SETMASK, &oldset, NULL); window_pane_set_event(new_wp); diff --git a/tmux.1 b/tmux.1 index cf82a209f..0c48486a8 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1181 2026/10/08 07:50:05 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1184 2026/10/12 12:35:03 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: October 8 2026 $ +.Dd $Mdocdate: October 12 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -1271,6 +1271,7 @@ If is specified, list only clients connected to that session. .Tg lscm .It Xo Ic list\-commands +.Op Fl h .Op Fl F Ar format .Op Ar command .Xc @@ -1279,6 +1280,11 @@ List the syntax of .Ar command or - if omitted - of all commands supported by .Nm . +With +.Fl h , +show command names and a short description. +.Fl h +twice shows syntax as well. .Tg ls .It Xo Ic list\-sessions .Op Fl r @@ -5194,6 +5200,8 @@ Ignore function keys from and use the .Nm internal set only. +.It kittygraphics +Supports the Kitty graphics protocol with Unicode placeholders. .It margins Supports DECSLRM margins. .It mouse @@ -5262,6 +5270,16 @@ For example: set \-s user\-keys[0] "\ee[5;30012\[ti]" bind User0 resize\-pane \-L 3 .Ed +.It Xo Ic utmp +.Op Ic on | off +.Xc +Maintain utmp records for panes using +.Xr utempter 3 . +This option is unavailable if +.Nm +was built without +.Xr utempter 3 +support. .It Xo Ic variation\-selector\-always\-wide .Op Cm on | off .Xc @@ -7409,7 +7427,9 @@ The following variables are available, where appropriate: .It Li "client_written" Ta "" Ta "Bytes written to client" .It Li "command" Ta "" Ta "Name of command in use, if any" .It Li "command_list_alias" Ta "" Ta "Command alias if listing commands" +.It Li "command_list_description" Ta "" Ta "Command description if listing commands" .It Li "command_list_name" Ta "" Ta "Command name if listing commands" +.It Li "command_list_name_width" Ta "" Ta "Maximum command name width in list" .It Li "command_list_usage" Ta "" Ta "Command usage if listing commands" .It Li "config_files" Ta "" Ta "List of configuration files loaded" .It Li "cursor_blinking" Ta "" Ta "1 if the cursor is blinking" @@ -7451,6 +7471,7 @@ The following variables are available, where appropriate: .It Li "hook_window_name" Ta "" Ta "Name of window where hook was run, if any" .It Li "host" Ta "#H" Ta "Hostname of local host" .It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)" +.It Li "image_support" Ta "" Ta "1 if server has support for images" .It Li "insert_flag" Ta "" Ta "Pane insert flag" .It Li "key_string" Ta "" Ta "String representation of the key binding" .It Li "key_repeat" Ta "" Ta "1 if key binding is repeatable" @@ -7605,7 +7626,6 @@ The following variables are available, where appropriate: .It Li "session_stack" Ta "" Ta "Window indexes in most recent order" .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" -.It Li "sixel_support" Ta "" Ta "1 if server has support for SIXEL" .It Li "start_time" Ta "" Ta "Server start time" .It Li "synchronized_output_flag" Ta "" Ta "1 if pane has synchronized output enabled" .It Li "uid" Ta "" Ta "Server UID" diff --git a/tmux.h b/tmux.h index c1cb418ac..37a504ac8 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1454 2026/10/08 07:50:05 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1457 2026/10/12 12:35:03 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -77,7 +77,7 @@ struct screen_write_cline; struct screen_write_ctx; struct session; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct sixel_image; #endif @@ -638,6 +638,7 @@ enum tty_code_code { TTYC_KUP6, TTYC_KUP7, TTYC_MS, + TTYC_NCV, TTYC_NOBR, TTYC_OL, TTYC_OP, @@ -1036,7 +1037,7 @@ struct style { u_int link; }; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* Image. */ struct image { struct screen *s; @@ -1112,7 +1113,7 @@ struct screen { bitstr_t *tabs; struct screen_sel *sel; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct images images; struct images saved_images; #endif @@ -1355,6 +1356,7 @@ struct window_pane { #define PANE_CAPTUREALLKEYS 0x100000 #define PANE_FLOATOVERZOOM 0x200000 #define PANE_CLOSEONCANCEL 0x400000 +#define PANE_UTMP 0x800000 bitstr_t *sync_dirty; u_int sync_dirty_size; @@ -1763,6 +1765,7 @@ struct tty_term { #define TERM_SIXEL 0x40 #define TERM_INVALIDMS 0x80 #define TERM_NOREPLACE 0x100 +#define TERM_KITTYGRAPHICS 0x200 int flags; LIST_ENTRY(tty_term) entry; @@ -1902,7 +1905,7 @@ struct tty_ctx { size_t size; } sel; -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES struct image *image; #endif }; @@ -2074,6 +2077,7 @@ struct cmd_entry_flag { /* Command definition. */ struct cmd_entry { const char *name; + const char *description; const char *alias; struct args_parse args; @@ -3002,7 +3006,7 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *); void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); void tty_draw_images(struct client *, struct window_pane *); #endif @@ -3646,7 +3650,7 @@ void screen_write_setselection(struct screen_write_ctx *, const char *, u_char *, u_int); void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int, int); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES void screen_write_sixelimage(struct screen_write_ctx *, struct sixel_image *, u_int); #endif @@ -3670,7 +3674,7 @@ int redraw_get_status_border_cell_type(struct redraw_span **, u_int); /* screen.c */ void screen_init(struct screen *, u_int, u_int, u_int); -void screen_reinit(struct screen *, int); +void screen_reinit(struct screen *); void screen_free(struct screen *); void screen_reset_tabs(struct screen *); void screen_reset_hyperlinks(struct screen *); @@ -3725,6 +3729,8 @@ struct window *window_find_by_id(u_int); void window_update_activity(struct window *); struct window *window_create(u_int, u_int, u_int, u_int); void window_pane_set_event(struct window_pane *); +void window_pane_utmp_add(struct window_pane *); +void window_pane_utmp_remove(struct window_pane *); void window_pane_wait_finish(struct window_pane *); struct window_pane *window_get_active_at(struct window *, u_int, u_int); struct window_pane *window_find_string(struct window *, const char *); @@ -4232,7 +4238,7 @@ void spawn_editor_finish(struct window_pane *); /* regsub.c */ char *regsub(const char *, const char *, const char *, int); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* image.c */ int image_free_all(struct screen *); struct image *image_store(struct screen *, struct sixel_image *); diff --git a/tty-draw.c b/tty-draw.c index d707d1bf3..c5d66a169 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-draw.c,v 1.17 2026/09/22 06:49:47 nicm Exp $ */ +/* $OpenBSD: tty-draw.c,v 1.18 2026/10/12 15:28:45 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -253,6 +253,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Outside the area being drawn. */ empty = nx - i; gcp = &grid_default_cell; + + /* Flush the background before clearing. */ + if (empty != 0 && gcp->bg != last.bg) + empty = 0; } else { /* Get the current cell. */ grid_view_get_cell(gd, px + i, py, &gc); diff --git a/tty-features.c b/tty-features.c index 7d8568b57..1f4cd969a 100644 --- a/tty-features.c +++ b/tty-features.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-features.c,v 1.46 2026/09/24 08:16:13 nicm Exp $ */ +/* $OpenBSD: tty-features.c,v 1.48 2026/10/12 12:35:03 nicm Exp $ */ /* * Copyright (c) 2020 Nicholas Marriott @@ -369,6 +369,13 @@ static const struct tty_feature tty_feature_sixel = { TERM_SIXEL }; +/* Terminal supports Kitty graphics Unicode placeholders. */ +static const struct tty_feature tty_feature_kittygraphics = { + "kittygraphics", + NULL, + TERM_KITTYGRAPHICS +}; + /* 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\\\\", @@ -399,6 +406,7 @@ static const struct tty_feature *const tty_features[] = { &tty_feature_extkeys, &tty_feature_focus, &tty_feature_ignorefkeys, + &tty_feature_kittygraphics, &tty_feature_margins, &tty_feature_mouse, &tty_feature_osc7, @@ -638,6 +646,7 @@ tty_default_features(struct client *c, const char *name, u_int version) "focus," "hyperlinks," "margins," + "sixel," "usstyle" }, { .name = "ghostty", @@ -652,7 +661,20 @@ tty_default_features(struct client *c, const char *name, u_int version) "osc7," "sync," "usstyle," - "progressbar" + "progressbar," + "kittygraphics" + }, + { .name = "kitty", + .features = TTY_FEATURES_BASE_MODERN_XTERM "," + "ccolour," + "cstyle," + "extkeys," + "focus," + "hyperlinks," + "osc7," + "sync," + "usstyle," + "kittygraphics" }, { .name = "Rio", .features = TTY_FEATURES_BASE_MODERN_XTERM "," diff --git a/tty-keys.c b/tty-keys.c index 96c73f1b6..b9ca2f019 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-keys.c,v 1.216 2026/10/06 17:49:45 nicm Exp $ */ +/* $OpenBSD: tty-keys.c,v 1.217 2026/10/12 12:26:46 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1675,6 +1675,8 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, tty_default_features(c, "mintty", 0); else if (strncmp(tmp, "foot(", 5) == 0) tty_default_features(c, "foot", 0); + else if (strncmp(tmp, "kitty(", 6) == 0) + tty_default_features(c, "kitty", 0); else if (strncmp(tmp, "WezTerm ", 7) == 0) tty_default_features(c, "WezTerm", 0); else if (strncmp(tmp, "ghostty ", 8) == 0) diff --git a/tty-term.c b/tty-term.c index 1ac078cd4..3311701ba 100644 --- a/tty-term.c +++ b/tty-term.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty-term.c,v 1.111 2026/09/22 14:10:26 nicm Exp $ */ +/* $OpenBSD: tty-term.c,v 1.112 2026/10/12 10:39:05 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -254,6 +254,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_KUP6] = { TTYCODE_STRING, "kUP6" }, [TTYC_KUP7] = { TTYCODE_STRING, "kUP7" }, [TTYC_MS] = { TTYCODE_STRING, "Ms" }, + [TTYC_NCV] = { TTYCODE_NUMBER, "ncv" }, [TTYC_NOBR] = { TTYCODE_STRING, "Nobr" }, [TTYC_OL] = { TTYCODE_STRING, "ol" }, [TTYC_OP] = { TTYCODE_STRING, "op" }, diff --git a/tty.c b/tty.c index 02ced9fdd..494e5634f 100644 --- a/tty.c +++ b/tty.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty.c,v 1.485 2026/10/07 07:34:59 nicm Exp $ */ +/* $OpenBSD: tty.c,v 1.486 2026/10/12 10:39:05 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -51,6 +51,7 @@ static void tty_check_bg(struct tty *, struct colour_palette *, struct grid_cell *); static void tty_check_us(struct tty *, struct colour_palette *, struct grid_cell *); +static void tty_check_ncv(struct tty *, struct grid_cell *); static int tty_map_theme_colour(struct tty *, int); static void tty_colours_fg(struct tty *, const struct grid_cell *); static void tty_colours_bg(struct tty *, const struct grid_cell *); @@ -67,7 +68,7 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), struct client *, struct tty_ctx *); #endif @@ -1459,7 +1460,7 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* Update context for client. */ static int tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) @@ -1587,7 +1588,7 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), } } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* Only write to the incoming tty instead of every client. */ static void tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *), @@ -2033,7 +2034,7 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) tty_invalidate(tty); } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES void tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) { @@ -2474,6 +2475,33 @@ tty_dim_default_colour(struct tty *tty, int c, int foreground) return (c); } +static void +tty_check_ncv(struct tty *tty, struct grid_cell *gc) +{ + int n; + + if (COLOUR_DEFAULT(gc->fg) && COLOUR_DEFAULT(gc->bg)) + return; + + n = tty_term_number(tty->term, TTYC_NCV); + if (n & 2) + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + if (n & 4) + gc->attr &= ~GRID_ATTR_REVERSE; + if (n & 8) + gc->attr &= ~GRID_ATTR_BLINK; + if (n & 16) + gc->attr &= ~GRID_ATTR_DIM; + if (n & 32) + gc->attr &= ~GRID_ATTR_BRIGHT; + if (n & 64) + gc->attr &= ~GRID_ATTR_HIDDEN; + if (n & 256) + gc->attr &= ~GRID_ATTR_CHARSET; + if (n & 32768) + gc->attr &= ~GRID_ATTR_ITALICS; +} + void tty_attributes(struct tty *tty, const struct grid_cell *gc, const struct tty_style_ctx *style_ctx) @@ -2544,6 +2572,7 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_check_fg(tty, palette, &gc2); tty_check_bg(tty, palette, &gc2); tty_check_us(tty, palette, &gc2); + tty_check_ncv(tty, &gc2); /* * If any bits are being cleared or the underline colour is now default, diff --git a/window-client.c b/window-client.c index 3c2f81097..05bb2f801 100644 --- a/window-client.c +++ b/window-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-client.c,v 1.49 2026/08/31 12:41:03 kirill Exp $ */ +/* $OpenBSD: window-client.c,v 1.50 2026/10/12 12:35:03 nicm Exp $ */ /* * Copyright (c) 2017 Nicholas Marriott @@ -89,19 +89,20 @@ static const char *window_client_info_lines[] = { " #[#{E:tree-mode-border-style},acs]x#[default] " WINDOW_CLIENT_FEATURE(hyperlinks) " " WINDOW_CLIENT_FEATURE(ignorefkeys) " " - WINDOW_CLIENT_FEATURE(margins) " " - WINDOW_CLIENT_FEATURE(mouse), + WINDOW_CLIENT_FEATURE(kittygraphics) " " + WINDOW_CLIENT_FEATURE(margins), " #[#{E:tree-mode-border-style},acs]x#[default] " + WINDOW_CLIENT_FEATURE(mouse) " " WINDOW_CLIENT_FEATURE(osc7) " " WINDOW_CLIENT_FEATURE(overline) " " - WINDOW_CLIENT_FEATURE(progressbar) " " - WINDOW_CLIENT_FEATURE(rectfill), + WINDOW_CLIENT_FEATURE(progressbar), " #[#{E:tree-mode-border-style},acs]x#[default] " + WINDOW_CLIENT_FEATURE(rectfill) " " WINDOW_CLIENT_FEATURE(sixel) " " WINDOW_CLIENT_FEATURE(strikethrough) " " - WINDOW_CLIENT_FEATURE(sync) " " - WINDOW_CLIENT_FEATURE(title), + WINDOW_CLIENT_FEATURE(sync), " #[#{E:tree-mode-border-style},acs]x#[default] " + WINDOW_CLIENT_FEATURE(title) " " WINDOW_CLIENT_FEATURE(usstyle) " " WINDOW_CLIENT_FEATURE(utf8), "#[#{E:tree-mode-border-style},acs]qqqqqqqqqqqqqqn#{R:q,#{window_width}}#[default]", diff --git a/window.c b/window.c index efe3f01c8..ddc49a2a5 100644 --- a/window.c +++ b/window.c @@ -1579,6 +1579,46 @@ window_pane_scrollbar_redraw_visibility(struct window_pane *wp) server_redraw_window(wp->window); } +#ifdef HAVE_UTEMPTER +void +window_pane_utmp_add(struct window_pane *wp) +{ + char *cp; + + if (wp->fd == -1 || + (wp->flags & (PANE_EMPTY|PANE_EXITED|PANE_UTMP)) || + !options_get_number(global_options, "utmp")) + return; + xasprintf(&cp, "tmux(%lu).%%%u", (long)getpid(), wp->id); + utempter_add_record(wp->fd, cp); + kill(getpid(), SIGCHLD); + wp->flags |= PANE_UTMP; + free(cp); +} +#else +void +window_pane_utmp_add(__unused struct window_pane *wp) +{ +} +#endif + +#ifdef HAVE_UTEMPTER +void +window_pane_utmp_remove(struct window_pane *wp) +{ + if (~wp->flags & PANE_UTMP) + return; + utempter_remove_record(wp->fd); + kill(getpid(), SIGCHLD); + wp->flags &= ~PANE_UTMP; +} +#else +void +window_pane_utmp_remove(__unused struct window_pane *wp) +{ +} +#endif + static void window_pane_destroy(struct window_pane *wp) { @@ -1597,10 +1637,7 @@ window_pane_destroy(struct window_pane *wp) wp->event = NULL; } if (wp->fd != -1) { -#ifdef HAVE_UTEMPTER - utempter_remove_record(wp->fd); - kill(getpid(), SIGCHLD); -#endif + window_pane_utmp_remove(wp); close(wp->fd); wp->fd = -1; }