diff options
95 files changed, 4789 insertions, 1165 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 89c6549b..0c517a52 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,7 +29,7 @@ uname -sp && tmux -V && echo $TERM Also include: -- Your platform (Linux, OS X, or whatever). +- Your platform (Linux, macOS, or whatever). - A brief description of the problem with steps to reproduce. diff --git a/.github/README.md b/.github/README.md index 5590d0b2..353f10ec 100644 --- a/.github/README.md +++ b/.github/README.md @@ -4,7 +4,7 @@ tmux is a terminal multiplexer: it enables a number of terminals to be created, accessed, and controlled from a single screen. tmux may be detached from a screen and continue running in the background, then later reattached. -This release runs on OpenBSD, FreeBSD, NetBSD, Linux, OS X and Solaris. +This release runs on OpenBSD, FreeBSD, NetBSD, Linux, macOS and Solaris. ## Dependencies diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index c6691e46..a4d3e9b5 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -4,8 +4,14 @@ on: schedule: - cron: '0 0 * * *' +permissions: + contents: read + jobs: lock: + permissions: + issues: write # for dessant/lock-threads to lock issues + pull-requests: write # for dessant/lock-threads to lock PRs runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v2 @@ -1,5 +1,102 @@ +CHANGES FROM 3.3 TO 3.3a + +* Do not crash when run-shell produces output from a config file. + +* Do not unintentionally turn off all mouse mode when button mode is also + present. + CHANGES FROM 3.2a TO 3.3 +* Add an ACL list for users connecting to the tmux socket. Users may be + forbidden from attaching, forced to attach read-only, or allowed to attach + read-write. A new command, server-access, configures the list. File system + permissions must still be configured manually. + +* Emit window-layout-changed on swap-pane. + +* Better error reporting when applying custom layouts. + +* Handle ANSI escape sequences in run-shell output. + +* Add pane_start_path to match start_command. + +* Set PWD so shells have a hint about the real path. + +* Do not allow pipe-pane on dead panes. + +* Do not report mouse positions (incorrectly) above the maximum of 223 in + normal mouse mode. + +* Add an option (default off) to control the passthrough escape sequence. + +* Support more mouse buttons when the terminal sends them. + +* Add a window-resized hook which is fired when the window is actually resized + which may be later than the client resize. + +* Add next_session_id format with the next session ID. + +* Add formats for client and server UID and user. + +* Add argument to refresh-client -l to forward clipboard to a pane. + +* Add remain-on-exit-format to set text shown when pane is dead. + +* With split-window -f use percentages of window size not pane size. + +* Add an option (fill-character) to set the character used for unused areas of + a client. + +* Add an option (scroll-on-clear) to control if tmux scrolls into history on + clear. + +* Add a capability for OSC 7 and use it similarly to how the title is set (and + controlled by the same set-titles option). + +* Add support for systemd socket activation (where systemd creates the Unix + domain socket for tmux rather than tmux creating it). Build with + --enable-systemd. + +* Add an option (pane-border-indicators) to select how the active pane is shown + on the pane border (colour, arrows or both). + +* Support underscore styles with capture-pane -e. + +* Make pane-border-format a pane option rather than window. + +* Respond to OSC 4 queries + +* Fix g/G keys in modes to do the same thing as copy mode (and vi). + +* Bump the time terminals have to respond to device attributes queries to three + seconds. + +* If automatic-rename is off, allow the rename escape sequence to set an empty + name. + +* Trim menu item text more intelligently. + +* Add cursor-style and cursor-colour options to set the default cursor style + and colour. + +* Accept some useful and non-conflicting emacs keys in vi normal mode at the + command prompt. + +* Add a format modifier (c) to force a colour to RGB. + +* Add -s and -S to display-popup to set styles, -b to set lines and -T to set + popup title. New popup-border-lines, popup-border-style and popup-style + options set the defaults. + +* Add -e flag to set an environment variable for a popup. + +* Make send-keys without arguments send the key it is bound to (if bound to a + key). + +* Try to leave terminal cursor at the right position even when tmux is drawing + its own cursor or selection (such as at the command prompt and in choose + mode) for people using screen readers and similar which can make use of it. + * Change so that {} is converted to tmux commands immediately when parsed. This means it must contain valid tmux commands. For commands which expand %% and %%%, this now only happens within string arguments. Use of nested aliases @@ -1205,7 +1302,7 @@ Incompatible Changes bind -Tcopy-mode C-r command-prompt -i -p'search up' "send -X search-backward-incremental '%%'" - There are also some new commmands available with send -X, such as + There are also some new commands available with send -X, such as copy-pipe-and-cancel. * set-remain-on-exit has gone -- can be achieved with hooks instead. * Hooks: before hooks have been removed and only a selection of commands now @@ -3181,7 +3278,7 @@ The list of older changes is below. * (nicm) -n on new-session is now -s, and -n is now the initial window name. This was documented but not implemented :-/. * (nicm) kill-window command, bound to & by default (because it should be hard - to hit accidently). + to hit accidentally). * (nicm) bell-style option with three choices: "none" completely ignore bell; "any" pass through a bell in any window to current; "current" ignore bells except in current window. This applies only to the bell terminal signal, diff --git a/Makefile.am b/Makefile.am index 68494932..14d8874b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,6 +123,7 @@ dist_tmux_SOURCES = \ cmd-select-pane.c \ cmd-select-window.c \ cmd-send-keys.c \ + cmd-server-access.c \ cmd-set-buffer.c \ cmd-set-environment.c \ cmd-set-option.c \ @@ -149,6 +150,7 @@ dist_tmux_SOURCES = \ grid-reader.c \ grid-view.c \ grid.c \ + hyperlinks.c \ input-keys.c \ input.c \ job.c \ @@ -172,6 +174,7 @@ dist_tmux_SOURCES = \ screen-redraw.c \ screen-write.c \ screen.c \ + server-acl.c \ server-client.c \ server-fn.c \ server.c \ @@ -204,6 +207,11 @@ if NEED_FORKPTY nodist_tmux_SOURCES += compat/forkpty-@PLATFORM@.c endif +# Add compat file for systemd. +if HAVE_SYSTEMD +nodist_tmux_SOURCES += compat/systemd.c +endif + # Add compat file for utf8proc. if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c @@ -4,7 +4,7 @@ tmux is a terminal multiplexer: it enables a number of terminals to be created, accessed, and controlled from a single screen. tmux may be detached from a screen and continue running in the background, then later reattached. -This release runs on OpenBSD, FreeBSD, NetBSD, Linux, OS X and Solaris. +This release runs on OpenBSD, FreeBSD, NetBSD, Linux, macOS and Solaris. * Dependencies @@ -3,7 +3,7 @@ tmuxへようこそ! tmuxはターミナルマルチプレクサーです。複数のターミナルを一つのスクリーン内に作成し、操作することができます。 バックグラウンドで処理を実行中に一度スクリーンから離れて後から復帰することも可能です。 -OpenBSD、FreeBSD、NetBSD、Linux、OS X、Solarisで実行できます。 +OpenBSD、FreeBSD、NetBSD、Linux、macOS、Solarisで実行できます。 tmuxはlibevent 2.x.に依存します。 下記からダウンロードしてください。 diff --git a/arguments.c b/arguments.c index 4b08de2c..46777f7f 100644 --- a/arguments.c +++ b/arguments.c @@ -108,6 +108,7 @@ args_value_as_string(struct args_value *value) case ARGS_STRING: return (value->string); } + fatalx("unexpected argument type"); } /* Create an empty arguments set. */ @@ -130,8 +131,9 @@ args_parse(const struct args_parse *parse, struct args_value *values, u_int i; enum args_parse_type type; struct args_value *value, *new; - u_char flag, argument; + u_char flag; const char *found, *string, *s; + int optional_argument; if (count == 0) return (args_create()); @@ -168,18 +170,27 @@ args_parse(const struct args_parse *parse, struct args_value *values, args_free(args); return (NULL); } - argument = *++found; - if (argument != ':') { + if (*++found != ':') { log_debug("%s: -%c", __func__, flag); args_set(args, flag, NULL); continue; } + if (*found == ':') { + optional_argument = 1; + found++; + } new = xcalloc(1, sizeof *new); if (*string != '\0') { new->type = ARGS_STRING; new->string = xstrdup(string); } else { if (i == count) { + if (optional_argument) { + log_debug("%s: -%c", __func__, + flag); + args_set(args, flag, NULL); + continue; + } xasprintf(cause, "-%c expects an argument", flag); @@ -753,6 +764,7 @@ args_make_commands(struct args_command_state *state, int argc, char **argv, case CMD_PARSE_SUCCESS: return (pr->cmdlist); } + fatalx("invalid parse return state"); } /* Free commands state. */ @@ -819,6 +831,12 @@ args_strtonum(struct args *args, u_char flag, long long minval, return (0); } value = TAILQ_LAST(&entry->values, args_values); + if (value == NULL || + value->type != ARGS_STRING || + value->string == NULL) { + *cause = xstrdup("missing"); + return (0); + } ll = strtonum(value->string, minval, maxval, &errstr); if (errstr != NULL) { @@ -830,6 +848,41 @@ args_strtonum(struct args *args, u_char flag, long long minval, return (ll); } +/* Convert an argument value to a number, and expand formats. */ +long long +args_strtonum_and_expand(struct args *args, u_char flag, long long minval, + long long maxval, struct cmdq_item *item, char **cause) +{ + const char *errstr; + char *formatted; + long long ll; + struct args_entry *entry; + struct args_value *value; + + if ((entry = args_find(args, flag)) == NULL) { + *cause = xstrdup("missing"); + return (0); + } + value = TAILQ_LAST(&entry->values, args_values); + if (value == NULL || + value->type != ARGS_STRING || + value->string == NULL) { + *cause = xstrdup("missing"); + return (0); + } + + formatted = format_single_from_target(item, value->string); + ll = strtonum(formatted, minval, maxval, &errstr); + free(formatted); + if (errstr != NULL) { + *cause = xstrdup(errstr); + return (0); + } + + *cause = NULL; + return (ll); +} + /* Convert an argument to a number which may be a percentage. */ long long args_percentage(struct args *args, u_char flag, long long minval, @@ -886,3 +939,70 @@ args_string_percentage(const char *value, long long minval, long long maxval, *cause = NULL; return (ll); } + +/* + * Convert an argument to a number which may be a percentage, and expand + * formats. + */ +long long +args_percentage_and_expand(struct args *args, u_char flag, long long minval, + long long maxval, long long curval, struct cmdq_item *item, char **cause) +{ + const char *value; + struct args_entry *entry; + + if ((entry = args_find(args, flag)) == NULL) { + *cause = xstrdup("missing"); + return (0); + } + value = TAILQ_LAST(&entry->values, args_values)->string; + return (args_string_percentage_and_expand(value, minval, maxval, curval, + item, cause)); +} + +/* + * Convert a string to a number which may be a percentage, and expand formats. + */ +long long +args_string_percentage_and_expand(const char *value, long long minval, + long long maxval, long long curval, struct cmdq_item *item, char **cause) +{ + const char *errstr; + long long ll; + size_t valuelen = strlen(value); + char *copy, *f; + + if (value[valuelen - 1] == '%') { + copy = xstrdup(value); + copy[valuelen - 1] = '\0'; + + f = format_single_from_target(item, copy); + ll = strtonum(f, 0, 100, &errstr); + free(f); + free(copy); + if (errstr != NULL) { + *cause = xstrdup(errstr); + return (0); + } + ll = (curval * ll) / 100; + if (ll < minval) { + *cause = xstrdup("too small"); + return (0); + } + if (ll > maxval) { + *cause = xstrdup("too large"); + return (0); + } + } else { + f = format_single_from_target(item, value); + ll = strtonum(f, minval, maxval, &errstr); + free(f); + if (errstr != NULL) { + *cause = xstrdup(errstr); + return (0); + } + } + + *cause = NULL; + return (ll); +} @@ -51,8 +51,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) return (CMD_RETURN_NORMAL); cfg_finished = 1; - if (!RB_EMPTY(&sessions)) - cfg_show_causes(RB_MIN(sessions, &sessions)); + cfg_show_causes(NULL); if (cfg_item != NULL) cmdq_continue(cfg_item); @@ -238,11 +237,29 @@ cfg_print_causes(struct cmdq_item *item) void cfg_show_causes(struct session *s) { + struct client *c = TAILQ_FIRST(&clients); struct window_pane *wp; struct window_mode_entry *wme; u_int i; - if (s == NULL || cfg_ncauses == 0) + if (cfg_ncauses == 0) + return; + + if (c != NULL && (c->flags & CLIENT_CONTROL)) { + for (i = 0; i < cfg_ncauses; i++) { + control_write(c, "%%config-error %s", cfg_causes[i]); + free(cfg_causes[i]); + } + goto out; + } + + if (s == NULL) { + if (c != NULL && c->session != NULL) + s = c->session; + else + s = RB_MIN(sessions, &sessions); + } + if (s == NULL || s->attached == 0) /* wait for an attached session */ return; wp = s->curw->window->active; @@ -250,10 +267,11 @@ cfg_show_causes(struct session *s) if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); for (i = 0; i < cfg_ncauses; i++) { - window_copy_add(wp, "%s", cfg_causes[i]); + window_copy_add(wp, 0, "%s", cfg_causes[i]); free(cfg_causes[i]); } +out: free(cfg_causes); cfg_causes = NULL; cfg_ncauses = 0; @@ -361,6 +361,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, /* Send identify messages. */ client_send_identify(ttynam, termname, caps, ncaps, cwd, feat); tty_term_free_list(caps, ncaps); + proc_flush_peer(client_peer); /* Send first command. */ if (msg == MSG_COMMAND) { @@ -531,7 +532,7 @@ client_signal(int sig) if (sig == SIGCHLD) waitpid(WAIT_ANY, &status, WNOHANG); else if (!client_attached) { - if (sig == SIGTERM) + if (sig == SIGTERM || sig == SIGHUP) proc_exit(client_proc); } else { switch (sig) { diff --git a/cmd-attach-session.c b/cmd-attach-session.c index cc795b22..4e2d15da 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -43,7 +43,7 @@ const struct cmd_entry cmd_attach_session_entry = { /* -t is special */ - .flags = CMD_STARTSERVER, + .flags = CMD_STARTSERVER|CMD_READONLY, .exec = cmd_attach_session_exec }; @@ -69,6 +69,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, if (c == NULL) return (CMD_RETURN_NORMAL); + if (server_client_check_nested(c)) { cmdq_error(item, "sessions should be nested with care, " "unset $TMUX to force"); @@ -157,6 +158,9 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, c->flags |= CLIENT_ATTACHED; } + if (cfg_finished) + cfg_show_causes(s); + return (CMD_RETURN_NORMAL); } diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 964f831e..422f87d6 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -53,8 +53,8 @@ const struct cmd_entry cmd_clear_history_entry = { .name = "clear-history", .alias = "clearhist", - .args = { "t:", 0, 0, NULL }, - .usage = CMD_TARGET_PANE_USAGE, + .args = { "Ht:", 0, 0, NULL }, + .usage = "[-H] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -133,7 +133,8 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, if (Sflag != NULL && strcmp(Sflag, "-") == 0) top = 0; else { - n = args_strtonum(args, 'S', INT_MIN, SHRT_MAX, &cause); + n = args_strtonum_and_expand(args, 'S', INT_MIN, SHRT_MAX, + item, &cause); if (cause != NULL) { top = gd->hsize; free(cause); @@ -149,7 +150,8 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, if (Eflag != NULL && strcmp(Eflag, "-") == 0) bottom = gd->hsize + gd->sy - 1; else { - n = args_strtonum(args, 'E', INT_MIN, SHRT_MAX, &cause); + n = args_strtonum_and_expand(args, 'E', INT_MIN, SHRT_MAX, + item, &cause); if (cause != NULL) { bottom = gd->hsize + gd->sy - 1; free(cause); @@ -175,7 +177,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, buf = NULL; for (i = top; i <= bottom; i++) { line = grid_string_cells(gd, 0, i, sx, &gc, with_codes, - escape_c0, !join_lines && !no_trim); + escape_c0, !join_lines && !no_trim, wp->screen); linelen = strlen(line); buf = cmd_capture_pane_append(buf, len, line, linelen); @@ -202,6 +204,8 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_clear_history_entry) { window_pane_reset_mode_all(wp); grid_clear_history(wp->base.grid); + if (args_has(args, 'H')) + screen_reset_hyperlinks(wp->screen); return (CMD_RETURN_NORMAL); } diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 7aa1d217..f2f4b2e3 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -100,7 +100,7 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) const struct window_mode *mode; if (cmd_get_entry(self) == &cmd_choose_buffer_entry) { - if (paste_get_top(NULL) == NULL) + if (paste_is_empty()) return (CMD_RETURN_NORMAL); mode = &window_buffer_mode; } else if (cmd_get_entry(self) == &cmd_choose_client_entry) { diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index a7a02702..4455856b 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -112,7 +112,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) } next_prompt = prompts; } else - next_prompt = prompts = xstrdup (s); + next_prompt = prompts = xstrdup(s); if ((s = args_get(args, 'I')) != NULL) next_input = inputs = xstrdup(s); else diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 95841962..ce8c95e0 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -72,7 +72,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) int wait = !args_has(args, 'b'); cdata = xcalloc(1, sizeof *cdata); - cdata->cmdlist = args_make_commands_now(self, item, 0, 0); + cdata->cmdlist = args_make_commands_now(self, item, 0, 1); if (cdata->cmdlist == NULL) return (CMD_RETURN_ERROR); diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 1a11bd01..e6a503b1 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -52,10 +52,12 @@ const struct cmd_entry cmd_display_popup_entry = { .name = "display-popup", .alias = "popup", - .args = { "BCc:d:Eh:t:w:x:y:", 0, -1, NULL }, - .usage = "[-BCE] [-c target-client] [-d start-directory] [-h height] " - CMD_TARGET_PANE_USAGE " [-w width] " - "[-x position] [-y position] [shell-command]", + .args = { "Bb:Cc:d:e:Eh:s:S:t:T:w:x:y:", 0, -1, NULL }, + .usage = "[-BCE] [-b border-lines] [-c target-client] " + "[-d start-directory] [-e environment] [-h height] " + "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE + "[-T title] [-w width] [-x position] [-y position] " + "[shell-command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -353,9 +355,16 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) struct client *tc = cmdq_get_target_client(item); struct tty *tty = &tc->tty; const char *value, *shell, *shellcmd = NULL; - char *cwd, *cause, **argv = NULL; + const char *style = args_get(args, 's'); + const char *border_style = args_get(args, 'S'); + char *cwd, *cause = NULL, **argv = NULL, *title; int flags = 0, argc = 0; + enum box_lines lines = BOX_LINES_DEFAULT; u_int px, py, w, h, count = args_count(args); + struct args_value *av; + struct environ *env = NULL; + struct options *o = s->curw->window->options; + struct options_entry *oe; if (args_has(args, 'C')) { server_client_clear_overlay(tc); @@ -391,6 +400,20 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h)) return (CMD_RETURN_NORMAL); + value = args_get(args, 'b'); + if (args_has(args, 'B')) + lines = BOX_LINES_NONE; + else if (value != NULL) { + oe = options_get(o, "popup-border-lines"); + lines = options_find_choice(options_table_entry(oe), value, + &cause); + if (cause != NULL) { + cmdq_error(item, "popup-border-lines %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + value = args_get(args, 'd'); if (value != NULL) cwd = format_single_from_target(item, value); @@ -409,17 +432,34 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) } else args_to_vector(args, &argc, &argv); + if (args_has(args, 'e') >= 1) { + env = environ_create(); + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(env, av->string, 0); + av = args_next_value(av); + } + } + + if (args_has(args, 'T')) + title = format_single_from_target(item, args_get(args, 'T')); + else + title = xstrdup(""); if (args_has(args, 'E') > 1) flags |= POPUP_CLOSEEXITZERO; else if (args_has(args, 'E')) flags |= POPUP_CLOSEEXIT; - if (args_has(args, 'B')) - flags |= POPUP_NOBORDER; - if (popup_display(flags, item, px, py, w, h, shellcmd, argc, argv, cwd, - tc, s, NULL, NULL) != 0) { + if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc, + argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) { cmd_free_argv(argc, argv); + if (env != NULL) + environ_free(env); + free(title); return (CMD_RETURN_NORMAL); } + if (env != NULL) + environ_free(env); + free(title); cmd_free_argv(argc, argv); return (CMD_RETURN_WAIT); } diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 5773a2d0..06f6dc27 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -144,7 +144,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, llen = 0; if (sx < len * 6 || sy < 5) { - tty_attributes(tty, &fgc, &grid_default_cell, NULL); + tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); if (sx >= len + llen + 1) { len += llen + 1; tty_cursor(tty, xoff + px - len / 2, yoff + py); @@ -161,7 +161,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, px -= len * 3; py -= 2; - tty_attributes(tty, &bgc, &grid_default_cell, NULL); + tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL); for (ptr = buf; *ptr != '\0'; ptr++) { if (*ptr < '0' || *ptr > '9') continue; @@ -179,7 +179,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, if (sy <= 6) goto out; - tty_attributes(tty, &fgc, &grid_default_cell, NULL); + tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL); if (rlen != 0 && sx >= rlen) { tty_cursor(tty, xoff + sx - rlen, yoff); tty_putn(tty, rbuf, rlen, rlen); diff --git a/cmd-if-shell.c b/cmd-if-shell.c index 211e08d6..205a8ce1 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -118,7 +118,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) if (cdata->client != NULL) cdata->client->references++; - if (job_run(shellcmd, 0, NULL, s, + if (job_run(shellcmd, 0, NULL, NULL, s, server_client_get_cwd(cmdq_get_client(item), s), NULL, cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1, -1) == NULL) { diff --git a/cmd-join-pane.c b/cmd-join-pane.c index cb3fb343..e82b4cde 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -71,10 +71,11 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) struct window *src_w, *dst_w; struct window_pane *src_wp, *dst_wp; char *cause = NULL; - int size, percentage, dst_idx; + int size, dst_idx; int flags; enum layout_type type; struct layout_cell *lc; + u_int curval = 0; dst_s = target->s; dst_wl = target->wl; @@ -97,24 +98,31 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; - size = -1; - if (args_has(args, 'l')) { - if (type == LAYOUT_TOPBOTTOM) { - size = args_percentage(args, 'l', 0, INT_MAX, - dst_wp->sy, &cause); + /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ + if (args_has(args, 'l') || args_has(args, 'p')) { + if (args_has(args, 'f')) { + if (type == LAYOUT_TOPBOTTOM) + curval = dst_w->sy; + else + curval = dst_w->sx; } else { - size = args_percentage(args, 'l', 0, INT_MAX, - dst_wp->sx, &cause); - } - } else if (args_has(args, 'p')) { - percentage = args_strtonum(args, 'p', 0, 100, &cause); - if (cause == NULL) { if (type == LAYOUT_TOPBOTTOM) - size = (dst_wp->sy * percentage) / 100; + curval = dst_wp->sy; else - size = (dst_wp->sx * percentage) / 100; + curval = dst_wp->sx; } } + + size = -1; + if (args_has(args, 'l')) { + size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, + item, &cause); + } else if (args_has(args, 'p')) { + size = args_strtonum_and_expand(args, 'l', 0, 100, item, + &cause); + if (cause == NULL) + size = curval * size / 100; + } if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); diff --git a/cmd-list-clients.c b/cmd-list-clients.c index a5b7d147..53a99178 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -31,6 +31,8 @@ #define LIST_CLIENTS_TEMPLATE \ "#{client_name}: #{session_name} " \ "[#{client_width}x#{client_height} #{client_termname}] " \ + "#{?#{!=:#{client_uid},#{uid}}," \ + "[user #{?client_user,#{client_user},#{client_uid},}] ,}" \ "#{?client_flags,(,}#{client_flags}#{?client_flags,),}" static enum cmd_retval cmd_list_clients_exec(struct cmd *, struct cmdq_item *); diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index 59810dea..70fd7ed9 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -77,7 +77,7 @@ cmd_load_buffer_done(__unused struct client *c, const char *path, int error, } else if (tc != NULL && tc->session != NULL && (~tc->flags & CLIENT_DEAD)) - tty_set_selection(&tc->tty, copy, bsize); + tty_set_selection(&tc->tty, "", copy, bsize); if (tc != NULL) server_client_unref(tc); } diff --git a/cmd-new-session.c b/cmd-new-session.c index cb9abfb3..c90369bc 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -333,13 +333,6 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) server_client_set_key_table(c, NULL); } - /* - * If there are still configuration file errors to display, put the new - * session's current window into more mode and display them now. - */ - if (cfg_finished) - cfg_show_causes(s); - /* Print if requested. */ if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) @@ -357,6 +350,9 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) cmd_find_from_session(&fs, s, 0); cmdq_insert_hook(s, item, &fs, "after-new-session"); + if (cfg_finished) + cfg_show_causes(s); + if (sc.argv != NULL) cmd_free_argv(sc.argc, sc.argv); free(cwd); diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c index 20318492..0fa656ce 100644 --- a/cmd-pipe-pane.c +++ b/cmd-pipe-pane.c @@ -67,6 +67,12 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) struct format_tree *ft; sigset_t set, oldset; + /* Do nothing if pane is dead. */ + if (wp->fd == -1 || (wp->flags & PANE_EXITED)) { + cmdq_error(item, "target pane has exited"); + return (CMD_RETURN_ERROR); + } + /* Destroy the old pipe. */ old_fd = wp->pipe_fd; if (wp->pipe_fd != -1) { @@ -130,7 +136,7 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) sigprocmask(SIG_SETMASK, &oldset, NULL); close(pipe_fd[0]); - null_fd = open(_PATH_DEVNULL, O_WRONLY, 0); + null_fd = open(_PATH_DEVNULL, O_WRONLY); if (out) { if (dup2(pipe_fd[1], STDIN_FILENO) == -1) _exit(1); diff --git a/cmd-queue.c b/cmd-queue.c index 4fbdc4e7..8325e2e8 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -19,9 +19,11 @@ #include <sys/types.h> #include <ctype.h> +#include <pwd.h> #include <stdlib.h> #include <string.h> #include <time.h> +#include <unistd.h> #include "tmux.h" @@ -124,7 +126,7 @@ cmdq_new(void) { struct cmdq_list *queue; - queue = xcalloc (1, sizeof *queue); + queue = xcalloc(1, sizeof *queue); TAILQ_INIT (&queue->list); return (queue); } @@ -558,17 +560,31 @@ cmdq_add_message(struct cmdq_item *item) { struct client *c = item->client; struct cmdq_state *state = item->state; - const char *name, *key; + const char *key; char *tmp; + uid_t uid; + struct passwd *pw; + char *user = NULL; tmp = cmd_print(item->cmd); if (c != NULL) { - name = c->name; + uid = proc_get_peer_uid(c->peer); + if (uid != (uid_t)-1 && uid != getuid()) { + if ((pw = getpwuid(uid)) != NULL) + xasprintf(&user, "[%s]", pw->pw_name); + else + user = xstrdup("[unknown]"); + } else + user = xstrdup(""); if (c->session != NULL && state->event.key != KEYC_NONE) { key = key_string_lookup_key(state->event.key, 0); - server_add_message("%s key %s: %s", name, key, tmp); - } else - server_add_message("%s command: %s", name, tmp); + server_add_message("%s%s key %s: %s", c->name, user, + key, tmp); + } else { + server_add_message("%s%s command: %s", c->name, user, + tmp); + } + free(user); } else server_add_message("command: %s", tmp); free(tmp); @@ -840,7 +856,7 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); } - window_copy_add(wp, "%s", msg); + window_copy_add(wp, 0, "%s", msg); } free(msg); diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 821558ae..6b947280 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -34,7 +34,7 @@ const struct cmd_entry cmd_refresh_client_entry = { .name = "refresh-client", .alias = "refresh", - .args = { "A:B:cC:Df:F:lLRSt:U", 0, 1, NULL }, + .args = { "A:B:cC:Df:F:l::LRSt:U", 0, 1, NULL }, .usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] " "[-C XxY] [-f flags] " CMD_TARGET_CLIENT_USAGE " [adjustment]", @@ -163,6 +163,37 @@ out: } static enum cmd_retval +cmd_refresh_client_clipboard(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); + const char *p; + u_int i; + struct cmd_find_state fs; + + p = args_get(args, 'l'); + if (p == NULL) { + if (tc->flags & CLIENT_CLIPBOARDBUFFER) + return (CMD_RETURN_NORMAL); + tc->flags |= CLIENT_CLIPBOARDBUFFER; + } else { + if (cmd_find_target(&fs, item, p, CMD_FIND_PANE, 0) != 0) + return (CMD_RETURN_ERROR); + for (i = 0; i < tc->clipboard_npanes; i++) { + if (tc->clipboard_panes[i] == fs.wp->id) + break; + } + if (i != tc->clipboard_npanes) + return (CMD_RETURN_NORMAL); + tc->clipboard_panes = xreallocarray(tc->clipboard_panes, + tc->clipboard_npanes + 1, sizeof *tc->clipboard_panes); + tc->clipboard_panes[tc->clipboard_npanes++] = fs.wp->id; + } + tty_clipboard_query(&tc->tty); + return (CMD_RETURN_NORMAL); +} + +static enum cmd_retval cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); @@ -224,10 +255,8 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (args_has(args, 'l')) { - tty_putcode_ptr2(&tc->tty, TTYC_MS, "", "?"); - return (CMD_RETURN_NORMAL); - } + if (args_has(args, 'l')) + return (cmd_refresh_client_clipboard(self, item)); if (args_has(args, 'F')) /* -F is an alias for -f */ server_client_set_flags(tc, args_get(args, 'F')); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 81744f72..c9439441 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -60,7 +60,7 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) const char *errstr; char *cause; u_int adjust; - int x, y; + int x, y, status; struct grid *gd = wp->base.grid; if (args_has(args, 'T')) { @@ -121,6 +121,17 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } + status = options_get_number(w->options, "pane-border-status"); + switch (status) { + case PANE_STATUS_TOP: + if (y != INT_MAX && wp->yoff == 1) + y++; + break; + case PANE_STATUS_BOTTOM: + if (y != INT_MAX && wp->yoff + wp->sy == w->sy - 1) + y++; + break; + } layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y); } diff --git a/cmd-run-shell.c b/cmd-run-shell.c index bf43d313..560eface 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -84,22 +84,17 @@ cmd_run_shell_print(struct job *job, const char *msg) if (cdata->wp_id != -1) wp = window_pane_find_by_id(cdata->wp_id); - if (wp == NULL) { - if (cdata->item != NULL) { - cmdq_print(cdata->item, "%s", msg); - return; - } - if (cmd_find_from_nothing(&fs, 0) != 0) - return; + if (wp == NULL && cdata->item != NULL && cdata->client != NULL) + wp = server_client_get_pane(cdata->client); + if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0) wp = fs.wp; - if (wp == NULL) - return; - } + if (wp == NULL) + return; wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); - window_copy_add(wp, "%s", msg); + window_copy_add(wp, 1, "%s", msg); } static enum cmd_retval @@ -188,7 +183,7 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg) cmd_run_shell_free(cdata); return; } - if (job_run(cmd, 0, NULL, cdata->s, cdata->cwd, NULL, + if (job_run(cmd, 0, NULL, NULL, cdata->s, cdata->cwd, NULL, cmd_run_shell_callback, cmd_run_shell_free, cdata, cdata->flags, -1, -1) == NULL) cmd_run_shell_free(cdata); @@ -227,7 +222,8 @@ cmd_run_shell_callback(struct job *job) int retcode, status; do { - if ((line = evbuffer_readline(event->input)) != NULL) { + line = evbuffer_readln(event->input, NULL, EVBUFFER_EOL_LF); + if (line != NULL) { cmd_run_shell_print(job, line); free(line); } diff --git a/cmd-select-layout.c b/cmd-select-layout.c index c857a0e1..6dfe2b6a 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -77,7 +77,7 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) struct window *w = wl->window; struct window_pane *wp = target->wp; const char *layoutname; - char *oldlayout; + char *oldlayout, *cause; int next, previous, layout; server_unzoom_window(w); @@ -124,8 +124,9 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) } if (layoutname != NULL) { - if (layout_parse(w, layoutname) == -1) { - cmdq_error(item, "can't set layout: %s", layoutname); + if (layout_parse(w, layoutname, &cause) == -1) { + cmdq_error(item, "%s: %s", cause, layoutname); + free(cause); goto error; } goto changed; diff --git a/cmd-send-keys.c b/cmd-send-keys.c index 47fa1caa..e22d94a6 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -151,7 +151,8 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) char *cause = NULL; if (args_has(args, 'N')) { - np = args_strtonum(args, 'N', 1, UINT_MAX, &cause); + np = args_strtonum_and_expand(args, 'N', 1, UINT_MAX, item, + &cause); if (cause != NULL) { cmdq_error(item, "repeat count %s", cause); free(cause); @@ -203,6 +204,8 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) } if (count == 0) { + if (args_has(args, 'N') || args_has(args, 'R')) + return (CMD_RETURN_NORMAL); for (; np != 0; np--) cmd_send_keys_inject_key(item, NULL, event->key); return (CMD_RETURN_NORMAL); diff --git a/cmd-server-access.c b/cmd-server-access.c new file mode 100644 index 00000000..b2b718b8 --- /dev/null +++ b/cmd-server-access.c @@ -0,0 +1,147 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/stat.h> +#include <sys/types.h> + +#include <pwd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "tmux.h" + +/* + * Controls access to session. + */ + +static enum cmd_retval cmd_server_access_exec(struct cmd *, struct cmdq_item *); + +const struct cmd_entry cmd_server_access_entry = { + .name = "server-access", + .alias = NULL, + + .args = { "adlrw", 0, 1, NULL }, + .usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]", + + .flags = CMD_CLIENT_CANFAIL, + .exec = cmd_server_access_exec +}; + +static enum cmd_retval +cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw) +{ + struct client *loop; + struct server_acl_user *user; + uid_t uid; + + if ((user = server_acl_user_find(pw->pw_uid)) == NULL) { + cmdq_error(item, "user %s not found", pw->pw_name); + return (CMD_RETURN_ERROR); + } + TAILQ_FOREACH(loop, &clients, entry) { + uid = proc_get_peer_uid(loop->peer); + if (uid == server_acl_get_uid(user)) { + loop->exit_message = xstrdup("access not allowed"); + loop->flags |= CLIENT_EXIT; + } + } + server_acl_user_deny(pw->pw_uid); + + return (CMD_RETURN_NORMAL); +} + +static enum cmd_retval +cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) +{ + + struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_target_client(item); + char *name; + struct passwd *pw = NULL; + + if (args_has(args, 'l')) { + server_acl_display(item); + return (CMD_RETURN_NORMAL); + } + if (args_count(args) == 0) { + cmdq_error(item, "missing user argument"); + return (CMD_RETURN_ERROR); + } + + name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); + if (*name != '\0') + pw = getpwnam(name); + if (pw == NULL) { + cmdq_error(item, "unknown user: %s", name); + return (CMD_RETURN_ERROR); + } + free(name); + + if (pw->pw_uid == 0 || pw->pw_uid == getuid()) { + cmdq_error(item, "%s owns the server, can't change access", + pw->pw_name); + return (CMD_RETURN_ERROR); + } + + if (args_has(args, 'a') && args_has(args, 'd')) { + cmdq_error(item, "-a and -d cannot be used together"); + return (CMD_RETURN_ERROR); + } + if (args_has(args, 'w') && args_has(args, 'r')) { + cmdq_error(item, "-r and -w cannot be used together"); + return (CMD_RETURN_ERROR); + } + + if (args_has(args, 'd')) + return (cmd_server_access_deny(item, pw)); + if (args_has(args, 'a')) { + if (server_acl_user_find(pw->pw_uid) != NULL) { + cmdq_error(item, "user %s is already added", + pw->pw_name); + return (CMD_RETURN_ERROR); + } + server_acl_user_allow(pw->pw_uid); + /* Do not return - allow -r or -w with -a. */ + } else if (args_has(args, 'r') || args_has(args, 'w')) { + /* -r or -w implies -a if user does not exist. */ + if (server_acl_user_find(pw->pw_uid) == NULL) + server_acl_user_allow(pw->pw_uid); + } + + if (args_has(args, 'w')) { + if (server_acl_user_find(pw->pw_uid) == NULL) { + cmdq_error(item, "user %s not found", pw->pw_name); + return (CMD_RETURN_ERROR); + } + server_acl_user_allow_write(pw->pw_uid); + return (CMD_RETURN_NORMAL); + } + + if (args_has(args, 'r')) { + if (server_acl_user_find(pw->pw_uid) == NULL) { + cmdq_error(item, "user %s not found", pw->pw_name); + return (CMD_RETURN_ERROR); + } + server_acl_user_deny_write(pw->pw_uid); + return (CMD_RETURN_NORMAL); + } + + return (CMD_RETURN_NORMAL); +} diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c index 9112683f..35e72955 100644 --- a/cmd-set-buffer.c +++ b/cmd-set-buffer.c @@ -69,8 +69,13 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) pb = paste_get_name(bufname); if (cmd_get_entry(self) == &cmd_delete_buffer_entry) { - if (pb == NULL) + if (pb == NULL) { + if (bufname != NULL) { + cmdq_error(item, "unknown buffer: %s", bufname); + return (CMD_RETURN_ERROR); + } pb = paste_get_top(&bufname); + } if (pb == NULL) { cmdq_error(item, "no buffer"); return (CMD_RETURN_ERROR); @@ -80,8 +85,13 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(args, 'n')) { - if (pb == NULL) + if (pb == NULL) { + if (bufname != NULL) { + cmdq_error(item, "unknown buffer: %s", bufname); + return (CMD_RETURN_ERROR); + } pb = paste_get_top(&bufname); + } if (pb == NULL) { cmdq_error(item, "no buffer"); return (CMD_RETURN_ERROR); @@ -121,7 +131,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } if (args_has(args, 'w') && tc != NULL) - tty_set_selection(&tc->tty, bufdata, bufsize); + tty_set_selection(&tc->tty, "", bufdata, bufsize); return (CMD_RETURN_NORMAL); } diff --git a/cmd-show-options.c b/cmd-show-options.c index 0e973ea0..252a33c6 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -102,7 +102,7 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) name = options_match(argument, &idx, &ambiguous); if (name == NULL) { if (args_has(args, 'q')) - goto fail; + goto out; if (ambiguous) cmdq_error(item, "ambiguous option: %s", argument); else @@ -113,7 +113,7 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) &cause); if (scope == OPTIONS_TABLE_NONE) { if (args_has(args, 'q')) - goto fail; + goto out; cmdq_error(item, "%s", cause); free(cause); goto fail; @@ -126,7 +126,14 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) parent = 0; if (o != NULL) cmd_show_options_print(self, item, o, idx, parent); + else if (*name == '@') { + if (args_has(args, 'q')) + goto out; + cmdq_error(item, "invalid option: %s", argument); + goto fail; + } +out: free(name); free(argument); return (CMD_RETURN_NORMAL); diff --git a/cmd-split-window.c b/cmd-split-window.c index 888d4106..637bad30 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -60,57 +60,51 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) struct client *tc = cmdq_get_target_client(item); struct session *s = target->s; struct winlink *wl = target->wl; + struct window *w = wl->window; struct window_pane *wp = target->wp, *new_wp; enum layout_type type; struct layout_cell *lc; struct cmd_find_state fs; - int size, percentage, flags, input; - const char *template, *errstr, *p; - char *cause, *cp, *copy; - size_t plen; + int size, flags, input; + const char *template; + char *cause = NULL, *cp; struct args_value *av; - u_int count = args_count(args); + u_int count = args_count(args), curval = 0; + type = LAYOUT_TOPBOTTOM; if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; - else - type = LAYOUT_TOPBOTTOM; - if ((p = args_get(args, 'l')) != NULL) { - plen = strlen(p); - if (p[plen - 1] == '%') { - copy = xstrdup(p); - copy[plen - 1] = '\0'; - percentage = strtonum(copy, 0, INT_MAX, &errstr); - free(copy); - if (errstr != NULL) { - cmdq_error(item, "percentage %s", errstr); - return (CMD_RETURN_ERROR); - } + + /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ + if (args_has(args, 'l') || args_has(args, 'p')) { + if (args_has(args, 'f')) { if (type == LAYOUT_TOPBOTTOM) - size = (wp->sy * percentage) / 100; + curval = w->sy; else - size = (wp->sx * percentage) / 100; + curval = w->sx; } else { - size = args_strtonum(args, 'l', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(item, "lines %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } + if (type == LAYOUT_TOPBOTTOM) + curval = wp->sy; + else + curval = wp->sx; } + } + + size = -1; + if (args_has(args, 'l')) { + size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, + item, &cause); } else if (args_has(args, 'p')) { - percentage = args_strtonum(args, 'p', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(item, "create pane failed: -p %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - if (type == LAYOUT_TOPBOTTOM) - size = (wp->sy * percentage) / 100; - else - size = (wp->sx * percentage) / 100; - } else - size = -1; + size = args_strtonum_and_expand(args, 'l', 0, 100, item, + &cause); + if (cause == NULL) + size = curval * size / 100; + } + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } window_push_zoom(wp->window, 1, args_has(args, 'Z')); input = (args_has(args, 'I') && count == 0); diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 7d477739..4191b894 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -135,6 +135,9 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) } server_redraw_window(src_w); server_redraw_window(dst_w); + notify_window("window-layout-changed", src_w); + if (src_w != dst_w) + notify_window("window-layout-changed", dst_w); out: if (window_pop_zoom(src_w)) @@ -95,6 +95,7 @@ extern const struct cmd_entry cmd_select_pane_entry; extern const struct cmd_entry cmd_select_window_entry; extern const struct cmd_entry cmd_send_keys_entry; extern const struct cmd_entry cmd_send_prefix_entry; +extern const struct cmd_entry cmd_server_access_entry; extern const struct cmd_entry cmd_set_buffer_entry; extern const struct cmd_entry cmd_set_environment_entry; extern const struct cmd_entry cmd_set_hook_entry; @@ -187,6 +188,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_select_window_entry, &cmd_send_keys_entry, &cmd_send_prefix_entry, + &cmd_server_access_entry, &cmd_set_buffer_entry, &cmd_set_environment_entry, &cmd_set_hook_entry, @@ -105,6 +105,21 @@ colour_split_rgb(int c, u_char *r, u_char *g, u_char *b) *b = c & 0xff; } +/* Force colour to RGB if not already. */ +int +colour_force_rgb(int c) +{ + if (c & COLOUR_FLAG_RGB) + return (c); + if (c & COLOUR_FLAG_256) + return (colour_256toRGB(c)); + if (c >= 0 && c <= 7) + return (colour_256toRGB(c)); + if (c >= 90 && c <= 97) + return (colour_256toRGB(8 + c - 90)); + return (-1); +} + /* Convert colour to a string. */ const char * colour_tostring(int c) @@ -113,7 +128,7 @@ colour_tostring(int c) u_char r, g, b; if (c == -1) - return ("invalid"); + return ("none"); if (c & COLOUR_FLAG_RGB) { colour_split_rgb(c, &r, &g, &b); @@ -334,6 +334,11 @@ char *strndup(const char *, size_t); void *memmem(const void *, size_t, const void *, size_t); #endif +#ifndef HAVE_GETPEEREID +/* getpeereid.c */ +int getpeereid(int, uid_t *, gid_t *); +#endif + #ifndef HAVE_DAEMON /* daemon.c */ int daemon(int, int); @@ -416,6 +421,11 @@ void *reallocarray(void *, size_t, size_t); void *recallocarray(void *, size_t, size_t, size_t); #endif +#ifdef HAVE_SYSTEMD +/* systemd.c */ +int systemd_create_socket(int, char **); +#endif + #ifdef HAVE_UTF8PROC /* utf8proc.c */ int utf8proc_wcwidth(wchar_t); diff --git a/compat/getpeereid.c b/compat/getpeereid.c new file mode 100644 index 00000000..c194e886 --- /dev/null +++ b/compat/getpeereid.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <stdio.h> + +#ifdef HAVE_UCRED_H +#include <ucred.h> +#endif + +#include "compat.h" + +int +getpeereid(int s, uid_t *uid, gid_t *gid) +{ +#ifdef HAVE_SO_PEERCRED + struct ucred uc; + int len = sizeof uc; + + if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1) + return (-1); + *uid = uc.uid; + *gid = uc.gid; + return (0); +#elif defined(HAVE_GETPEERUCRED) + ucred_t *ucred = NULL; + + if (getpeerucred(s, &ucred) == -1) + return (-1); + if ((*uid = ucred_geteuid(ucred)) == -1) + return (-1); + if ((*gid = ucred_getrgid(ucred)) == -1) + return (-1); + ucred_free(ucred); + return (0); +#else + return (getuid()); +#endif +} diff --git a/compat/systemd.c b/compat/systemd.c new file mode 100644 index 00000000..7317e43a --- /dev/null +++ b/compat/systemd.c @@ -0,0 +1,58 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/un.h> + +#include <systemd/sd-daemon.h> + +#include "tmux.h" + +int +systemd_create_socket(int flags, char **cause) +{ + int fds; + int fd; + struct sockaddr_un sa; + int addrlen = sizeof sa; + + fds = sd_listen_fds(0); + if (fds > 1) { /* too many file descriptors */ + errno = E2BIG; + goto fail; + } + + if (fds == 1) { /* socket-activated */ + fd = SD_LISTEN_FDS_START; + if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) { + errno = EPFNOSUPPORT; + goto fail; + } + if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1) + goto fail; + socket_path = xstrdup(sa.sun_path); + return (fd); + } + + return (server_create_socket(flags, cause)); + +fail: + if (cause != NULL) + xasprintf(cause, "systemd socket error (%s)", strerror(errno)); + return (-1); +} diff --git a/configure.ac b/configure.ac index 26bd1a98..276f71c8 100644 --- a/configure.ac +++ b/configure.ac @@ -69,6 +69,11 @@ AC_ARG_ENABLE( AS_HELP_STRING(--enable-static, create a static build) ) if test "x$enable_static" = xyes; then + case "$host_os" in + *darwin*) + AC_MSG_ERROR([static linking is not supported on macOS]) + ;; + esac test "x$PKG_CONFIG" != x && PKG_CONFIG="$PKG_CONFIG --static" AM_LDFLAGS="-static $AM_LDFLAGS" LDFLAGS="$AM_LDFLAGS $SAVED_LDFLAGS" @@ -123,6 +128,7 @@ AC_CHECK_HEADERS([ \ sys/dir.h \ sys/ndir.h \ sys/tree.h \ + ucred.h \ util.h \ ]) @@ -141,7 +147,8 @@ AC_CHECK_FUNCS([ \ flock \ prctl \ proc_pidinfo \ - sysconf + getpeerucred \ + sysconf \ ]) # Check for functions with a compatibility implementation. @@ -155,6 +162,7 @@ AC_REPLACE_FUNCS([ \ freezero \ getdtablecount \ getdtablesize \ + getpeereid \ getline \ getprogname \ memmem \ @@ -382,6 +390,31 @@ if test "x$enable_utf8proc" = xyes; then fi AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes]) +# Check for systemd support. +AC_ARG_ENABLE( + systemd, + AS_HELP_STRING(--enable-systemd, enable systemd integration) +) +if test x"$enable_systemd" = xyes; then + PKG_CHECK_MODULES( + SYSTEMD, + libsystemd, + [ + AM_CPPFLAGS="$SYSTEMD_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" + LIBS="$SYSTEMD_LIBS $LIBS" + found_systemd=yes + ], + found_systemd=no + ) + if test "x$found_systemd" = xyes; then + AC_DEFINE(HAVE_SYSTEMD) + else + AC_MSG_ERROR("systemd not found") + fi +fi +AM_CONDITIONAL(HAVE_SYSTEMD, [test "x$found_systemd" = xyes]) + # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) AC_LINK_IFELSE([AC_LANG_PROGRAM( @@ -679,6 +712,14 @@ AC_CHECK_DECL( [#include <sys/prctl.h>] ) +# Look for setsockopt(SO_PEERCRED). +AC_CHECK_DECL( + SO_PEERCRED, + AC_DEFINE(HAVE_SO_PEERCRED), + , + [#include <sys/socket.h>] +) + # Look for fcntl(F_CLOSEM). AC_CHECK_DECL( F_CLOSEM, @@ -775,7 +775,6 @@ control_start(struct client *c) cs->read_event = bufferevent_new(c->fd, control_read_callback, control_write_callback, control_error_callback, c); - bufferevent_enable(cs->read_event, EV_READ); if (c->flags & CLIENT_CONTROLCONTROL) cs->write_event = cs->read_event; @@ -792,6 +791,13 @@ control_start(struct client *c) } } +/* Control client ready. */ +void +control_ready(struct client *c) +{ + bufferevent_enable(c->control_state->read_event, EV_READ); +} + /* Discard all output for a client. */ void control_discard(struct client *c) diff --git a/format-draw.c b/format-draw.c index 6164cc44..1a7e60b3 100644 --- a/format-draw.c +++ b/format-draw.c @@ -677,7 +677,8 @@ format_draw_many(struct screen_write_ctx *ctx, struct style *sy, char ch, /* Draw a format to a screen. */ void format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, - u_int available, const char *expanded, struct style_ranges *srs) + u_int available, const char *expanded, struct style_ranges *srs, + int default_colours) { enum { LEFT, CENTRE, @@ -819,6 +820,10 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, log_debug("%s: style '%s' -> '%s'", __func__, tmp, style_tostring(&sy)); free(tmp); + if (default_colours) { + sy.gc.bg = base->bg; + sy.gc.fg = base->fg; + } /* If this style has a fill colour, store it for later. */ if (sy.fill != 8) @@ -1149,13 +1154,13 @@ format_trim_right(const char *expanded, u_int limit) while (*cp != '\0') { if (*cp == '#') { end = format_leading_hashes(cp, &n, &leading_width); + copy_width = leading_width; if (width <= skip) { - if (skip - width >= leading_width) + if (skip - width >= copy_width) copy_width = 0; else copy_width -= (skip - width); - } else - copy_width = leading_width; + } if (copy_width != 0) { if (n == 1) *out++ = '#'; @@ -24,6 +24,7 @@ #include <fnmatch.h> #include <libgen.h> #include <math.h> +#include <pwd.h> #include <regex.h> #include <stdarg.h> #include <stdlib.h> @@ -101,6 +102,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_WINDOW_NAME 0x4000 #define FORMAT_SESSION_NAME 0x8000 #define FORMAT_CHARACTER 0x10000 +#define FORMAT_COLOUR 0x20000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -390,7 +392,7 @@ format_job_get(struct format_expand_state *es, const char *cmd) if (force && fj->job != NULL) job_free(fj->job); if (force || (fj->job == NULL && fj->last != t)) { - fj->job = job_run(expanded, 0, NULL, NULL, + fj->job = job_run(expanded, 0, NULL, NULL, NULL, server_client_get_cwd(ft->client, NULL), format_job_update, format_job_complete, NULL, fj, JOB_NOWAIT, -1, -1); if (fj->job == NULL) { @@ -799,6 +801,20 @@ format_cb_start_command(struct format_tree *ft) return (cmd_stringify_argv(wp->argc, wp->argv)); } +/* Callback for pane_start_path. */ +static void * +format_cb_start_path(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp == NULL) + return (NULL); + + if (wp->cwd == NULL) + return (xstrdup("")); + return (xstrdup(wp->cwd)); +} + /* Callback for pane_current_command. */ static void * format_cb_current_command(struct format_tree *ft) @@ -1129,6 +1145,25 @@ format_cb_mouse_word(struct format_tree *ft) return (format_grid_word(gd, x, gd->hsize + y)); } +/* Callback for mouse_hyperlink. */ +static void * +format_cb_mouse_hyperlink(struct format_tree *ft) +{ + struct window_pane *wp; + struct grid *gd; + u_int x, y; + + if (!ft->m.valid) + return (NULL); + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp == NULL) + return (NULL); + if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0) + return (NULL); + gd = wp->base.grid; + return (format_grid_hyperlink(gd, x, gd->hsize + y, wp->screen)); +} + /* Callback for mouse_line. */ static void * format_cb_mouse_line(struct format_tree *ft) @@ -1386,6 +1421,35 @@ format_cb_client_tty(struct format_tree *ft) return (NULL); } +/* Callback for client_uid. */ +static void * +format_cb_client_uid(struct format_tree *ft) +{ + uid_t uid; + + if (ft->c != NULL) { + uid = proc_get_peer_uid(ft->c->peer); + if (uid != (uid_t)-1) + return (format_printf("%ld", (long)uid)); + } + return (NULL); +} + +/* Callback for client_user. */ +static void * +format_cb_client_user(struct format_tree *ft) +{ + uid_t uid; + struct passwd *pw; + + if (ft->c != NULL) { + uid = proc_get_peer_uid(ft->c->peer); + if (uid != (uid_t)-1 && (pw = getpwuid(uid)) != NULL) + return (xstrdup(pw->pw_name)); + } + return (NULL); +} + /* Callback for client_utf8. */ static void * format_cb_client_utf8(struct format_tree *ft) @@ -1649,6 +1713,13 @@ format_cb_mouse_y(struct format_tree *ft) return (NULL); } +/* Callback for next_session_id. */ +static void * +format_cb_next_session_id(__unused struct format_tree *ft) +{ + return (format_printf("$%u", next_session_id)); +} + /* Callback for origin_flag. */ static void * format_cb_origin_flag(struct format_tree *ft) @@ -1718,6 +1789,23 @@ format_cb_pane_dead(struct format_tree *ft) return (NULL); } +/* Callback for pane_dead_signal. */ +static void * +format_cb_pane_dead_signal(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + const char *name; + + if (wp != NULL) { + if ((wp->flags & PANE_STATUSREADY) && WIFSIGNALED(wp->status)) { + name = sig2name(WTERMSIG(wp->status)); + return (format_printf("%s", name)); + } + return (NULL); + } + return (NULL); +} + /* Callback for pane_dead_status. */ static void * format_cb_pane_dead_status(struct format_tree *ft) @@ -1732,6 +1820,20 @@ format_cb_pane_dead_status(struct format_tree *ft) return (NULL); } +/* Callback for pane_dead_time. */ +static void * +format_cb_pane_dead_time(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if (wp->flags & PANE_STATUSDRAWN) + return (&wp->dead_time); + return (NULL); + } + return (NULL); +} + /* Callback for pane_format. */ static void * format_cb_pane_format(struct format_tree *ft) @@ -2513,6 +2615,24 @@ format_cb_tree_mode_format(__unused struct format_tree *ft) return (xstrdup(window_tree_mode.default_format)); } +/* Callback for uid. */ +static void * +format_cb_uid(__unused struct format_tree *ft) +{ + return (format_printf("%ld", (long)getuid())); +} + +/* Callback for user. */ +static void * +format_cb_user(__unused struct format_tree *ft) +{ + struct passwd *pw; + + if ((pw = getpwuid(getuid())) != NULL) + return (xstrdup(pw->pw_name)); + return (NULL); +} + /* Format table type. */ enum format_table_type { FORMAT_TABLE_STRING, @@ -2619,6 +2739,12 @@ static const struct format_table_entry format_table[] = { { "client_tty", FORMAT_TABLE_STRING, format_cb_client_tty }, + { "client_uid", FORMAT_TABLE_STRING, + format_cb_client_uid + }, + { "client_user", FORMAT_TABLE_STRING, + format_cb_client_user + }, { "client_utf8", FORMAT_TABLE_STRING, format_cb_client_utf8 }, @@ -2682,6 +2808,9 @@ static const struct format_table_entry format_table[] = { { "mouse_button_flag", FORMAT_TABLE_STRING, format_cb_mouse_button_flag }, + { "mouse_hyperlink", FORMAT_TABLE_STRING, + format_cb_mouse_hyperlink + }, { "mouse_line", FORMAT_TABLE_STRING, format_cb_mouse_line }, @@ -2706,6 +2835,9 @@ static const struct format_table_entry format_table[] = { { "mouse_y", FORMAT_TABLE_STRING, format_cb_mouse_y }, + { "next_session_id", FORMAT_TABLE_STRING, + format_cb_next_session_id + }, { "origin_flag", FORMAT_TABLE_STRING, format_cb_origin_flag }, @@ -2739,9 +2871,15 @@ static const struct format_table_entry format_table[] = { { "pane_dead", FORMAT_TABLE_STRING, format_cb_pane_dead }, + { "pane_dead_signal", FORMAT_TABLE_STRING, + format_cb_pane_dead_signal + }, { "pane_dead_status", FORMAT_TABLE_STRING, format_cb_pane_dead_status }, + { "pane_dead_time", FORMAT_TABLE_TIME, + format_cb_pane_dead_time + }, { "pane_fg", FORMAT_TABLE_STRING, format_cb_pane_fg }, @@ -2796,6 +2934,9 @@ static const struct format_table_entry format_table[] = { { "pane_start_command", FORMAT_TABLE_STRING, format_cb_start_command }, + { "pane_start_path", FORMAT_TABLE_STRING, + format_cb_start_path + }, { "pane_synchronized", FORMAT_TABLE_STRING, format_cb_pane_synchronized }, @@ -2895,6 +3036,12 @@ static const struct format_table_entry format_table[] = { { "tree_mode_format", FORMAT_TABLE_STRING, format_cb_tree_mode_format }, + { "uid", FORMAT_TABLE_STRING, + format_cb_uid + }, + { "user", FORMAT_TABLE_STRING, + format_cb_user + }, { "version", FORMAT_TABLE_STRING, format_cb_version }, @@ -3262,12 +3409,12 @@ format_quote_style(const char *s) } /* Make a prettier time. */ -static char * -format_pretty_time(time_t t) +char * +format_pretty_time(time_t t, int seconds) { struct tm now_tm, tm; time_t now, age; - char s[6]; + char s[9]; time(&now); if (now < t) @@ -3279,7 +3426,10 @@ format_pretty_time(time_t t) /* Last 24 hours. */ if (age < 24 * 3600) { - strftime(s, sizeof s, "%H:%M", &tm); + if (seconds) + strftime(s, sizeof s, "%H:%M:%S", &tm); + else + strftime(s, sizeof s, "%H:%M", &tm); return (xstrdup(s)); } @@ -3384,7 +3534,7 @@ found: if (t == 0) return (NULL); if (modifiers & FORMAT_PRETTY) - found = format_pretty_time(t); + found = format_pretty_time(t, 0); else { if (time_format != NULL) { localtime_r(&t, &tm); @@ -3414,12 +3564,12 @@ found: } if (modifiers & FORMAT_QUOTE_SHELL) { saved = found; - found = xstrdup(format_quote_shell(saved)); + found = format_quote_shell(saved); free(saved); } if (modifiers & FORMAT_QUOTE_STYLE) { saved = found; - found = xstrdup(format_quote_style(saved)); + found = format_quote_style(saved); free(saved); } return (found); @@ -3555,7 +3705,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, /* * Modifiers are a ; separated list of the forms: - * l,m,C,a,b,d,n,t,w,q,E,T,S,W,P,<,> + * l,m,C,a,b,c,d,n,t,w,q,E,T,S,W,P,<,> * =a * =/a * =/a/ @@ -3572,7 +3722,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, cp++; /* Check single character modifiers with no arguments. */ - if (strchr("labdnwETSWP<>", cp[0]) != NULL && + if (strchr("labcdnwETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -4052,10 +4202,10 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, const char *errstr, *copy, *cp, *marker = NULL; const char *time_format = NULL; char *copy0, *condition, *found, *new; - char *value, *left, *right, c; + char *value, *left, *right; size_t valuelen; int modifiers = 0, limit = 0, width = 0; - int j; + int j, c; struct format_modifier *list, *cmp = NULL, *search = NULL; struct format_modifier **sub = NULL, *mexp = NULL, *fm; u_int i, count, nsub = 0; @@ -4126,6 +4276,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, case 'b': modifiers |= FORMAT_BASENAME; break; + case 'c': + modifiers |= FORMAT_COLOUR; + break; case 'd': modifiers |= FORMAT_DIRNAME; break; @@ -4201,6 +4354,18 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, goto done; } + /* Is this a colour? */ + if (modifiers & FORMAT_COLOUR) { + new = format_expand1(es, copy); + c = colour_fromstring(new); + if (c == -1 || (c = colour_force_rgb(c)) == -1) + value = xstrdup(""); + else + xasprintf(&value, "%06x", c & 0xffffff); + free(new); + goto done; + } + /* Is this a loop, comparison or condition? */ if (modifiers & FORMAT_SESSIONS) { value = format_loop_sessions(es, copy); @@ -4463,7 +4628,7 @@ format_expand1(struct format_expand_state *es, const char *fmt) { struct format_tree *ft = es->ft; char *buf, *out, *name; - const char *ptr, *s; + const char *ptr, *s, *style_end = NULL; size_t off, len, n, outlen; int ch, brackets; char expanded[8192]; @@ -4558,18 +4723,20 @@ format_expand1(struct format_expand_state *es, const char *fmt) break; fmt += n + 1; continue; + case '[': case '#': /* * If ##[ (with two or more #s), then it is a style and * can be left for format_draw to handle. */ - ptr = fmt; - n = 2; + ptr = fmt - (ch == '['); + n = 2 - (ch == '['); while (*ptr == '#') { ptr++; n++; } if (*ptr == '[') { + style_end = format_skip(fmt - 2, "]"); format_log(es, "found #*%zu[", n); while (len - off < n + 2) { buf = xreallocarray(buf, 2, len); @@ -4592,10 +4759,12 @@ format_expand1(struct format_expand_state *es, const char *fmt) continue; default: s = NULL; - if (ch >= 'A' && ch <= 'Z') - s = format_upper[ch - 'A']; - else if (ch >= 'a' && ch <= 'z') - s = format_lower[ch - 'a']; + if (fmt > style_end) { /* skip inside #[] */ + if (ch >= 'A' && ch <= 'Z') + s = format_upper[ch - 'A']; + else if (ch >= 'a' && ch <= 'z') + s = format_lower[ch - 'a']; + } if (s == NULL) { while (len - off < 3) { buf = xreallocarray(buf, 2, len); @@ -4917,3 +5086,20 @@ format_grid_line(struct grid *gd, u_int y) } return (s); } + +/* Return hyperlink at given coordinates. Caller frees. */ +char * +format_grid_hyperlink(struct grid *gd, u_int x, u_int y, struct screen* s) +{ + const char *uri; + struct grid_cell gc; + + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + return (NULL); + if (s->hyperlinks == NULL || gc.link == 0) + return (NULL); + if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL)) + return (NULL); + return (xstrdup(uri)); +} diff --git a/grid-view.c b/grid-view.c index f230d3c8..689ac4e4 100644 --- a/grid-view.c +++ b/grid-view.c @@ -231,5 +231,5 @@ grid_view_string_cells(struct grid *gd, u_int px, u_int py, u_int nx) px = grid_view_x(gd, px); py = grid_view_y(gd, py); - return (grid_string_cells(gd, px, py, nx, NULL, 0, 0, 0)); + return (grid_string_cells(gd, px, py, nx, NULL, 0, 0, 0, NULL)); } @@ -37,7 +37,7 @@ /* Default grid cell data. */ const struct grid_cell grid_default_cell = { - { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 + { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 }; /* @@ -45,12 +45,12 @@ const struct grid_cell grid_default_cell = { * appears in the grid - because of this, they are always extended cells. */ static const struct grid_cell grid_padding_cell = { - { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0 + { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0, 0 }; /* Cleared grid cell data. */ static const struct grid_cell grid_cleared_cell = { - { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0 + { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0, 0 }; static const struct grid_cell_entry grid_cleared_entry = { GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } } @@ -90,6 +90,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if (gc->us != 0) /* only supports 256 or RGB */ return (1); + if (gc->link != 0) + return (1); return (0); } @@ -131,6 +133,7 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, gee->fg = gc->fg; gee->bg = gc->bg; gee->us = gc->us; + gee->link = gc->link; return (gee); } @@ -231,6 +234,8 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) return (0); if (gc1->attr != gc2->attr || gc1->flags != gc2->flags) return (0); + if (gc1->link != gc2->link) + return (0); return (1); } @@ -399,6 +404,7 @@ grid_scroll_history(struct grid *gd, u_int bg) gd->hscrolled++; grid_compact_line(&gd->linedata[gd->hsize]); + gd->linedata[gd->hsize].time = current_time; gd->hsize++; } @@ -438,6 +444,7 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg) /* Move the line into the history. */ memcpy(gl_history, gl_upper, sizeof *gl_history); + gl_history->time = current_time; /* Then move the region up and clear the bottom line. */ memmove(gl_upper, gl_upper + 1, (lower - upper) * sizeof *gl_upper); @@ -507,6 +514,7 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->fg = gee->fg; gc->bg = gee->bg; gc->us = gee->us; + gc->link = gee->link; utf8_to_data(gee->data, &gc->data); } return; @@ -522,6 +530,7 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->bg |= COLOUR_FLAG_256; gc->us = 0; utf8_set(&gc->data, gce->data.data); + gc->link = 0; } /* Get cell for reading. */ @@ -826,18 +835,97 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values) return (n); } +/* Get underscore colour sequence. */ +static size_t +grid_string_cells_us(const struct grid_cell *gc, int *values) +{ + size_t n; + u_char r, g, b; + + n = 0; + if (gc->us & COLOUR_FLAG_256) { + values[n++] = 58; + values[n++] = 5; + values[n++] = gc->us & 0xff; + } else if (gc->us & COLOUR_FLAG_RGB) { + values[n++] = 58; + values[n++] = 2; + colour_split_rgb(gc->us, &r, &g, &b); + values[n++] = r; + values[n++] = g; + values[n++] = b; + } + return (n); +} + +/* Add on SGR code. */ +static void +grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc, + int *oldc, size_t nnewc, size_t noldc, int escape_c0) +{ + u_int i; + char tmp[64]; + + if (nnewc != 0 && + (nnewc != noldc || + memcmp(newc, oldc, nnewc * sizeof newc[0]) != 0 || + (n != 0 && s[0] == 0))) { + if (escape_c0) + strlcat(buf, "\\033[", len); + else + strlcat(buf, "\033[", len); + for (i = 0; i < nnewc; i++) { + if (i + 1 < nnewc) + xsnprintf(tmp, sizeof tmp, "%d;", newc[i]); + else + xsnprintf(tmp, sizeof tmp, "%d", newc[i]); + strlcat(buf, tmp, len); + } + strlcat(buf, "m", len); + } +} + +static int +grid_string_cells_add_hyperlink(char *buf, size_t len, const char *id, + const char *uri, int escape_c0) +{ + char *tmp; + + if (strlen(uri) + strlen(id) + 17 >= len) + return (0); + + if (escape_c0) + strlcat(buf, "\\033]8;", len); + else + strlcat(buf, "\033]8;", len); + if (*id != '\0') { + xasprintf(&tmp, "id=%s;", id); + strlcat(buf, tmp, len); + free(tmp); + } else + strlcat(buf, ";", len); + strlcat(buf, uri, len); + if (escape_c0) + strlcat(buf, "\\033\\\\", len); + else + strlcat(buf, "\033\\", len); + return (1); +} + /* * Returns ANSI code to set particular attributes (colour, bold and so on) * given a current state. */ static void grid_string_cells_code(const struct grid_cell *lastgc, - const struct grid_cell *gc, char *buf, size_t len, int escape_c0) + const struct grid_cell *gc, char *buf, size_t len, int escape_c0, + struct screen *sc, int *has_link) { - int oldc[64], newc[64], s[128]; - size_t noldc, nnewc, n, i; - u_int attr = gc->attr, lastattr = lastgc->attr; - char tmp[64]; + int oldc[64], newc[64], s[128]; + size_t noldc, nnewc, n, i; + u_int attr = gc->attr, lastattr = lastgc->attr; + char tmp[64]; + const char *uri, *id; struct { u_int mask; @@ -861,7 +949,9 @@ grid_string_cells_code(const struct grid_cell *lastgc, /* If any attribute is removed, begin with 0. */ for (i = 0; i < nitems(attrs); i++) { - if (!(attr & attrs[i].mask) && (lastattr & attrs[i].mask)) { + if (((~attr & attrs[i].mask) && + (lastattr & attrs[i].mask)) || + (lastgc->us != 0 && gc->us == 0)) { s[n++] = 0; lastattr &= GRID_ATTR_CHARSET; break; @@ -897,42 +987,20 @@ grid_string_cells_code(const struct grid_cell *lastgc, /* If the foreground colour changed, write its parameters. */ nnewc = grid_string_cells_fg(gc, newc); noldc = grid_string_cells_fg(lastgc, oldc); - if (nnewc != noldc || - memcmp(newc, oldc, nnewc * sizeof newc[0]) != 0 || - (n != 0 && s[0] == 0)) { - if (escape_c0) - strlcat(buf, "\\033[", len); - else - strlcat(buf, "\033[", len); - for (i = 0; i < nnewc; i++) { - if (i + 1 < nnewc) - xsnprintf(tmp, sizeof tmp, "%d;", newc[i]); - else - xsnprintf(tmp, sizeof tmp, "%d", newc[i]); - strlcat(buf, tmp, len); - } - strlcat(buf, "m", len); - } + grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, + escape_c0); /* If the background colour changed, append its parameters. */ nnewc = grid_string_cells_bg(gc, newc); noldc = grid_string_cells_bg(lastgc, oldc); - if (nnewc != noldc || - memcmp(newc, oldc, nnewc * sizeof newc[0]) != 0 || - (n != 0 && s[0] == 0)) { - if (escape_c0) - strlcat(buf, "\\033[", len); - else - strlcat(buf, "\033[", len); - for (i = 0; i < nnewc; i++) { - if (i + 1 < nnewc) - xsnprintf(tmp, sizeof tmp, "%d;", newc[i]); - else - xsnprintf(tmp, sizeof tmp, "%d", newc[i]); - strlcat(buf, tmp, len); - } - strlcat(buf, "m", len); - } + grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, + escape_c0); + + /* If the underscore colour changed, append its parameters. */ + nnewc = grid_string_cells_us(gc, newc); + noldc = grid_string_cells_us(lastgc, oldc); + grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, + escape_c0); /* Append shift in/shift out if needed. */ if ((attr & GRID_ATTR_CHARSET) && !(lastattr & GRID_ATTR_CHARSET)) { @@ -947,19 +1015,32 @@ grid_string_cells_code(const struct grid_cell *lastgc, else strlcat(buf, "\017", len); /* SI */ } + + /* Add hyperlink if changed. */ + if (sc != NULL && sc->hyperlinks != NULL && lastgc->link != gc->link) { + if (hyperlinks_get(sc->hyperlinks, gc->link, &uri, &id, NULL)) { + *has_link = grid_string_cells_add_hyperlink(buf, len, + id, uri, escape_c0); + } else if (*has_link) { + grid_string_cells_add_hyperlink(buf, len, "", "", + escape_c0); + *has_link = 0; + } + } } /* Convert cells into a string. */ char * grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, - struct grid_cell **lastgc, int with_codes, int escape_c0, int trim) + struct grid_cell **lastgc, int with_codes, int escape_c0, int trim, + struct screen *s) { struct grid_cell gc; static struct grid_cell lastgc1; const char *data; - char *buf, code[128]; + char *buf, code[8192]; size_t len, off, size, codelen; - u_int xx; + u_int xx, has_link = 0; const struct grid_line *gl; if (lastgc != NULL && *lastgc == NULL) { @@ -973,7 +1054,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, gl = grid_peek_line(gd, py); for (xx = px; xx < px + nx; xx++) { - if (gl == NULL || xx >= gl->cellsize) + if (gl == NULL || xx >= gl->cellused) break; grid_get_cell(gd, xx, py, &gc); if (gc.flags & GRID_FLAG_PADDING) @@ -981,7 +1062,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, if (with_codes) { grid_string_cells_code(*lastgc, &gc, code, sizeof code, - escape_c0); + escape_c0, s, &has_link); codelen = strlen(code); memcpy(*lastgc, &gc, sizeof **lastgc); } else @@ -1007,6 +1088,18 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, off += size; } + if (has_link) { + grid_string_cells_add_hyperlink(code, sizeof code, "", "", + escape_c0); + codelen = strlen(code); + while (len < off + size + codelen + 1) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, code, codelen); + off += codelen; + } + if (trim) { while (off > 0 && buf[off - 1] == ' ') off--; diff --git a/hyperlinks.c b/hyperlinks.c new file mode 100644 index 00000000..cde500c0 --- /dev/null +++ b/hyperlinks.c @@ -0,0 +1,227 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Will <author@will.party> + * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#include "tmux.h" + +/* + * OSC 8 hyperlinks, described at: + * + * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + * + * Each hyperlink and ID combination is assigned a number ("inner" in this + * file) which is stored in an extended grid cell and maps into a tree here. + * + * Each URI has one inner number and one external ID (which tmux uses to send + * the hyperlink to the terminal) and one internal ID (which is received from + * the sending application inside tmux). + * + * Anonymous hyperlinks are each unique and are not reused even if they have + * the same URI (terminals will not want to tie them together). + */ + +#define MAX_HYPERLINKS 5000 + +static uint64_t hyperlinks_next_external_id = 1; +static u_int global_hyperlinks_count; + +struct hyperlinks_uri { + struct hyperlinks *tree; + + u_int inner; + const char *internal_id; + const char *external_id; + const char *uri; + + TAILQ_ENTRY(hyperlinks_uri) list_entry; + RB_ENTRY(hyperlinks_uri) by_inner_entry; + RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */ +}; +RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri); +RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri); + +TAILQ_HEAD(hyperlinks_list, hyperlinks_uri); +static struct hyperlinks_list global_hyperlinks = + TAILQ_HEAD_INITIALIZER(global_hyperlinks); + +struct hyperlinks { + u_int next_inner; + struct hyperlinks_by_inner_tree by_inner; + struct hyperlinks_by_uri_tree by_uri; +}; + +static int +hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right) +{ + int r; + + if (*left->internal_id == '\0' || *right->internal_id == '\0') { + /* + * If both URIs are anonymous, use the inner for comparison so + * that they do not match even if the URI is the same - each + * anonymous URI should be unique. + */ + if (*left->internal_id != '\0') + return (-1); + if (*right->internal_id != '\0') + return (1); + return (left->inner - right->inner); + } + + r = strcmp(left->internal_id, right->internal_id); + if (r != 0) + return (r); + return (strcmp(left->uri, right->uri)); +} +RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, + hyperlinks_by_uri_cmp); +RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, + hyperlinks_by_uri_cmp); + +static int +hyperlinks_by_inner_cmp(struct hyperlinks_uri *left, + struct hyperlinks_uri *right) +{ + return (left->inner - right->inner); +} +RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, + hyperlinks_by_inner_cmp); +RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, + hyperlinks_by_inner_cmp); + +/* Remove a hyperlink. */ +static void +hyperlinks_remove(struct hyperlinks_uri *hlu) +{ + struct hyperlinks *hl = hlu->tree; + + TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry); + global_hyperlinks_count--; + + RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu); + RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu); + + free((void *)hlu->internal_id); + free((void *)hlu->external_id); + free((void *)hlu->uri); + free(hlu); +} + +/* Store a new hyperlink or return if it already exists. */ +u_int +hyperlinks_put(struct hyperlinks *hl, const char *uri_in, + const char *internal_id_in) +{ + struct hyperlinks_uri find, *hlu; + char *uri, *internal_id, *external_id; + + /* + * Anonymous URI are stored with an empty internal ID and the tree + * comparator will make sure they never match each other (so each + * anonymous URI is unique). + */ + if (internal_id_in == NULL) + internal_id_in = ""; + + utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); + utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); + + if (*internal_id_in != '\0') { + find.uri = uri; + find.internal_id = internal_id; + + hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find); + if (hlu != NULL) { + free (uri); + free (internal_id); + return (hlu->inner); + } + } + xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++); + + hlu = xcalloc(1, sizeof *hlu); + hlu->inner = hl->next_inner++; + hlu->internal_id = internal_id; + hlu->external_id = external_id; + hlu->uri = uri; + hlu->tree = hl; + RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu); + RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu); + + TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry); + if (++global_hyperlinks_count == MAX_HYPERLINKS) + hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks)); + + return (hlu->inner); +} + +/* Get hyperlink by inner number. */ +int +hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, + const char **internal_id_out, const char **external_id_out) +{ + struct hyperlinks_uri find, *hlu; + + find.inner = inner; + + hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find); + if (hlu == NULL) + return (0); + if (internal_id_out != NULL) + *internal_id_out = hlu->internal_id; + if (external_id_out != NULL) + *external_id_out = hlu->external_id; + *uri_out = hlu->uri; + return (1); +} + +/* Initialize hyperlink set. */ +struct hyperlinks * +hyperlinks_init(void) +{ + struct hyperlinks *hl; + + hl = xcalloc(1, sizeof *hl); + hl->next_inner = 1; + RB_INIT(&hl->by_uri); + RB_INIT(&hl->by_inner); + return (hl); +} + +/* Free all hyperlinks but not the set itself. */ +void +hyperlinks_reset(struct hyperlinks *hl) +{ + struct hyperlinks_uri *hlu, *hlu1; + + RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1) + hyperlinks_remove(hlu); +} + +/* Free hyperlink set. */ +void +hyperlinks_free(struct hyperlinks *hl) +{ + hyperlinks_reset(hl); + free(hl); +} diff --git a/input-keys.c b/input-keys.c index ebdf0384..bb4edf79 100644 --- a/input-keys.c +++ b/input-keys.c @@ -417,7 +417,7 @@ int input_key(struct screen *s, struct bufferevent *bev, key_code key) { struct input_key_entry *ike; - key_code justkey, newkey, outkey; + key_code justkey, newkey, outkey, modifiers; struct utf8_data ud; char tmp[64], modifier; @@ -518,7 +518,12 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) return (input_key(s, bev, key & ~KEYC_CTRL)); } outkey = (key & KEYC_MASK_KEY); - switch (key & KEYC_MASK_MODIFIERS) { + modifiers = (key & KEYC_MASK_MODIFIERS); + if (outkey < 32 && outkey != 9 && outkey != 13 && outkey != 27) { + outkey = 64 + outkey; + modifiers |= KEYC_CTRL; + } + switch (modifiers) { case KEYC_SHIFT: modifier = '2'; break; @@ -577,13 +582,13 @@ input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y, */ if (m->sgr_type != ' ') { if (MOUSE_DRAG(m->sgr_b) && - MOUSE_BUTTONS(m->sgr_b) == 3 && + MOUSE_RELEASE(m->sgr_b) && (~s->mode & MODE_MOUSE_ALL)) return (0); } else { if (MOUSE_DRAG(m->b) && - MOUSE_BUTTONS(m->b) == 3 && - MOUSE_BUTTONS(m->lb) == 3 && + MOUSE_RELEASE(m->b) && + MOUSE_RELEASE(m->lb) && (~s->mode & MODE_MOUSE_ALL)) return (0); } @@ -601,19 +606,34 @@ input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y, len = xsnprintf(buf, sizeof buf, "\033[<%u;%u;%u%c", m->sgr_b, x + 1, y + 1, m->sgr_type); } else if (s->mode & MODE_MOUSE_UTF8) { - if (m->b > 0x7ff - 32 || x > 0x7ff - 33 || y > 0x7ff - 33) + if (m->b > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_BTN_OFF || + x > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF || + y > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF) return (0); len = xsnprintf(buf, sizeof buf, "\033[M"); - len += input_key_split2(m->b + 32, &buf[len]); - len += input_key_split2(x + 33, &buf[len]); - len += input_key_split2(y + 33, &buf[len]); + len += input_key_split2(m->b + MOUSE_PARAM_BTN_OFF, &buf[len]); + len += input_key_split2(x + MOUSE_PARAM_POS_OFF, &buf[len]); + len += input_key_split2(y + MOUSE_PARAM_POS_OFF, &buf[len]); } else { - if (m->b > 223) + if (m->b + MOUSE_PARAM_BTN_OFF > MOUSE_PARAM_MAX) return (0); + len = xsnprintf(buf, sizeof buf, "\033[M"); - buf[len++] = m->b + 32; - buf[len++] = x + 33; - buf[len++] = y + 33; + buf[len++] = m->b + MOUSE_PARAM_BTN_OFF; + + /* + * The incoming x and y may be out of the range which can be + * supported by the "normal" mouse protocol. Clamp the + * coordinates to the supported range. + */ + if (x + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX) + buf[len++] = MOUSE_PARAM_MAX; + else + buf[len++] = x + MOUSE_PARAM_POS_OFF; + if (y + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX) + buf[len++] = MOUSE_PARAM_MAX; + else + buf[len++] = y + MOUSE_PARAM_POS_OFF; } *rbuf = buf; @@ -135,12 +135,15 @@ static void input_set_state(struct input_ctx *, static void input_reset_cell(struct input_ctx *); static void input_osc_4(struct input_ctx *, const char *); +static void input_osc_8(struct input_ctx *, const char *); static void input_osc_10(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *); +static void input_osc_12(struct input_ctx *, const char *); static void input_osc_52(struct input_ctx *, const char *); static void input_osc_104(struct input_ctx *, const char *); static void input_osc_110(struct input_ctx *, const char *); static void input_osc_111(struct input_ctx *, const char *); +static void input_osc_112(struct input_ctx *, const char *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -1076,6 +1079,9 @@ input_reply(struct input_ctx *ictx, const char *fmt, ...) va_list ap; char *reply; + if (bev == NULL) + return; + va_start(ap, fmt); xvasprintf(&reply, fmt, ap); va_end(ap); @@ -1617,7 +1623,7 @@ input_csi_dispatch(struct input_ctx *ictx) case INPUT_CSI_DECSCUSR: n = input_get(ictx, 0, 0, 0); if (n != -1) - screen_set_cursor_style(s, n); + screen_set_cursor_style(n, &s->cstyle, &s->mode); break; case INPUT_CSI_XDA: n = input_get(ictx, 0, 0, 0); @@ -1683,6 +1689,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) break; case 12: screen_write_mode_clear(sctx, MODE_CURSOR_BLINKING); + screen_write_mode_set(sctx, MODE_CURSOR_BLINKING_SET); break; case 25: /* TCEM */ screen_write_mode_clear(sctx, MODE_CURSOR); @@ -1772,6 +1779,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) break; case 12: screen_write_mode_set(sctx, MODE_CURSOR_BLINKING); + screen_write_mode_set(sctx, MODE_CURSOR_BLINKING_SET); break; case 25: /* TCEM */ screen_write_mode_set(sctx, MODE_CURSOR); @@ -1794,6 +1802,8 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) screen_write_mode_set(sctx, MODE_FOCUSON); if (wp == NULL) break; + if (!options_get_number(global_options, "focus-events")) + break; if (wp->flags & PANE_FOCUSED) bufferevent_write(wp->event, "\033[I", 3); else @@ -2231,15 +2241,19 @@ input_enter_dcs(struct input_ctx *ictx) static int input_dcs_dispatch(struct input_ctx *ictx) { + struct window_pane *wp = ictx->wp; struct screen_write_ctx *sctx = &ictx->ctx; u_char *buf = ictx->input_buf; size_t len = ictx->input_len; const char prefix[] = "tmux;"; const u_int prefixlen = (sizeof prefix) - 1; + if (wp == NULL) + return (0); if (ictx->flags & INPUT_DISCARD) return (0); - + if (!options_get_number(ictx->wp->options, "allow-passthrough")) + return (0); log_debug("%s: \"%s\"", __func__, buf); if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) @@ -2279,6 +2293,8 @@ input_exit_osc(struct input_ctx *ictx) option = 0; while (*p >= '0' && *p <= '9') option = option * 10 + *p++ - '0'; + if (*p != ';' && *p != '\0') + return; if (*p == ';') p++; @@ -2303,6 +2319,9 @@ input_exit_osc(struct input_ctx *ictx) } } break; + case 8: + input_osc_8(ictx, p); + break; case 10: input_osc_10(ictx, p); break; @@ -2310,8 +2329,7 @@ input_exit_osc(struct input_ctx *ictx) input_osc_11(ictx, p); break; case 12: - if (utf8_isvalid(p) && *p != '?') /* ? is colour request */ - screen_set_cursor_colour(sctx->s, p); + input_osc_12(ictx, p); break; case 52: input_osc_52(ictx, p); @@ -2326,8 +2344,7 @@ input_exit_osc(struct input_ctx *ictx) input_osc_111(ictx, p); break; case 112: - if (*p == '\0') /* no arguments allowed */ - screen_set_cursor_colour(sctx->s, ""); + input_osc_112(ictx, p); break; default: log_debug("%s: unknown '%u'", __func__, option); @@ -2380,6 +2397,7 @@ static void input_exit_rename(struct input_ctx *ictx) { struct window_pane *wp = ictx->wp; + struct window *w; struct options_entry *o; if (wp == NULL) @@ -2392,17 +2410,20 @@ input_exit_rename(struct input_ctx *ictx) if (!utf8_isvalid(ictx->input_buf)) return; + w = wp->window; if (ictx->input_len == 0) { - o = options_get_only(wp->window->options, "automatic-rename"); + o = options_get_only(w->options, "automatic-rename"); if (o != NULL) options_remove_or_default(o, -1, NULL); - return; + if (!options_get_number(w->options, "automatic-rename")) + window_set_name(w, ""); + } else { + options_set_number(w->options, "automatic-rename", 0); + window_set_name(w, ictx->input_buf); } - window_set_name(wp->window, ictx->input_buf); - options_set_number(wp->window->options, "automatic-rename", 0); - server_redraw_window_borders(wp->window); - server_status_window(wp->window); + server_redraw_window_borders(w); + server_status_window(w); } /* Open UTF-8 character. */ @@ -2489,7 +2510,9 @@ input_osc_colour_reply(struct input_ctx *ictx, u_int n, int c) u_char r, g, b; const char *end; - if (c == 8 || (~c & COLOUR_FLAG_RGB)) + if (c != -1) + c = colour_force_rgb(c); + if (c == -1) return; colour_split_rgb(c, &r, &g, &b); @@ -2497,7 +2520,8 @@ input_osc_colour_reply(struct input_ctx *ictx, u_int n, int c) end = "\007"; else end = "\033\\"; - input_reply(ictx, "\033]%u;rgb:%02hhx/%02hhx/%02hhx%s", n, r, g, b, end); + input_reply(ictx, "\033]%u;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s", + n, r, r, g, g, b, b, end); } /* Handle the OSC 4 sequence for setting (multiple) palette entries. */ @@ -2521,6 +2545,12 @@ input_osc_4(struct input_ctx *ictx, const char *p) } s = strsep(&next, ";"); + if (strcmp(s, "?") == 0) { + c = colour_palette_get(ictx->palette, idx); + if (c != -1) + input_osc_colour_reply(ictx, 4, c); + continue; + } if ((c = input_osc_parse_colour(s)) == -1) { s = next; continue; @@ -2536,6 +2566,47 @@ input_osc_4(struct input_ctx *ictx, const char *p) free(copy); } +/* Handle the OSC 8 sequence for embedding hyperlinks. */ +static void +input_osc_8(struct input_ctx *ictx, const char *p) +{ + struct hyperlinks *hl = ictx->ctx.s->hyperlinks; + struct grid_cell *gc = &ictx->cell.cell; + const char *start, *end, *uri; + char *id = NULL; + + for (start = p; (end = strpbrk(start, ":;")) != NULL; start = end + 1) { + if (end - start >= 4 && strncmp(start, "id=", 3) == 0) { + if (id != NULL) + goto bad; + id = xstrndup(start + 3, end - start - 3); + } + + /* The first ; is the end of parameters and start of the URI. */ + if (*end == ';') + break; + } + if (end == NULL || *end != ';') + goto bad; + uri = end + 1; + if (*uri == '\0') { + gc->link = 0; + free(id); + return; + } + gc->link = hyperlinks_put(hl, uri, id); + if (id == NULL) + log_debug("hyperlink (anonymous) %s = %u", uri, gc->link); + else + log_debug("hyperlink (id=%s) %s = %u", id, uri, gc->link); + free(id); + return; + +bad: + log_debug("bad OSC 8 %s", p); + free(id); +} + /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void input_osc_10(struct input_ctx *ictx, const char *p) @@ -2564,7 +2635,7 @@ input_osc_10(struct input_ctx *ictx, const char *p) } } -/* Handle the OSC 110 sequence for resetting background colour. */ +/* Handle the OSC 110 sequence for resetting foreground colour. */ static void input_osc_110(struct input_ctx *ictx, const char *p) { @@ -2624,18 +2695,54 @@ input_osc_111(struct input_ctx *ictx, const char *p) } } +/* Handle the OSC 12 sequence for setting and querying cursor colour. */ +static void +input_osc_12(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + int c; + + if (strcmp(p, "?") == 0) { + if (wp != NULL) { + c = ictx->ctx.s->ccolour; + if (c == -1) + c = ictx->ctx.s->default_ccolour; + input_osc_colour_reply(ictx, 12, c); + } + return; + } + + if ((c = input_osc_parse_colour(p)) == -1) { + log_debug("bad OSC 12: %s", p); + return; + } + screen_set_cursor_colour(ictx->ctx.s, c); +} + +/* Handle the OSC 112 sequence for resetting cursor colour. */ +static void +input_osc_112(struct input_ctx *ictx, const char *p) +{ + if (*p == '\0') /* no arguments allowed */ + screen_set_cursor_colour(ictx->ctx.s, -1); +} + + /* Handle the OSC 52 sequence for setting the clipboard. */ static void input_osc_52(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; char *end; - const char *buf; - size_t len; + const char *buf = NULL; + size_t len = 0; u_char *out; int outlen, state; struct screen_write_ctx ctx; struct paste_buffer *pb; + const char* allow = "cpqs01234567"; + char flags[sizeof "cpqs01234567"] = ""; + u_int i, j = 0; if (wp == NULL) return; @@ -2650,27 +2757,19 @@ input_osc_52(struct input_ctx *ictx, const char *p) return; log_debug("%s: %s", __func__, end); + for (i = 0; p + i != end; i++) { + if (strchr(allow, p[i]) != NULL && strchr(flags, p[i]) == NULL) + flags[j++] = p[i]; + } + log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, flags); + if (strcmp(end, "?") == 0) { - if ((pb = paste_get_top(NULL)) != NULL) { + if ((pb = paste_get_top(NULL)) != NULL) buf = paste_buffer_data(pb, &len); - outlen = 4 * ((len + 2) / 3) + 1; - out = xmalloc(outlen); - if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) { - free(out); - return; - } - } else { - outlen = 0; - out = NULL; - } - bufferevent_write(ictx->event, "\033]52;;", 6); - if (outlen != 0) - bufferevent_write(ictx->event, out, outlen); if (ictx->input_end == INPUT_END_BEL) - bufferevent_write(ictx->event, "\007", 1); + input_reply_clipboard(ictx->event, buf, len, "\007"); else - bufferevent_write(ictx->event, "\033\\", 2); - free(out); + input_reply_clipboard(ictx->event, buf, len, "\033\\"); return; } @@ -2685,7 +2784,7 @@ input_osc_52(struct input_ctx *ictx, const char *p) } screen_write_start_pane(&ctx, wp, NULL); - screen_write_setselection(&ctx, out, outlen); + screen_write_setselection(&ctx, flags, out, outlen); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); @@ -2728,3 +2827,26 @@ input_osc_104(struct input_ctx *ictx, const char *p) screen_write_fullredraw(&ictx->ctx); free(copy); } + +void +input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len, + const char *end) +{ + char *out = NULL; + size_t outlen = 0; + + if (buf != NULL && len != 0) { + outlen = 4 * ((len + 2) / 3) + 1; + out = xmalloc(outlen); + if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) { + free(out); + return; + } + } + + bufferevent_write(bev, "\033]52;;", 6); + if (outlen != 0) + bufferevent_write(bev, out, outlen); + bufferevent_write(bev, end, strlen(end)); + free(out); +} @@ -69,7 +69,7 @@ static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); /* Start a job running. */ struct job * -job_run(const char *cmd, int argc, char **argv, struct session *s, +job_run(const char *cmd, int argc, char **argv, struct environ *e, struct session *s, const char *cwd, job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb, void *data, int flags, int sx, int sy) { @@ -87,6 +87,8 @@ job_run(const char *cmd, int argc, char **argv, struct session *s, * if-shell to decide on default-terminal based on outside TERM. */ env = environ_for_session(s, !cfg_finished); + if (e != NULL) + environ_copy(e, env); sigfillset(&set); sigprocmask(SIG_BLOCK, &set, &oldset); @@ -137,7 +139,7 @@ job_run(const char *cmd, int argc, char **argv, struct session *s, close(out[1]); close(out[0]); - nullfd = open(_PATH_DEVNULL, O_RDWR, 0); + nullfd = open(_PATH_DEVNULL, O_RDWR); if (nullfd == -1) fatal("open failed"); if (dup2(nullfd, STDERR_FILENO) == -1) diff --git a/key-bindings.c b/key-bindings.c index 9f7e734a..bcc6004d 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -54,6 +54,9 @@ " '#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}' 'c' {copy-mode -q; set-buffer -- \"#{q:mouse_word}\"}" \ " '#{?mouse_line,Copy Line,}' 'l' {copy-mode -q; set-buffer -- \"#{q:mouse_line}\"}" \ " ''" \ + " '#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}' 'C-h' {copy-mode -q; send-keys -l -- \"#{q:mouse_hyperlink}\"}" \ + " '#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}' 'h' {copy-mode -q; set-buffer -- \"#{q:mouse_hyperlink}\"}" \ + " ''" \ " 'Horizontal Split' 'h' {split-window -h}" \ " 'Vertical Split' 'v' {split-window -v}" \ " ''" \ @@ -492,6 +495,7 @@ key_bindings_init(void) "bind -Tcopy-mode \\; { send -X jump-again }", "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }", "bind -Tcopy-mode N { send -X search-reverse }", + "bind -Tcopy-mode P { send -X toggle-position }", "bind -Tcopy-mode R { send -X rectangle-toggle }", "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }", "bind -Tcopy-mode X { send -X set-mark }", @@ -588,6 +592,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi L { send -X bottom-line }", "bind -Tcopy-mode-vi M { send -X middle-line }", "bind -Tcopy-mode-vi N { send -X search-reverse }", + "bind -Tcopy-mode-vi P { send -X toggle-position }", "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }", "bind -Tcopy-mode-vi V { send -X select-line }", "bind -Tcopy-mode-vi W { send -X next-space }", diff --git a/key-string.c b/key-string.c index 4f7be858..0ca91306 100644 --- a/key-string.c +++ b/key-string.c @@ -91,26 +91,68 @@ static const struct { KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1), KEYC_MOUSE_STRING(MOUSEDOWN2, MouseDown2), KEYC_MOUSE_STRING(MOUSEDOWN3, MouseDown3), + KEYC_MOUSE_STRING(MOUSEDOWN6, MouseDown6), + KEYC_MOUSE_STRING(MOUSEDOWN7, MouseDown7), + KEYC_MOUSE_STRING(MOUSEDOWN8, MouseDown8), + KEYC_MOUSE_STRING(MOUSEDOWN9, MouseDown9), + KEYC_MOUSE_STRING(MOUSEDOWN10, MouseDown10), + KEYC_MOUSE_STRING(MOUSEDOWN11, MouseDown11), KEYC_MOUSE_STRING(MOUSEUP1, MouseUp1), KEYC_MOUSE_STRING(MOUSEUP2, MouseUp2), KEYC_MOUSE_STRING(MOUSEUP3, MouseUp3), + KEYC_MOUSE_STRING(MOUSEUP6, MouseUp6), + KEYC_MOUSE_STRING(MOUSEUP7, MouseUp7), + KEYC_MOUSE_STRING(MOUSEUP8, MouseUp8), + KEYC_MOUSE_STRING(MOUSEUP9, MouseUp9), + KEYC_MOUSE_STRING(MOUSEUP10, MouseUp10), + KEYC_MOUSE_STRING(MOUSEUP11, MouseUp11), KEYC_MOUSE_STRING(MOUSEDRAG1, MouseDrag1), KEYC_MOUSE_STRING(MOUSEDRAG2, MouseDrag2), KEYC_MOUSE_STRING(MOUSEDRAG3, MouseDrag3), + KEYC_MOUSE_STRING(MOUSEDRAG6, MouseDrag6), + KEYC_MOUSE_STRING(MOUSEDRAG7, MouseDrag7), + KEYC_MOUSE_STRING(MOUSEDRAG8, MouseDrag8), + KEYC_MOUSE_STRING(MOUSEDRAG9, MouseDrag9), + KEYC_MOUSE_STRING(MOUSEDRAG10, MouseDrag10), + KEYC_MOUSE_STRING(MOUSEDRAG11, MouseDrag11), KEYC_MOUSE_STRING(MOUSEDRAGEND1, MouseDragEnd1), KEYC_MOUSE_STRING(MOUSEDRAGEND2, MouseDragEnd2), KEYC_MOUSE_STRING(MOUSEDRAGEND3, MouseDragEnd3), + KEYC_MOUSE_STRING(MOUSEDRAGEND6, MouseDragEnd6), + KEYC_MOUSE_STRING(MOUSEDRAGEND7, MouseDragEnd7), + KEYC_MOUSE_STRING(MOUSEDRAGEND8, MouseDragEnd8), + KEYC_MOUSE_STRING(MOUSEDRAGEND9, MouseDragEnd9), + KEYC_MOUSE_STRING(MOUSEDRAGEND10, MouseDragEnd10), + KEYC_MOUSE_STRING(MOUSEDRAGEND11, MouseDragEnd11), KEYC_MOUSE_STRING(WHEELUP, WheelUp), KEYC_MOUSE_STRING(WHEELDOWN, WheelDown), KEYC_MOUSE_STRING(SECONDCLICK1, SecondClick1), KEYC_MOUSE_STRING(SECONDCLICK2, SecondClick2), KEYC_MOUSE_STRING(SECONDCLICK3, SecondClick3), + KEYC_MOUSE_STRING(SECONDCLICK6, SecondClick6), + KEYC_MOUSE_STRING(SECONDCLICK7, SecondClick7), + KEYC_MOUSE_STRING(SECONDCLICK8, SecondClick8), + KEYC_MOUSE_STRING(SECONDCLICK9, SecondClick9), + KEYC_MOUSE_STRING(SECONDCLICK10, SecondClick10), + KEYC_MOUSE_STRING(SECONDCLICK11, SecondClick11), KEYC_MOUSE_STRING(DOUBLECLICK1, DoubleClick1), KEYC_MOUSE_STRING(DOUBLECLICK2, DoubleClick2), KEYC_MOUSE_STRING(DOUBLECLICK3, DoubleClick3), + KEYC_MOUSE_STRING(DOUBLECLICK6, DoubleClick6), + KEYC_MOUSE_STRING(DOUBLECLICK7, DoubleClick7), + KEYC_MOUSE_STRING(DOUBLECLICK8, DoubleClick8), + KEYC_MOUSE_STRING(DOUBLECLICK9, DoubleClick9), + KEYC_MOUSE_STRING(DOUBLECLICK10, DoubleClick10), + KEYC_MOUSE_STRING(DOUBLECLICK11, DoubleClick11), KEYC_MOUSE_STRING(TRIPLECLICK1, TripleClick1), KEYC_MOUSE_STRING(TRIPLECLICK2, TripleClick2), KEYC_MOUSE_STRING(TRIPLECLICK3, TripleClick3), + KEYC_MOUSE_STRING(TRIPLECLICK6, TripleClick6), + KEYC_MOUSE_STRING(TRIPLECLICK7, TripleClick7), + KEYC_MOUSE_STRING(TRIPLECLICK8, TripleClick8), + KEYC_MOUSE_STRING(TRIPLECLICK9, TripleClick9), + KEYC_MOUSE_STRING(TRIPLECLICK10, TripleClick10), + KEYC_MOUSE_STRING(TRIPLECLICK11, TripleClick11) }; /* Find key string in table. */ diff --git a/layout-custom.c b/layout-custom.c index e7fb4253..932b30e7 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -154,7 +154,7 @@ layout_check(struct layout_cell *lc) /* Parse a layout string and arrange window as layout. */ int -layout_parse(struct window *w, const char *layout) +layout_parse(struct window *w, const char *layout, char **cause) { struct layout_cell *lc, *lcchild; struct window_pane *wp; @@ -165,22 +165,31 @@ layout_parse(struct window *w, const char *layout) if (sscanf(layout, "%hx,", &csum) != 1) return (-1); layout += 5; - if (csum != layout_checksum(layout)) + if (csum != layout_checksum(layout)) { + *cause = xstrdup("invalid layout"); return (-1); + } /* Build the layout. */ lc = layout_construct(NULL, &layout); - if (lc == NULL) + if (lc == NULL) { + *cause = xstrdup("invalid layout"); return (-1); - if (*layout != '\0') + } + if (*layout != '\0') { + *cause = xstrdup("invalid layout"); goto fail; + } /* Check this window will fit into the layout. */ for (;;) { npanes = window_count_panes(w); ncells = layout_count_cells(lc); - if (npanes > ncells) + if (npanes > ncells) { + xasprintf(cause, "have %u panes but need %u", npanes, + ncells); goto fail; + } if (npanes == ncells) break; @@ -217,8 +226,10 @@ layout_parse(struct window *w, const char *layout) } /* Check the new layout. */ - if (!layout_check(lc)) + if (!layout_check(lc)) { + *cause = xstrdup("size mismatch after applying layout"); return (-1); + } /* Resize to the layout size. */ window_resize(w, lc->sx, lc->sy, -1, -1); @@ -143,7 +143,7 @@ fatal(const char *msg, ...) va_list ap; if (snprintf(tmp, sizeof tmp, "fatal: %s: ", strerror(errno)) < 0) - exit (1); + exit(1); va_start(ap, msg); log_vwrite(msg, ap, tmp); @@ -55,10 +55,11 @@ menu_add_item(struct menu *menu, const struct menu_item *item, struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs) { struct menu_item *new_item; - const char *key, *cmd; - char *s, *name; - u_int width; + const char *key = NULL, *cmd, *suffix = ""; + char *s, *trimmed, *name; + u_int width, max_width; int line; + size_t keylen, slen; line = (item == NULL || item->name == NULL || *item->name == '\0'); if (line && menu->count == 0) @@ -80,11 +81,36 @@ menu_add_item(struct menu *menu, const struct menu_item *item, menu->count--; return; } + max_width = c->tty.sx - 4; + + slen = strlen(s); if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) { key = key_string_lookup_key(item->key, 0); - xasprintf(&name, "%s#[default] #[align=right](%s)", s, key); + keylen = strlen(key) + 3; /* 3 = space and two brackets */ + + /* + * Add the key if it is shorter than a quarter of the available + * space or there is space for the entire item text and the + * key. + */ + if (keylen <= max_width / 4) + max_width -= keylen; + else if (keylen >= max_width || slen >= max_width - keylen) + key = NULL; + } + + if (slen > max_width) { + max_width--; + suffix = ">"; + } + trimmed = format_trim_right(s, max_width); + if (key != NULL) { + xasprintf(&name, "%s%s#[default] #[align=right](%s)", + trimmed, suffix, key); } else - xasprintf(&name, "%s", s); + xasprintf(&name, "%s%s", trimmed, suffix); + free(trimmed); + new_item->name = name; free(s); @@ -100,6 +126,8 @@ menu_add_item(struct menu *menu, const struct menu_item *item, new_item->key = item->key; width = format_width(new_item->name); + if (*new_item->name == '-') + width--; if (width > menu->width) menu->width = width; } @@ -132,25 +160,29 @@ menu_free(struct menu *menu) } struct screen * -menu_mode_cb(__unused struct client *c, void *data, __unused u_int *cx, - __unused u_int *cy) +menu_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) { struct menu_data *md = data; + *cx = md->px + 2; + if (md->choice == -1) + *cy = md->py; + else + *cy = md->py + 1 + md->choice; + return (&md->s); } -int -menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py) +/* Return parts of the input range which are not obstructed by the menu. */ +void +menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py, + u_int nx, struct overlay_ranges *r) { struct menu_data *md = data; struct menu *menu = md->menu; - if (px < md->px || px > md->px + menu->width + 3) - return (1); - if (py < md->py || py > md->py + menu->count + 1) - return (1); - return (0); + server_client_overlay_range(md->px, md->py, menu->width + 4, + menu->count + 2, px, py, nx, r); } void @@ -210,7 +242,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) if (KEYC_IS_MOUSE(event->key)) { if (md->flags & MENU_NOMOUSE) { - if (MOUSE_BUTTONS(m->b) != 0) + if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1) return (1); return (0); } @@ -223,7 +255,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) return (1); } else { if (!MOUSE_RELEASE(m->b) && - MOUSE_WHEEL(m->b) == 0 && + !MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b)) return (1); } @@ -237,7 +269,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) if (MOUSE_RELEASE(m->b)) goto chosen; } else { - if (MOUSE_WHEEL(m->b) == 0 && !MOUSE_DRAG(m->b)) + if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b)) goto chosen; } md->choice = m->y - (md->py + 1); diff --git a/mode-tree.c b/mode-tree.c index c92f7cff..c007e27f 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -104,7 +104,6 @@ struct mode_tree_menu { struct mode_tree_data *data; struct client *c; u_int line; - void *itemdata; }; static void mode_tree_free_items(struct mode_tree_list *); @@ -716,14 +715,14 @@ mode_tree_draw(struct mode_tree_data *mtd) screen_write_nputs(&ctx, w, &gc0, "%s", text); if (mti->text != NULL) { format_draw(&ctx, &gc0, w - width, mti->text, - NULL); + NULL, 0); } } else { screen_write_clearendofline(&ctx, gc.bg); screen_write_nputs(&ctx, w, &gc, "%s", text); if (mti->text != NULL) { format_draw(&ctx, &gc, w - width, mti->text, - NULL); + NULL, 0); } } free(text); @@ -736,10 +735,8 @@ mode_tree_draw(struct mode_tree_data *mtd) } sy = screen_size_y(s); - if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) { - screen_write_stop(&ctx); - return; - } + if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4) + goto done; line = &mtd->line_list[mtd->current]; mti = line->item; @@ -747,7 +744,7 @@ mode_tree_draw(struct mode_tree_data *mtd) mti = mti->parent; screen_write_cursormove(&ctx, 0, h, 0); - screen_write_box(&ctx, w, sy - h); + screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL); if (mtd->sort_list != NULL) { xasprintf(&text, " %s (sort: %s%s)", mti->name, @@ -783,6 +780,8 @@ mode_tree_draw(struct mode_tree_data *mtd) mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y); } +done: + screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0); screen_write_stop(&ctx); } @@ -909,18 +908,14 @@ static void mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, key_code key, void *data) { - struct mode_tree_menu *mtm = data; - struct mode_tree_data *mtd = mtm->data; - struct mode_tree_item *mti; + struct mode_tree_menu *mtm = data; + struct mode_tree_data *mtd = mtm->data; if (mtd->dead || key == KEYC_NONE) goto out; if (mtm->line >= mtd->line_size) goto out; - mti = mtd->line_list[mtm->line].item; - if (mti->itemdata != mtm->itemdata) - goto out; mtd->current = mtm->line; mtd->menucb(mtd->modedata, mtm->c, key); @@ -954,14 +949,13 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, title = xstrdup(""); } menu = menu_create(title); - menu_add_items(menu, items, NULL, NULL, NULL); + menu_add_items(menu, items, NULL, c, NULL); free(title); mtm = xmalloc(sizeof *mtm); mtm->data = mtd; mtm->c = c; mtm->line = line; - mtm->itemdata = mti->itemdata; mtd->references++; if (x >= (menu->width + 4) / 2) @@ -1055,7 +1049,6 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, case '\016': /* C-n */ mode_tree_down(mtd, 1); break; - case 'g': case KEYC_PPAGE: case '\002': /* C-b */ for (i = 0; i < mtd->height; i++) { @@ -1064,7 +1057,6 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_up(mtd, 1); } break; - case 'G': case KEYC_NPAGE: case '\006': /* C-f */ for (i = 0; i < mtd->height; i++) { @@ -1073,10 +1065,12 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_down(mtd, 1); } break; + case 'g': case KEYC_HOME: mtd->current = 0; mtd->offset = 0; break; + case 'G': case KEYC_END: mtd->current = mtd->line_size - 1; if (mtd->current > mtd->height - 1) @@ -34,18 +34,37 @@ struct notify_entry { int pane; }; +static struct cmdq_item * +notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne, + struct cmd_list *cmdlist, struct cmdq_state *state) +{ + struct cmdq_item *new_item; + char *s; + + if (cmdlist == NULL) + return (item); + if (log_get_level() != 0) { + s = cmd_list_print(cmdlist, 0); + log_debug("%s: hook %s is: %s", __func__, ne->name, s); + free(s); + } + new_item = cmdq_get_command(cmdlist, state); + return (cmdq_insert_after(item, new_item)); +} + static void notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) { struct cmd_find_state fs; struct options *oo; - struct cmdq_item *new_item; - struct cmdq_state *new_state; + struct cmdq_state *state; struct options_entry *o; struct options_array_item *a; struct cmd_list *cmdlist; + const char *value; + struct cmd_parse_result *pr; - log_debug("%s: %s", __func__, ne->name); + log_debug("%s: inserting hook %s", __func__, ne->name); cmd_find_clear_state(&fs, 0); if (cmd_find_empty_state(&ne->fs) || !cmd_find_valid_state(&ne->fs)) @@ -66,23 +85,37 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) oo = fs.wl->window->options; o = options_get(oo, ne->name); } - if (o == NULL) + if (o == NULL) { + log_debug("%s: hook %s not found", __func__, ne->name); return; + } - new_state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS); - cmdq_add_formats(new_state, ne->formats); - - a = options_array_first(o); - while (a != NULL) { - cmdlist = options_array_item_value(a)->cmdlist; - if (cmdlist != NULL) { - new_item = cmdq_get_command(cmdlist, new_state); - item = cmdq_insert_after(item, new_item); + state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS); + cmdq_add_formats(state, ne->formats); + + if (*ne->name == '@') { + value = options_get_string(oo, ne->name); + pr = cmd_parse_from_string(value, NULL); + switch (pr->status) { + case CMD_PARSE_ERROR: + log_debug("%s: can't parse hook %s: %s", __func__, + ne->name, pr->error); + free(pr->error); + break; + case CMD_PARSE_SUCCESS: + notify_insert_one_hook(item, ne, pr->cmdlist, state); + break; + } + } else { + a = options_array_first(o); + while (a != NULL) { + cmdlist = options_array_item_value(a)->cmdlist; + item = notify_insert_one_hook(item, ne, cmdlist, state); + a = options_array_next(a); } - a = options_array_next(a); } - cmdq_free_state(new_state); + cmdq_free_state(state); } static enum cmd_retval diff --git a/options-table.c b/options-table.c index 76c2b053..b442d65e 100644 --- a/options-table.c +++ b/options-table.c @@ -56,12 +56,22 @@ static const char *options_table_bell_action_list[] = { static const char *options_table_visual_bell_list[] = { "off", "on", "both", NULL }; +static const char *options_table_cursor_style_list[] = { + "default", "blinking-block", "block", "blinking-underline", "underline", + "blinking-bar", "bar", NULL +}; static const char *options_table_pane_status_list[] = { "off", "top", "bottom", NULL }; -static const char *options_table_pane_lines_list[] = { +static const char *options_table_pane_border_indicators_list[] = { + "off", "colour", "arrows", "both", NULL +}; +static const char *options_table_pane_border_lines_list[] = { "single", "double", "heavy", "simple", "number", NULL }; +static const char *options_table_popup_border_lines_list[] = { + "single", "double", "heavy", "simple", "rounded", "padded", "none", NULL +}; static const char *options_table_set_clipboard_list[] = { "off", "external", "on", NULL }; @@ -184,6 +194,7 @@ const struct options_name_map options_other_names[] = { { "display-panes-color", "display-panes-colour" }, { "display-panes-active-color", "display-panes-active-colour" }, { "clock-mode-color", "clock-mode-colour" }, + { "cursor-color", "cursor-colour" }, { "pane-colors", "pane-colours" }, { NULL, NULL } }; @@ -231,6 +242,21 @@ const struct options_table_entry options_table[] = { "If empty, no command is run." }, + { .name = "cursor-colour", + .type = OPTIONS_TABLE_COLOUR, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_num = -1, + .text = "Colour of the cursor." + }, + + { .name = "cursor-style", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .choices = options_table_cursor_style_list, + .default_num = 0, + .text = "Style of the cursor." + }, + { .name = "default-terminal", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, @@ -251,6 +277,7 @@ const struct options_table_entry options_table[] = { .minimum = 0, .maximum = INT_MAX, .default_num = 500, + .unit = "milliseconds", .text = "Time to wait before assuming a key is Escape." }, @@ -275,7 +302,7 @@ const struct options_table_entry options_table[] = { .choices = options_table_extended_keys_list, .default_num = 0, .text = "Whether to request extended key sequences from terminals " - "that support it." + "that support it." }, { .name = "focus-events", @@ -335,7 +362,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "xterm*:clipboard:ccolour:cstyle:focus:title," - "screen*:title", + "screen*:title," + "rxvt*:ignorefkeys", .separator = ",", .text = "List of terminal features, used if they cannot be " "automatically detected." @@ -774,6 +802,14 @@ const struct options_table_entry options_table[] = { "linked to ('off')." }, + { .name = "allow-passthrough", + .type = OPTIONS_TABLE_FLAG, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_num = 0, + .text = "Whether applications are allowed to use the escape sequence " + "to bypass tmux." + }, + { .name = "allow-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, @@ -847,6 +883,13 @@ const struct options_table_entry options_table[] = { .text = "Style of the marked line in copy mode." }, + { .name = "fill-character", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "", + .text = "Character used to fill unused parts of window." + }, + { .name = "main-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, @@ -941,19 +984,28 @@ const struct options_table_entry options_table[] = { { .name = "pane-border-format", .type = OPTIONS_TABLE_STRING, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "#{?pane_active,#[reverse],}#{pane_index}#[default] " "\"#{pane_title}\"", .text = "Format of text in the pane status lines." }, + { .name = "pane-border-indicators", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_WINDOW, + .choices = options_table_pane_border_indicators_list, + .default_num = PANE_BORDER_COLOUR, + .text = "Whether to indicate the active pane by colouring border or " + "displaying arrow markers." + }, + { .name = "pane-border-lines", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, - .choices = options_table_pane_lines_list, + .choices = options_table_pane_border_lines_list, .default_num = PANE_LINES_SINGLE, .text = "Type of characters used to draw pane border lines. Some of " - "these are only supported on terminals with UTF-8 support." + "these are only supported on terminals with UTF-8 support." }, { .name = "pane-border-status", @@ -981,6 +1033,33 @@ const struct options_table_entry options_table[] = { .text = "The default colour palette for colours zero to 255." }, + { .name = "popup-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default style of popups." + }, + + { .name = "popup-border-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default style of popup borders." + }, + + { .name = "popup-border-lines", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_WINDOW, + .choices = options_table_popup_border_lines_list, + .default_num = BOX_LINES_SINGLE, + .text = "Type of characters used to draw popup border lines. Some of " + "these are only supported on terminals with UTF-8 support." + }, + { .name = "remain-on-exit", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, @@ -990,6 +1069,27 @@ const struct options_table_entry options_table[] = { "killed ('off' or 'failed') when the program inside exits." }, + { .name = "remain-on-exit-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_str = "Pane is dead (" + "#{?#{!=:#{pane_dead_status},}," + "status #{pane_dead_status},}" + "#{?#{!=:#{pane_dead_signal},}," + "signal #{pane_dead_signal},}, " + "#{t:pane_dead_time})", + .text = "Message shown after the program in a pane has exited, if " + "remain-on-exit is enabled." + }, + + { .name = "scroll-on-clear", + .type = OPTIONS_TABLE_FLAG, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_num = 1, + .text = "Whether the contents of the screen should be scrolled into" + "history when clearing the whole screen." + }, + { .name = "synchronize-panes", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, @@ -1108,7 +1208,7 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_num = 1, .text = "Whether xterm-style function key sequences should be sent. " - "This option is no longer used." + "This option is no longer used." }, /* Hook options. */ @@ -1156,8 +1256,8 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("client-active", ""), OPTIONS_TABLE_HOOK("client-attached", ""), OPTIONS_TABLE_HOOK("client-detached", ""), - OPTIONS_TABLE_HOOK("client-focus-in", ""), - OPTIONS_TABLE_HOOK("client-focus-out", ""), + OPTIONS_TABLE_HOOK("client-focus-in", ""), + OPTIONS_TABLE_HOOK("client-focus-out", ""), OPTIONS_TABLE_HOOK("client-resized", ""), OPTIONS_TABLE_HOOK("client-session-changed", ""), OPTIONS_TABLE_PANE_HOOK("pane-died", ""), @@ -1175,6 +1275,7 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("window-linked", ""), OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""), OPTIONS_TABLE_WINDOW_HOOK("window-renamed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-resized", ""), OPTIONS_TABLE_HOOK("window-unlinked", ""), { .name = NULL } @@ -989,28 +989,39 @@ options_from_string_flag(struct options *oo, const char *name, return (0); } +int +options_find_choice(const struct options_table_entry *oe, const char *value, + char **cause) +{ + const char **cp; + int n = 0, choice = -1; + + for (cp = oe->choices; *cp != NULL; cp++) { + if (strcmp(*cp, value) == 0) + choice = n; + n++; + } + if (choice == -1) { + xasprintf(cause, "unknown value: %s", value); + return (-1); + } + return (choice); +} + static int options_from_string_choice(const struct options_table_entry *oe, struct options *oo, const char *name, const char *value, char **cause) { - const char **cp; - int n, choice = -1; + int choice = -1; if (value == NULL) { choice = options_get_number(oo, name); if (choice < 2) choice = !choice; } else { - n = 0; - for (cp = oe->choices; *cp != NULL; cp++) { - if (strcmp(*cp, value) == 0) - choice = n; - n++; - } - if (choice == -1) { - xasprintf(cause, "unknown value: %s", value); + choice = options_find_choice(oe, value, cause); + if (choice < 0) return (-1); - } } options_set_number(oo, name, choice); return (0); @@ -1096,14 +1107,28 @@ options_push_changes(const char *name) struct window *w; struct window_pane *wp; + log_debug("%s: %s", __func__, name); + if (strcmp(name, "automatic-rename") == 0) { RB_FOREACH(w, windows, &windows) { if (w->active == NULL) continue; - if (options_get_number(w->options, "automatic-rename")) + if (options_get_number(w->options, name)) w->active->flags |= PANE_CHANGED; } } + if (strcmp(name, "cursor-colour") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + window_pane_default_cursor(wp); + } + if (strcmp(name, "cursor-style") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + window_pane_default_cursor(wp); + } + if (strcmp(name, "fill-character") == 0) { + RB_FOREACH(w, windows, &windows) + window_set_fill_character(w); + } if (strcmp(name, "key-table") == 0) { TAILQ_FOREACH(loop, &clients, entry) server_client_set_key_table(loop, NULL); diff --git a/osdep-netbsd.c b/osdep-netbsd.c index b473e017..6003c035 100644 --- a/osdep-netbsd.c +++ b/osdep-netbsd.c @@ -35,9 +35,6 @@ ((p)->p_stat == SSTOP || (p)->p_stat == SZOMB) struct kinfo_proc2 *cmp_procs(struct kinfo_proc2 *, struct kinfo_proc2 *); -char *osdep_get_name(int, char *); -char *osdep_get_cwd(int); -struct event_base *osdep_event_init(void); struct kinfo_proc2 * cmp_procs(struct kinfo_proc2 *p1, struct kinfo_proc2 *p2) diff --git a/osdep-openbsd.c b/osdep-openbsd.c index f5c61372..b4c8fc56 100644 --- a/osdep-openbsd.c +++ b/osdep-openbsd.c @@ -16,8 +16,8 @@ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/param.h> /* MAXCOMLEN */ #include <sys/types.h> +#include <sys/signal.h> #include <sys/proc.h> #include <sys/sysctl.h> #include <sys/stat.h> @@ -141,7 +141,7 @@ char * osdep_get_cwd(int fd) { int name[] = { CTL_KERN, KERN_PROC_CWD, 0 }; - static char path[MAXPATHLEN]; + static char path[PATH_MAX]; size_t pathlen = sizeof path; if ((name[2] = tcgetpgrp(fd)) == -1) @@ -111,6 +111,12 @@ paste_walk(struct paste_buffer *pb) return (RB_NEXT(paste_time_tree, &paste_by_time, pb)); } +int +paste_is_empty(void) +{ + return RB_ROOT(&paste_by_time) == NULL; +} + /* Get the most recent automatic buffer. */ struct paste_buffer * paste_get_top(const char **name) @@ -118,6 +124,8 @@ paste_get_top(const char **name) struct paste_buffer *pb; pb = RB_MIN(paste_time_tree, &paste_by_time); + while (pb != NULL && !pb->automatic) + pb = RB_NEXT(paste_time_tree, &paste_by_time, pb); if (pb == NULL) return (NULL); if (name != NULL) @@ -30,9 +30,15 @@ struct popup_data { struct client *c; struct cmdq_item *item; int flags; + char *title; + + struct grid_cell border_cell; + enum box_lines border_lines; struct screen s; + struct grid_cell defaults; struct colour_palette palette; + struct job *job; struct input_ctx *ictx; int status; @@ -116,7 +122,7 @@ popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c) ttyctx->wsx = c->tty.sx; ttyctx->wsy = c->tty.sy; - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { ttyctx->xoff = ttyctx->rxoff = pd->px; ttyctx->yoff = ttyctx->ryoff = pd->py; } else { @@ -132,6 +138,7 @@ popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) { struct popup_data *pd = ctx->arg; + memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults); ttyctx->palette = &pd->palette; ttyctx->redraw_cb = popup_redraw_cb; ttyctx->set_client_cb = popup_set_client_cb; @@ -146,7 +153,7 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) if (pd->md != NULL) return (menu_mode_cb(c, pd->md, cx, cy)); - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { *cx = pd->px + pd->s.cx; *cy = pd->py + pd->s.cy; } else { @@ -156,18 +163,49 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) return (&pd->s); } -static int -popup_check_cb(struct client *c, void *data, u_int px, u_int py) +/* Return parts of the input range which are not obstructed by the popup. */ +static void +popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx, + struct overlay_ranges *r) { struct popup_data *pd = data; + struct overlay_ranges or[2]; + u_int i, j, k = 0; - if (pd->md != NULL && menu_check_cb(c, pd->md, px, py) == 0) - return (0); - if (px < pd->px || px > pd->px + pd->sx - 1) - return (1); - if (py < pd->py || py > pd->py + pd->sy - 1) - return (1); - return (0); + if (pd->md != NULL) { + /* Check each returned range for the menu against the popup. */ + menu_check_cb(c, pd->md, px, py, nx, r); + for (i = 0; i < 2; i++) { + server_client_overlay_range(pd->px, pd->py, pd->sx, + pd->sy, r->px[i], py, r->nx[i], &or[i]); + } + + /* + * or has up to OVERLAY_MAX_RANGES non-overlapping ranges, + * ordered from left to right. Collect them in the output. + */ + for (i = 0; i < 2; i++) { + /* Each or[i] only has 2 ranges. */ + for (j = 0; j < 2; j++) { + if (or[i].nx[j] > 0) { + r->px[k] = or[i].px[j]; + r->nx[k] = or[i].nx[j]; + k++; + } + } + } + + /* Zero remaining ranges if any. */ + for (i = k; i < OVERLAY_MAX_RANGES; i++) { + r->px[i] = 0; + r->nx[i] = 0; + } + + return; + } + + server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx, + r); } static void @@ -179,26 +217,29 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) struct screen_write_ctx ctx; u_int i, px = pd->px, py = pd->py; struct colour_palette *palette = &pd->palette; - struct grid_cell gc; + struct grid_cell defaults; screen_init(&s, pd->sx, pd->sy, 0); screen_write_start(&ctx, &s); screen_write_clearscreen(&ctx, 8); - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { screen_write_cursormove(&ctx, 0, 0, 0); screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy); } else if (pd->sx > 2 && pd->sy > 2) { - screen_write_box(&ctx, pd->sx, pd->sy); + screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines, + &pd->border_cell, pd->title); screen_write_cursormove(&ctx, 1, 1, 0); screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2, pd->sy - 2); } screen_write_stop(&ctx); - memcpy(&gc, &grid_default_cell, sizeof gc); - gc.fg = pd->palette.fg; - gc.bg = pd->palette.bg; + memcpy(&defaults, &pd->defaults, sizeof defaults); + if (defaults.fg == 8) + defaults.fg = palette->fg; + if (defaults.bg == 8) + defaults.bg = palette->bg; if (pd->md != NULL) { c->overlay_check = menu_check_cb; @@ -207,8 +248,10 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) c->overlay_check = NULL; c->overlay_data = NULL; } - for (i = 0; i < pd->sy; i++) - tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &gc, palette); + for (i = 0; i < pd->sy; i++) { + tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, + palette); + } if (pd->md != NULL) { c->overlay_check = NULL; c->overlay_data = NULL; @@ -245,6 +288,7 @@ popup_free_cb(struct client *c, void *data) screen_free(&pd->s); colour_palette_free(&pd->palette); + free(pd->title); free(pd); } @@ -278,7 +322,7 @@ popup_resize_cb(__unused struct client *c, void *data) pd->px = pd->ppx; /* Avoid zero size screens. */ - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { screen_resize(&pd->s, pd->sx, pd->sy, 0); if (pd->job != NULL) job_resize(pd->job, pd->sx, pd->sy ); @@ -404,7 +448,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd, pd->ppy = py; server_redraw_client(c); } else if (pd->dragging == SIZE) { - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { if (m->x < pd->px + 1) return; if (m->y < pd->py + 1) @@ -420,7 +464,7 @@ popup_handle_drag(struct client *c, struct popup_data *pd, pd->psx = pd->sx; pd->psy = pd->sy; - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { screen_resize(&pd->s, pd->sx, pd->sy, 0); if (pd->job != NULL) job_resize(pd->job, pd->sx, pd->sy); @@ -464,11 +508,11 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) m->x > pd->px + pd->sx - 1 || m->y < pd->py || m->y > pd->py + pd->sy - 1) { - if (MOUSE_BUTTONS(m->b) == 2) + if (MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3) goto menu; return (0); } - if (~pd->flags & POPUP_NOBORDER) { + if (pd->border_lines != BOX_LINES_NONE) { if (m->x == pd->px) border = LEFT; else if (m->x == pd->px + pd->sx - 1) @@ -479,16 +523,16 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) border = BOTTOM; } if ((m->b & MOUSE_MASK_MODIFIERS) == 0 && - MOUSE_BUTTONS(m->b) == 2 && + MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 && (border == LEFT || border == TOP)) goto menu; if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) || border != NONE) { if (!MOUSE_DRAG(m->b)) goto out; - if (MOUSE_BUTTONS(m->lb) == 0) + if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1) pd->dragging = MOVE; - else if (MOUSE_BUTTONS(m->lb) == 2) + else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3) pd->dragging = SIZE; pd->dx = m->lx - pd->px; pd->dy = m->ly - pd->py; @@ -502,7 +546,7 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) if (pd->job != NULL) { if (KEYC_IS_MOUSE(event->key)) { /* Must be inside, checked already. */ - if (pd->flags & POPUP_NOBORDER) { + if (pd->border_lines == BOX_LINES_NONE) { px = m->x - pd->px; py = m->y - pd->py; } else { @@ -521,10 +565,10 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) menu: pd->menu = menu_create(""); if (pd->flags & POPUP_INTERNAL) { - menu_add_items(pd->menu, popup_internal_menu_items, NULL, NULL, + menu_add_items(pd->menu, popup_internal_menu_items, NULL, c, NULL); } else - menu_add_items(pd->menu, popup_menu_items, NULL, NULL, NULL); + menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL); if (m->x >= (pd->menu->width + 4) / 2) x = m->x - (pd->menu->width + 4) / 2; else @@ -588,14 +632,25 @@ popup_job_complete_cb(struct job *job) } int -popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, - u_int sy, const char *shellcmd, int argc, char **argv, const char *cwd, - struct client *c, struct session *s, popup_close_cb cb, void *arg) +popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px, + u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd, + int argc, char **argv, const char *cwd, const char *title, struct client *c, + struct session *s, const char* style, const char* border_style, + popup_close_cb cb, void *arg) { struct popup_data *pd; u_int jx, jy; + struct options *o; + struct style sytmp; + + if (s != NULL) + o = s->curw->window->options; + else + o = c->session->curw->window->options; - if (flags & POPUP_NOBORDER) { + if (lines == BOX_LINES_DEFAULT) + lines = options_get_number(o, "popup-border-lines"); + if (lines == BOX_LINES_NONE) { if (sx < 1 || sy < 1) return (-1); jx = sx; @@ -612,6 +667,8 @@ popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, pd = xcalloc(1, sizeof *pd); pd->item = item; pd->flags = flags; + if (title != NULL) + pd->title = xstrdup(title); pd->c = c; pd->c->references++; @@ -620,10 +677,33 @@ popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, pd->arg = arg; pd->status = 128 + SIGHUP; - screen_init(&pd->s, sx - 2, sy - 2, 0); + pd->border_lines = lines; + memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell); + style_apply(&pd->border_cell, o, "popup-border-style", NULL); + if (border_style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) { + pd->border_cell.fg = sytmp.gc.fg; + pd->border_cell.bg = sytmp.gc.bg; + } + } + pd->border_cell.attr = 0; + + screen_init(&pd->s, jx, jy, 0); colour_palette_init(&pd->palette); colour_palette_from_option(&pd->palette, global_w_options); + memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults); + style_apply(&pd->defaults, o, "popup-style", NULL); + if (style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, &pd->defaults, style) == 0) { + pd->defaults.fg = sytmp.gc.fg; + pd->defaults.bg = sytmp.gc.bg; + } + } + pd->defaults.attr = 0; + pd->px = px; pd->py = py; pd->sx = sx; @@ -634,7 +714,7 @@ popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, pd->psx = sx; pd->psy = sy; - pd->job = job_run(shellcmd, argc, argv, s, cwd, + pd->job = job_run(shellcmd, argc, argv, env, s, cwd, popup_job_update_cb, popup_job_complete_cb, NULL, pd, JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, jx, jy); pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette); @@ -723,8 +803,9 @@ popup_editor(struct client *c, const char *buf, size_t len, py = (c->tty.sy / 2) - (sy / 2); xasprintf(&cmd, "%s %s", editor, path); - if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, - cmd, 0, NULL, _PATH_TMP, c, NULL, popup_editor_close_cb, pe) != 0) { + if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT, + NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL, + NULL, NULL, popup_editor_close_cb, pe) != 0) { popup_editor_free(pe); free(cmd); return (-1); @@ -56,6 +56,7 @@ struct tmuxpeer { struct imsgbuf ibuf; struct event event; + uid_t uid; int flags; #define PEER_BAD 0x1 @@ -201,7 +202,7 @@ proc_start(const char *name) #endif , event_get_version(), event_get_method() #ifdef HAVE_UTF8PROC - , utf8proc_version () + , utf8proc_version() #endif ); @@ -308,6 +309,7 @@ proc_add_peer(struct tmuxproc *tp, int fd, void (*dispatchcb)(struct imsg *, void *), void *arg) { struct tmuxpeer *peer; + gid_t gid; peer = xcalloc(1, sizeof *peer); peer->parent = tp; @@ -318,6 +320,9 @@ proc_add_peer(struct tmuxproc *tp, int fd, imsg_init(&peer->ibuf, fd); event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); + if (getpeereid(fd, &peer->uid, &gid) != 0) + peer->uid = (uid_t)-1; + log_debug("add peer %p: %d (%p)", peer, fd, arg); TAILQ_INSERT_TAIL(&tp->peers, peer, entry); @@ -345,6 +350,12 @@ proc_kill_peer(struct tmuxpeer *peer) } void +proc_flush_peer(struct tmuxpeer *peer) +{ + imsg_flush(&peer->ibuf); +} + +void proc_toggle_log(struct tmuxproc *tp) { log_toggle(tp->name); @@ -373,3 +384,9 @@ proc_fork_and_daemon(int *fd) return (pid); } } + +uid_t +proc_get_peer_uid(struct tmuxpeer *peer) +{ + return (peer->uid); +} diff --git a/regress/capture-pane-hyperlink.sh b/regress/capture-pane-hyperlink.sh new file mode 100644 index 00000000..681a8e17 --- /dev/null +++ b/regress/capture-pane-hyperlink.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# capture-pane -e for OSC 8 hyperlink + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +TMP=$(mktemp) +TMP2=$(mktemp) +trap "rm -f $TMP $TMP2" 0 1 15 +$TMUX kill-server 2>/dev/null + +do_test() { + $TMUX -f/dev/null new -d " + printf '$1' + $TMUX capturep -peS0 -E1 >$TMP" + echo $2 > $TMP2 + sleep 1 + cmp $TMP $TMP2 || exit 1 + return 0 +} + +do_test '\033]8;id=1;https://github.com\033\\test1\033]8;;\033\\\n' '\033]8;id=1;https://github.com\033\\test1\033]8;;\033\\\n' || exit 1 +do_test '\033]8;;https://github.com/tmux/tmux\033\\test1\033]8;;\033\\\n' '\033]8;;https://github.com/tmux/tmux\033\\test1\033]8;;\033\\\n' || exit 1 + +$TMUX has 2>/dev/null && exit 1 + +exit 0 diff --git a/regress/capture-pane-sgr0.sh b/regress/capture-pane-sgr0.sh index 0dd9cd82..6a19ac8a 100644 --- a/regress/capture-pane-sgr0.sh +++ b/regress/capture-pane-sgr0.sh @@ -22,8 +22,8 @@ $TMUX -f/dev/null new -d " sleep 1 ( - printf '\033[1m\033[31m\033[42mabc\033[0m\033[31m\033[49mdef\033[39m\n' - printf '\033[100m bright bg \033[49m\n' + printf '\033[1m\033[31m\033[42mabc\033[0m\033[31m\033[49mdef\n' + printf '\033[39m\033[100m bright bg\n' ) | cmp - $TMP || exit 1 $TMUX has 2>/dev/null && exit 1 diff --git a/regress/if-shell-error.sh b/regress/if-shell-error.sh index 24dc578e..2896d9aa 100644 --- a/regress/if-shell-error.sh +++ b/regress/if-shell-error.sh @@ -11,16 +11,13 @@ TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) -trap "rm -f $TMP" 0 1 15 +OUT=$(mktemp) +trap "rm -f $TMP $OUT" 0 1 15 cat <<EOF >$TMP if 'true' 'wibble wobble' EOF -$TMUX -f$TMP new -d || exit 1 -sleep 1 -E=$($TMUX display -p '#{pane_in_mode}') -$TMUX kill-server 2>/dev/null -[ "$E" = "1" ] || exit 1 - -exit 0 +$TMUX -f$TMP -C new <<EOF >$OUT +EOF +grep -q "^%config-error $TMP:1: $TMP:1: unknown command: wibble$" $OUT diff --git a/regress/kill-session-process-exit.sh b/regress/kill-session-process-exit.sh index 69ee27a2..82cd7412 100644 --- a/regress/kill-session-process-exit.sh +++ b/regress/kill-session-process-exit.sh @@ -8,13 +8,14 @@ TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null +sleep 1 $TMUX -f/dev/null new -d 'sleep 1000' || exit 1 P=$($TMUX display -pt0:0.0 '#{pane_pid}') $TMUX -f/dev/null new -d || exit 1 sleep 1 $TMUX kill-session -t0: -sleep 1 +sleep 3 kill -0 $P 2>/dev/null && exit 1 $TMUX kill-server 2>/dev/null @@ -61,6 +61,7 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) tty_update_window_offset(w); server_redraw_window(w); notify_window("window-layout-changed", w); + notify_window("window-resized", w); w->flags &= ~WINDOW_RESIZE; } @@ -178,7 +179,7 @@ clients_calculate_size(int type, int current, struct client *c, cw = NULL; /* Work out this client's size. */ - if (cw != NULL) { + if (cw != NULL && cw->sx != 0 && cw->sy != 0) { cx = cw->sx; cy = cw->sy; } else { @@ -348,6 +349,8 @@ recalculate_size_skip_client(struct client *loop, __unused int type, * is not the current window - this is used for aggressive-resize. * Otherwise skip any session that doesn't contain the window. */ + if (loop->session->curw == NULL) + return (1); if (current) return (loop->session->curw->window != w); return (session_has(loop->session, w) == 0); diff --git a/screen-redraw.c b/screen-redraw.c index 82e390cd..470135cc 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -31,70 +31,32 @@ static void screen_redraw_draw_pane(struct screen_redraw_ctx *, static void screen_redraw_set_context(struct client *, struct screen_redraw_ctx *); -#define CELL_INSIDE 0 -#define CELL_TOPBOTTOM 1 -#define CELL_LEFTRIGHT 2 -#define CELL_TOPLEFT 3 -#define CELL_TOPRIGHT 4 -#define CELL_BOTTOMLEFT 5 -#define CELL_BOTTOMRIGHT 6 -#define CELL_TOPJOIN 7 -#define CELL_BOTTOMJOIN 8 -#define CELL_LEFTJOIN 9 -#define CELL_RIGHTJOIN 10 -#define CELL_JOIN 11 -#define CELL_OUTSIDE 12 - -#define CELL_BORDERS " xqlkmjwvtun~" - #define START_ISOLATE "\342\201\246" #define END_ISOLATE "\342\201\251" -static const struct utf8_data screen_redraw_double_borders[] = { - { "", 0, 0, 0 }, - { "\342\225\221", 0, 3, 1 }, /* U+2551 */ - { "\342\225\220", 0, 3, 1 }, /* U+2550 */ - { "\342\225\224", 0, 3, 1 }, /* U+2554 */ - { "\342\225\227", 0, 3, 1 }, /* U+2557 */ - { "\342\225\232", 0, 3, 1 }, /* U+255A */ - { "\342\225\235", 0, 3, 1 }, /* U+255D */ - { "\342\225\246", 0, 3, 1 }, /* U+2566 */ - { "\342\225\251", 0, 3, 1 }, /* U+2569 */ - { "\342\225\240", 0, 3, 1 }, /* U+2560 */ - { "\342\225\243", 0, 3, 1 }, /* U+2563 */ - { "\342\225\254", 0, 3, 1 }, /* U+256C */ - { "\302\267", 0, 2, 1 } /* U+00B7 */ -}; - -static const struct utf8_data screen_redraw_heavy_borders[] = { - { "", 0, 0, 0 }, - { "\342\224\203", 0, 3, 1 }, /* U+2503 */ - { "\342\224\201", 0, 3, 1 }, /* U+2501 */ - { "\342\224\223", 0, 3, 1 }, /* U+2513 */ - { "\342\224\217", 0, 3, 1 }, /* U+250F */ - { "\342\224\227", 0, 3, 1 }, /* U+2517 */ - { "\342\224\233", 0, 3, 1 }, /* U+251B */ - { "\342\224\263", 0, 3, 1 }, /* U+2533 */ - { "\342\224\273", 0, 3, 1 }, /* U+253B */ - { "\342\224\243", 0, 3, 1 }, /* U+2523 */ - { "\342\224\253", 0, 3, 1 }, /* U+252B */ - { "\342\225\213", 0, 3, 1 }, /* U+254B */ - { "\302\267", 0, 2, 1 } /* U+00B7 */ -}; - +/* Border in relation to a pane. */ enum screen_redraw_border_type { SCREEN_REDRAW_OUTSIDE, SCREEN_REDRAW_INSIDE, - SCREEN_REDRAW_BORDER + SCREEN_REDRAW_BORDER_LEFT, + SCREEN_REDRAW_BORDER_RIGHT, + SCREEN_REDRAW_BORDER_TOP, + SCREEN_REDRAW_BORDER_BOTTOM }; +#define BORDER_MARKERS " +,.-" /* Get cell border character. */ static void -screen_redraw_border_set(struct window_pane *wp, int pane_lines, int cell_type, - struct grid_cell *gc) +screen_redraw_border_set(struct window *w, struct window_pane *wp, + enum pane_lines pane_lines, int cell_type, struct grid_cell *gc) { u_int idx; + if (cell_type == CELL_OUTSIDE && w->fill_character != NULL) { + utf8_copy(&gc->data, &w->fill_character[0]); + return; + } + switch (pane_lines) { case PANE_LINES_NUMBER: if (cell_type == CELL_OUTSIDE) { @@ -110,15 +72,15 @@ screen_redraw_border_set(struct window_pane *wp, int pane_lines, int cell_type, break; case PANE_LINES_DOUBLE: gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, &screen_redraw_double_borders[cell_type]); + utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); break; case PANE_LINES_HEAVY: gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, &screen_redraw_heavy_borders[cell_type]); + utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); break; case PANE_LINES_SIMPLE: gc->attr &= ~GRID_ATTR_CHARSET; - utf8_set(&gc->data, " |-+++++++++."[cell_type]); + utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); break; default: gc->attr |= GRID_ATTR_CHARSET; @@ -150,64 +112,74 @@ static enum screen_redraw_border_type screen_redraw_pane_border(struct window_pane *wp, u_int px, u_int py, int pane_status) { - u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; + struct options *oo = wp->window->options; + int split = 0; + u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; /* Inside pane. */ if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) return (SCREEN_REDRAW_INSIDE); + /* Get pane indicator. */ + switch (options_get_number(oo, "pane-border-indicators")) { + case PANE_BORDER_COLOUR: + case PANE_BORDER_BOTH: + split = 1; + break; + } + /* Left/right borders. */ if (pane_status == PANE_STATUS_OFF) { - if (screen_redraw_two_panes(wp->window, 0)) { + if (screen_redraw_two_panes(wp->window, 0) && split) { if (wp->xoff == 0 && px == wp->sx && py <= wp->sy / 2) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_RIGHT); if (wp->xoff != 0 && px == wp->xoff - 1 && py > wp->sy / 2) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_LEFT); } else { if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { if (wp->xoff != 0 && px == wp->xoff - 1) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_LEFT); if (px == ex) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_RIGHT); } } } else { if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { if (wp->xoff != 0 && px == wp->xoff - 1) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_LEFT); if (px == ex) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_RIGHT); } } /* Top/bottom borders. */ if (pane_status == PANE_STATUS_OFF) { - if (screen_redraw_two_panes(wp->window, 1)) { + if (screen_redraw_two_panes(wp->window, 1) && split) { if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_BOTTOM); if (wp->yoff != 0 && py == wp->yoff - 1 && px > wp->sx / 2) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_TOP); } else { if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { if (wp->yoff != 0 && py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_TOP); if (py == ey) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_BOTTOM); } } } else if (pane_status == PANE_STATUS_TOP) { if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { if (wp->yoff != 0 && py == wp->yoff - 1) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_TOP); } } else { if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { if (py == ey) - return (SCREEN_REDRAW_BORDER); + return (SCREEN_REDRAW_BORDER_BOTTOM); } } @@ -237,10 +209,10 @@ screen_redraw_cell_border(struct client *c, u_int px, u_int py, int pane_status) switch (screen_redraw_pane_border(wp, px, py, pane_status)) { case SCREEN_REDRAW_INSIDE: return (0); - case SCREEN_REDRAW_BORDER: - return (1); case SCREEN_REDRAW_OUTSIDE: break; + default: + return (1); } } @@ -394,7 +366,7 @@ screen_redraw_check_is(u_int px, u_int py, int pane_status, enum screen_redraw_border_type border; border = screen_redraw_pane_border(wp, px, py, pane_status); - if (border == SCREEN_REDRAW_BORDER) + if (border != SCREEN_REDRAW_INSIDE && border != SCREEN_REDRAW_OUTSIDE) return (1); return (0); } @@ -402,7 +374,7 @@ screen_redraw_check_is(u_int px, u_int py, int pane_status, /* Update pane status. */ static int screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, - struct screen_redraw_ctx *rctx, int pane_lines) + struct screen_redraw_ctx *rctx, enum pane_lines pane_lines) { struct window *w = wp->window; struct grid_cell gc; @@ -421,7 +393,7 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, style_apply(&gc, w->options, "pane-active-border-style", ft); else style_apply(&gc, w->options, "pane-border-style", ft); - fmt = options_get_string(w->options, "pane-border-format"); + fmt = options_get_string(wp->options, "pane-border-format"); expanded = format_expand_time(ft, fmt); if (wp->sx < 4) @@ -442,13 +414,13 @@ screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, else py = wp->yoff + wp->sy; cell_type = screen_redraw_type_of_cell(c, px, py, pane_status); - screen_redraw_border_set(wp, pane_lines, cell_type, &gc); + screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc); screen_write_cell(&ctx, &gc); } gc.attr &= ~GRID_ATTR_CHARSET; screen_write_cursormove(&ctx, 0, 0, 0); - format_draw(&ctx, &gc, width, expanded, NULL); + format_draw(&ctx, &gc, width, expanded, NULL, 0); screen_write_stop(&ctx); free(expanded); @@ -527,11 +499,12 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) static int screen_redraw_update(struct client *c, int flags) { - struct window *w = c->session->curw->window; - struct window_pane *wp; - struct options *wo = w->options; - int redraw, lines; - struct screen_redraw_ctx ctx; + struct window *w = c->session->curw->window; + struct window_pane *wp; + struct options *wo = w->options; + int redraw; + enum pane_lines lines; + struct screen_redraw_ctx ctx; if (c->message_string != NULL) redraw = status_message_redraw(c); @@ -684,15 +657,19 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) struct options *oo = w->options; struct tty *tty = &c->tty; struct format_tree *ft; - struct window_pane *wp; - u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; - int pane_status = ctx->pane_status, isolates; + struct window_pane *wp, *active = server_client_get_pane(c); struct grid_cell gc; const struct grid_cell *tmp; + struct overlay_ranges r; + u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; + int arrows = 0, border; + int pane_status = ctx->pane_status, isolates; - if (c->overlay_check != NULL && - !c->overlay_check(c, c->overlay_data, x, y)) - return; + if (c->overlay_check != NULL) { + c->overlay_check(c, c->overlay_data, x, y, 1, &r); + if (r.nx[0] + r.nx[1] == 0) + return; + } cell_type = screen_redraw_check_cell(c, x, y, pane_status, &wp); if (cell_type == CELL_INSIDE) @@ -718,7 +695,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) screen_redraw_check_is(x, y, pane_status, marked_pane.wp)) gc.attr ^= GRID_ATTR_REVERSE; } - screen_redraw_border_set(wp, ctx->pane_lines, cell_type, &gc); + screen_redraw_border_set(w, wp, ctx->pane_lines, cell_type, &gc); if (cell_type == CELL_TOPBOTTOM && (c->flags & CLIENT_UTF8) && @@ -733,7 +710,35 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) tty_cursor(tty, i, j); if (isolates) tty_puts(tty, END_ISOLATE); - tty_cell(tty, &gc, &grid_default_cell, NULL); + + switch (options_get_number(oo, "pane-border-indicators")) { + case PANE_BORDER_ARROWS: + case PANE_BORDER_BOTH: + arrows = 1; + break; + } + + if (wp != NULL && arrows) { + border = screen_redraw_pane_border(active, x, y, pane_status); + if (((i == wp->xoff + 1 && + (cell_type == CELL_LEFTRIGHT || + (cell_type == CELL_TOPJOIN && + border == SCREEN_REDRAW_BORDER_BOTTOM) || + (cell_type == CELL_BOTTOMJOIN && + border == SCREEN_REDRAW_BORDER_TOP))) || + (j == wp->yoff + 1 && + (cell_type == CELL_TOPBOTTOM || + (cell_type == CELL_LEFTJOIN && + border == SCREEN_REDRAW_BORDER_RIGHT) || + (cell_type == CELL_RIGHTJOIN && + border == SCREEN_REDRAW_BORDER_LEFT)))) && + screen_redraw_check_is(x, y, pane_status, active)) { + gc.attr |= GRID_ATTR_CHARSET; + utf8_set(&gc.data, BORDER_MARKERS[border]); + } + } + + tty_cell(tty, &gc, &grid_default_cell, NULL, NULL); if (isolates) tty_puts(tty, START_ISOLATE); } diff --git a/screen-write.c b/screen-write.c index c09d09ab..213b533c 100644 --- a/screen-write.c +++ b/screen-write.c @@ -184,8 +184,10 @@ screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, if (ctx->init_ctx_cb != NULL) { ctx->init_ctx_cb(ctx, ttyctx); if (ttyctx->palette != NULL) { - ttyctx->defaults.fg = ttyctx->palette->fg; - ttyctx->defaults.bg = ttyctx->palette->bg; + if (ttyctx->defaults.fg == 8) + ttyctx->defaults.fg = ttyctx->palette->fg; + if (ttyctx->defaults.bg == 8) + ttyctx->defaults.bg = ttyctx->palette->bg; } } else { ttyctx->redraw_cb = screen_write_redraw_cb; @@ -645,9 +647,8 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, memcpy(&default_gc, &grid_default_cell, sizeof default_gc); - screen_write_box(ctx, menu->width + 4, menu->count + 2); - screen_write_cursormove(ctx, cx + 2, cy, 0); - format_draw(ctx, &default_gc, menu->width, menu->title, NULL); + screen_write_box(ctx, menu->width + 4, menu->count + 2, + BOX_LINES_DEFAULT, &default_gc, menu->title); for (i = 0; i < menu->count; i++) { name = menu->items[i].name; @@ -664,10 +665,12 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, if (*name == '-') { name++; default_gc.attr |= GRID_ATTR_DIM; - format_draw(ctx, gc, menu->width, name, NULL); + format_draw(ctx, gc, menu->width, name, NULL, + 0); default_gc.attr &= ~GRID_ATTR_DIM; } else - format_draw(ctx, gc, menu->width, name, NULL); + format_draw(ctx, gc, menu->width, name, NULL, + gc == choice_gc); gc = &default_gc; } } @@ -675,39 +678,95 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, screen_write_set_cursor(ctx, cx, cy); } +static void +screen_write_box_border_set(enum box_lines box_lines, int cell_type, + struct grid_cell *gc) +{ + switch (box_lines) { + case BOX_LINES_NONE: + break; + case BOX_LINES_DOUBLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); + break; + case BOX_LINES_HEAVY: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); + break; + case BOX_LINES_ROUNDED: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type)); + break; + case BOX_LINES_SIMPLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); + break; + case BOX_LINES_PADDED: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_set(&gc->data, PADDED_BORDERS[cell_type]); + break; + case BOX_LINES_SINGLE: + case BOX_LINES_DEFAULT: + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[cell_type]); + break; + } +} + /* Draw a box on screen. */ void -screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny) +screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny, + enum box_lines lines, const struct grid_cell *gcp, const char *title) { struct screen *s = ctx->s; - struct grid_cell gc; + struct grid_cell gc; u_int cx, cy, i; cx = s->cx; cy = s->cy; - memcpy(&gc, &grid_default_cell, sizeof gc); + if (gcp != NULL) + memcpy(&gc, gcp, sizeof gc); + else + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.attr |= GRID_ATTR_CHARSET; gc.flags |= GRID_FLAG_NOPALETTE; - screen_write_putc(ctx, &gc, 'l'); + /* Draw top border */ + screen_write_box_border_set(lines, CELL_TOPLEFT, &gc); + screen_write_cell(ctx, &gc); + screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); for (i = 1; i < nx - 1; i++) - screen_write_putc(ctx, &gc, 'q'); - screen_write_putc(ctx, &gc, 'k'); + screen_write_cell(ctx, &gc); + screen_write_box_border_set(lines, CELL_TOPRIGHT, &gc); + screen_write_cell(ctx, &gc); + /* Draw bottom border */ screen_write_set_cursor(ctx, cx, cy + ny - 1); - screen_write_putc(ctx, &gc, 'm'); + screen_write_box_border_set(lines, CELL_BOTTOMLEFT, &gc); + screen_write_cell(ctx, &gc); + screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); for (i = 1; i < nx - 1; i++) - screen_write_putc(ctx, &gc, 'q'); - screen_write_putc(ctx, &gc, 'j'); + screen_write_cell(ctx, &gc); + screen_write_box_border_set(lines, CELL_BOTTOMRIGHT, &gc); + screen_write_cell(ctx, &gc); + /* Draw sides */ + screen_write_box_border_set(lines, CELL_TOPBOTTOM, &gc); for (i = 1; i < ny - 1; i++) { + /* left side */ screen_write_set_cursor(ctx, cx, cy + i); - screen_write_putc(ctx, &gc, 'x'); - } - for (i = 1; i < ny - 1; i++) { + screen_write_cell(ctx, &gc); + /* right side */ screen_write_set_cursor(ctx, cx + nx - 1, cy + i); - screen_write_putc(ctx, &gc, 'x'); + screen_write_cell(ctx, &gc); + } + + if (title != NULL) { + gc.attr &= ~GRID_ATTR_CHARSET; + screen_write_cursormove(ctx, cx + 2, cy, 0); + format_draw(ctx, &gc, nx - 4, title, NULL, 0); } screen_write_set_cursor(ctx, cx, cy); @@ -1368,7 +1427,11 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) ttyctx.bg = bg; /* Scroll into history if it is enabled and clearing entire screen. */ - if (s->cx == 0 && s->cy == 0 && (gd->flags & GRID_HISTORY)) + if (s->cx == 0 && + s->cy == 0 && + (gd->flags & GRID_HISTORY) && + ctx->wp != NULL && + options_get_number(ctx->wp->options, "scroll-on-clear")) grid_view_clear_history(gd, bg); else { if (s->cx <= sx - 1) @@ -1416,7 +1479,9 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) ttyctx.bg = bg; /* Scroll into history if it is enabled. */ - if (s->grid->flags & GRID_HISTORY) + if ((s->grid->flags & GRID_HISTORY) && + ctx->wp != NULL && + options_get_number(ctx->wp->options, "scroll-on-clear")) grid_view_clear_history(s->grid, bg); else grid_view_clear(s->grid, 0, 0, sx, sy, bg); @@ -2020,12 +2085,14 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc, /* Set external clipboard. */ void -screen_write_setselection(struct screen_write_ctx *ctx, u_char *str, u_int len) +screen_write_setselection(struct screen_write_ctx *ctx, const char *flags, + u_char *str, u_int len) { struct tty_ctx ttyctx; screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; + ttyctx.ptr2 = (void *)flags; ttyctx.num = len; tty_write(tty_cmd_setselection, &ttyctx); @@ -81,11 +81,15 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->path = NULL; s->cstyle = SCREEN_CURSOR_DEFAULT; - s->ccolour = xstrdup(""); + s->default_cstyle = SCREEN_CURSOR_DEFAULT; + s->default_mode = 0; + s->ccolour = -1; + s->default_ccolour = -1; s->tabs = NULL; s->sel = NULL; s->write_list = NULL; + s->hyperlinks = NULL; screen_reinit(s); } @@ -100,7 +104,7 @@ screen_reinit(struct screen *s) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - s->mode = MODE_CURSOR|MODE_WRAP; + s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF); if (options_get_number(global_options, "extended-keys") == 2) s->mode |= MODE_KEXTENDED; @@ -115,6 +119,17 @@ screen_reinit(struct screen *s) screen_clear_selection(s); screen_free_titles(s); + screen_reset_hyperlinks(s); +} + +/* Reset hyperlinks of a screen. */ +void +screen_reset_hyperlinks(struct screen *s) +{ + if (s->hyperlinks == NULL) + s->hyperlinks = hyperlinks_init(); + else + hyperlinks_reset(s->hyperlinks); } /* Destroy a screen. */ @@ -125,7 +140,6 @@ screen_free(struct screen *s) free(s->tabs); free(s->path); free(s->title); - free(s->ccolour); if (s->write_list != NULL) screen_write_free_list(s); @@ -134,6 +148,8 @@ screen_free(struct screen *s) grid_destroy(s->saved_grid); grid_destroy(s->grid); + if (s->hyperlinks != NULL) + hyperlinks_free(s->hyperlinks); screen_free_titles(s); } @@ -151,48 +167,47 @@ screen_reset_tabs(struct screen *s) bit_set(s->tabs, i); } -/* Set screen cursor style. */ +/* Set screen cursor style and mode. */ void -screen_set_cursor_style(struct screen *s, u_int style) +screen_set_cursor_style(u_int style, enum screen_cursor_style *cstyle, + int *mode) { - log_debug("%s: new %u, was %u", __func__, style, s->cstyle); switch (style) { case 0: - s->cstyle = SCREEN_CURSOR_DEFAULT; + *cstyle = SCREEN_CURSOR_DEFAULT; break; case 1: - s->cstyle = SCREEN_CURSOR_BLOCK; - s->mode |= MODE_CURSOR_BLINKING; + *cstyle = SCREEN_CURSOR_BLOCK; + *mode |= MODE_CURSOR_BLINKING; break; case 2: - s->cstyle = SCREEN_CURSOR_BLOCK; - s->mode &= ~MODE_CURSOR_BLINKING; + *cstyle = SCREEN_CURSOR_BLOCK; + *mode &= ~MODE_CURSOR_BLINKING; break; case 3: - s->cstyle = SCREEN_CURSOR_UNDERLINE; - s->mode |= MODE_CURSOR_BLINKING; + *cstyle = SCREEN_CURSOR_UNDERLINE; + *mode |= MODE_CURSOR_BLINKING; break; case 4: - s->cstyle = SCREEN_CURSOR_UNDERLINE; - s->mode &= ~MODE_CURSOR_BLINKING; + *cstyle = SCREEN_CURSOR_UNDERLINE; + *mode &= ~MODE_CURSOR_BLINKING; break; case 5: - s->cstyle = SCREEN_CURSOR_BAR; - s->mode |= MODE_CURSOR_BLINKING; + *cstyle = SCREEN_CURSOR_BAR; + *mode |= MODE_CURSOR_BLINKING; break; case 6: - s->cstyle = SCREEN_CURSOR_BAR; - s->mode &= ~MODE_CURSOR_BLINKING; + *cstyle = SCREEN_CURSOR_BAR; + *mode &= ~MODE_CURSOR_BLINKING; break; } } /* Set screen cursor colour. */ void -screen_set_cursor_colour(struct screen *s, const char *colour) +screen_set_cursor_colour(struct screen *s, int colour) { - free(s->ccolour); - s->ccolour = xstrdup(colour); + s->ccolour = colour; } /* Set screen title. */ @@ -660,9 +675,9 @@ screen_mode_to_string(int mode) static char tmp[1024]; if (mode == 0) - return "NONE"; + return ("NONE"); if (mode == ALL_MODES) - return "ALL"; + return ("ALL"); *tmp = '\0'; if (mode & MODE_CURSOR) diff --git a/server-acl.c b/server-acl.c new file mode 100644 index 00000000..26f2490d --- /dev/null +++ b/server-acl.c @@ -0,0 +1,186 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Holland Schutte, Jayson Morberg + * Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "tmux.h" + +struct server_acl_user { + uid_t uid; + + int flags; +#define SERVER_ACL_READONLY 0x1 + + RB_ENTRY(server_acl_user) entry; +}; + +static int +server_acl_cmp(struct server_acl_user *user1, struct server_acl_user *user2) +{ + if (user1->uid < user2->uid) + return (-1); + return (user1->uid > user2->uid); +} + +RB_HEAD(server_acl_entries, server_acl_user) server_acl_entries; +RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp); + +/* Initialize server_acl tree. */ +void +server_acl_init(void) +{ + RB_INIT(&server_acl_entries); + + if (getuid() != 0) + server_acl_user_allow(0); + server_acl_user_allow(getuid()); +} + +/* Find user entry. */ +struct server_acl_user* +server_acl_user_find(uid_t uid) +{ + struct server_acl_user find = { .uid = uid }; + + return (RB_FIND(server_acl_entries, &server_acl_entries, &find)); +} + +/* Display the tree. */ +void +server_acl_display(struct cmdq_item *item) +{ + struct server_acl_user *loop; + struct passwd *pw; + const char *name; + + RB_FOREACH(loop, server_acl_entries, &server_acl_entries) { + if (loop->uid == 0) + continue; + if ((pw = getpwuid(loop->uid)) != NULL) + name = pw->pw_name; + else + name = "unknown"; + if (loop->flags == SERVER_ACL_READONLY) + cmdq_print(item, "%s (R)", name); + else + cmdq_print(item, "%s (W)", name); + } +} + +/* Allow a user. */ +void +server_acl_user_allow(uid_t uid) +{ + struct server_acl_user *user; + + user = server_acl_user_find(uid); + if (user == NULL) { + user = xcalloc(1, sizeof *user); + user->uid = uid; + RB_INSERT(server_acl_entries, &server_acl_entries, user); + } +} + +/* Deny a user (remove from the tree). */ +void +server_acl_user_deny(uid_t uid) +{ + struct server_acl_user *user; + + user = server_acl_user_find(uid); + if (user != NULL) { + RB_REMOVE(server_acl_entries, &server_acl_entries, user); + free(user); + } +} + +/* Allow this user write access. */ +void +server_acl_user_allow_write(uid_t uid) +{ + struct server_acl_user *user; + struct client *c; + + user = server_acl_user_find(uid); + if (user == NULL) + return; + user->flags &= ~SERVER_ACL_READONLY; + + TAILQ_FOREACH(c, &clients, entry) { + uid = proc_get_peer_uid(c->peer); + if (uid != (uid_t)-1 && uid == user->uid) + c->flags &= ~CLIENT_READONLY; + } +} + +/* Deny this user write access. */ +void +server_acl_user_deny_write(uid_t uid) +{ + struct server_acl_user *user; + struct client *c; + + user = server_acl_user_find(uid); + if (user == NULL) + return; + user->flags |= SERVER_ACL_READONLY; + + TAILQ_FOREACH(c, &clients, entry) { + uid = proc_get_peer_uid(c->peer); + if (uid != (uid_t)-1 && uid == user->uid) + c->flags |= CLIENT_READONLY; + } +} + +/* + * Check if the client's UID exists in the ACL list and if so, set as read only + * if needed. Return false if the user does not exist. + */ +int +server_acl_join(struct client *c) +{ + struct server_acl_user *user; + uid_t uid; + + uid = proc_get_peer_uid(c->peer); + if (uid == (uid_t)-1) + return (0); + + user = server_acl_user_find(uid); + if (user == NULL) + return (0); + if (user->flags & SERVER_ACL_READONLY) + c->flags |= CLIENT_READONLY; + return (1); +} + +/* Get UID for user entry. */ +uid_t +server_acl_get_uid(struct server_acl_user *user) +{ + return (user->uid); +} diff --git a/server-client.c b/server-client.c index d8cbfdbf..c5591055 100644 --- a/server-client.c +++ b/server-client.c @@ -40,6 +40,7 @@ static void server_client_check_exit(struct client *); static void server_client_check_redraw(struct client *); static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); +static void server_client_set_path(struct client *); static void server_client_reset_state(struct client *); static int server_client_assume_paste(struct session *); static void server_client_update_latest(struct client *); @@ -144,6 +145,54 @@ server_client_clear_overlay(struct client *c) server_redraw_client(c); } +/* + * Given overlay position and dimensions, return parts of the input range which + * are visible. + */ +void +server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, + u_int py, u_int nx, struct overlay_ranges *r) +{ + u_int ox, onx; + + /* Return up to 2 ranges. */ + r->px[2] = 0; + r->nx[2] = 0; + + /* Trivial case of no overlap in the y direction. */ + if (py < y || py > y + sy - 1) { + r->px[0] = px; + r->nx[0] = nx; + r->px[1] = 0; + r->nx[1] = 0; + return; + } + + /* Visible bit to the left of the popup. */ + if (px < x) { + r->px[0] = px; + r->nx[0] = x - px; + if (r->nx[0] > nx) + r->nx[0] = nx; + } else { + r->px[0] = 0; + r->nx[0] = 0; + } + + /* Visible bit to the right of the popup. */ + ox = x + sx; + if (px > ox) + ox = px; + onx = px + nx; + if (onx > ox) { + r->px[1] = ox; + r->nx[1] = onx - ox; + } else { + r->px[1] = 0; + r->nx[1] = 0; + } +} + /* Check if this client is inside this server. */ int server_client_check_nested(struct client *c) @@ -374,6 +423,7 @@ server_client_lost(struct client *c) if (c->flags & CLIENT_TERMINAL) tty_free(&c->tty); free(c->ttyname); + free(c->clipboard_panes); free(c->term_name); free(c->term_type); @@ -465,7 +515,7 @@ server_client_detach(struct client *c, enum msgtype msgtype) { struct session *s = c->session; - if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) + if (s == NULL || (c->flags & CLIENT_NODETACHFLAGS)) return; c->flags |= CLIENT_EXIT; @@ -545,11 +595,11 @@ server_client_check_mouse(struct client *c, struct key_event *event) log_debug("double-click at %u,%u", x, y); } else if ((m->sgr_type != ' ' && MOUSE_DRAG(m->sgr_b) && - MOUSE_BUTTONS(m->sgr_b) == 3) || + MOUSE_RELEASE(m->sgr_b)) || (m->sgr_type == ' ' && MOUSE_DRAG(m->b) && - MOUSE_BUTTONS(m->b) == 3 && - MOUSE_BUTTONS(m->lb) == 3)) { + MOUSE_RELEASE(m->b) && + MOUSE_RELEASE(m->lb))) { type = MOVE; x = m->x, y = m->y, b = 0; log_debug("move at %u,%u", x, y); @@ -702,7 +752,7 @@ have_event: m->wp = -1; /* Stop dragging if needed. */ - if (type != DRAG && type != WHEEL && c->tty.mouse_drag_flag) { + if (type != DRAG && type != WHEEL && c->tty.mouse_drag_flag != 0) { if (c->tty.mouse_drag_release != NULL) c->tty.mouse_drag_release(c, m); @@ -713,8 +763,8 @@ have_event: * End a mouse drag by passing a MouseDragEnd key corresponding * to the button that started the drag. */ - switch (c->tty.mouse_drag_flag) { - case 1: + switch (c->tty.mouse_drag_flag - 1) { + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_MOUSEDRAGEND1_PANE; if (where == STATUS) @@ -728,7 +778,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDRAGEND1_BORDER; break; - case 2: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_MOUSEDRAGEND2_PANE; if (where == STATUS) @@ -742,7 +792,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDRAGEND2_BORDER; break; - case 3: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_MOUSEDRAGEND3_PANE; if (where == STATUS) @@ -756,6 +806,90 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDRAGEND3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_MOUSEDRAGEND6_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAGEND6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAGEND6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_MOUSEDRAGEND7_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAGEND7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAGEND7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_MOUSEDRAGEND8_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAGEND8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAGEND8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_MOUSEDRAGEND9_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAGEND9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAGEND9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_MOUSEDRAGEND10_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAGEND10_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND10_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND10_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND10_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAGEND10_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_MOUSEDRAGEND11_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAGEND11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAGEND11_BORDER; + break; default: key = KEYC_MOUSE; break; @@ -788,7 +922,7 @@ have_event: key = KEYC_DRAGGING; else { switch (MOUSE_BUTTONS(b)) { - case 0: + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_MOUSEDRAG1_PANE; if (where == STATUS) @@ -802,7 +936,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDRAG1_BORDER; break; - case 1: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_MOUSEDRAG2_PANE; if (where == STATUS) @@ -816,7 +950,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDRAG2_BORDER; break; - case 2: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_MOUSEDRAG3_PANE; if (where == STATUS) @@ -830,6 +964,90 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDRAG3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_MOUSEDRAG6_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAG6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAG6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_MOUSEDRAG7_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAG7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAG7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_MOUSEDRAG8_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAG8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAG8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_MOUSEDRAG9_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAG9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAG9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_MOUSEDRAG10_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAG10_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG10_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG10_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG10_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAG10_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_MOUSEDRAG11_PANE; + if (where == STATUS) + key = KEYC_MOUSEDRAG11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDRAG11_BORDER; + break; } } @@ -870,7 +1088,7 @@ have_event: break; case UP: switch (MOUSE_BUTTONS(b)) { - case 0: + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_MOUSEUP1_PANE; if (where == STATUS) @@ -884,7 +1102,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEUP1_BORDER; break; - case 1: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_MOUSEUP2_PANE; if (where == STATUS) @@ -898,7 +1116,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEUP2_BORDER; break; - case 2: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_MOUSEUP3_PANE; if (where == STATUS) @@ -912,11 +1130,95 @@ have_event: if (where == BORDER) key = KEYC_MOUSEUP3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_MOUSEUP6_PANE; + if (where == STATUS) + key = KEYC_MOUSEUP6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEUP6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_MOUSEUP7_PANE; + if (where == STATUS) + key = KEYC_MOUSEUP7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEUP7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_MOUSEUP8_PANE; + if (where == STATUS) + key = KEYC_MOUSEUP8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEUP8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_MOUSEUP9_PANE; + if (where == STATUS) + key = KEYC_MOUSEUP9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEUP9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_MOUSEUP1_PANE; + if (where == STATUS) + key = KEYC_MOUSEUP1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP1_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEUP1_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_MOUSEUP11_PANE; + if (where == STATUS) + key = KEYC_MOUSEUP11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEUP11_BORDER; + break; } break; case DOWN: switch (MOUSE_BUTTONS(b)) { - case 0: + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_MOUSEDOWN1_PANE; if (where == STATUS) @@ -930,7 +1232,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDOWN1_BORDER; break; - case 1: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_MOUSEDOWN2_PANE; if (where == STATUS) @@ -944,7 +1246,7 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDOWN2_BORDER; break; - case 2: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_MOUSEDOWN3_PANE; if (where == STATUS) @@ -958,11 +1260,95 @@ have_event: if (where == BORDER) key = KEYC_MOUSEDOWN3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_MOUSEDOWN6_PANE; + if (where == STATUS) + key = KEYC_MOUSEDOWN6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDOWN6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_MOUSEDOWN7_PANE; + if (where == STATUS) + key = KEYC_MOUSEDOWN7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDOWN7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_MOUSEDOWN8_PANE; + if (where == STATUS) + key = KEYC_MOUSEDOWN8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDOWN8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_MOUSEDOWN9_PANE; + if (where == STATUS) + key = KEYC_MOUSEDOWN9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDOWN9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_MOUSEDOWN10_PANE; + if (where == STATUS) + key = KEYC_MOUSEDOWN10_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN10_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN10_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN10_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDOWN10_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_MOUSEDOWN11_PANE; + if (where == STATUS) + key = KEYC_MOUSEDOWN11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_MOUSEDOWN11_BORDER; + break; } break; case SECOND: switch (MOUSE_BUTTONS(b)) { - case 0: + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_SECONDCLICK1_PANE; if (where == STATUS) @@ -976,7 +1362,7 @@ have_event: if (where == BORDER) key = KEYC_SECONDCLICK1_BORDER; break; - case 1: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_SECONDCLICK2_PANE; if (where == STATUS) @@ -990,7 +1376,7 @@ have_event: if (where == BORDER) key = KEYC_SECONDCLICK2_BORDER; break; - case 2: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_SECONDCLICK3_PANE; if (where == STATUS) @@ -1004,11 +1390,95 @@ have_event: if (where == BORDER) key = KEYC_SECONDCLICK3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_SECONDCLICK6_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_SECONDCLICK7_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_SECONDCLICK8_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_SECONDCLICK9_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_SECONDCLICK10_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK10_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK10_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK10_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK10_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK10_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_SECONDCLICK11_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK11_BORDER; + break; } break; case DOUBLE: switch (MOUSE_BUTTONS(b)) { - case 0: + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_DOUBLECLICK1_PANE; if (where == STATUS) @@ -1022,7 +1492,7 @@ have_event: if (where == BORDER) key = KEYC_DOUBLECLICK1_BORDER; break; - case 1: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_DOUBLECLICK2_PANE; if (where == STATUS) @@ -1036,7 +1506,7 @@ have_event: if (where == BORDER) key = KEYC_DOUBLECLICK2_BORDER; break; - case 2: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_DOUBLECLICK3_PANE; if (where == STATUS) @@ -1050,11 +1520,95 @@ have_event: if (where == BORDER) key = KEYC_DOUBLECLICK3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_DOUBLECLICK6_PANE; + if (where == STATUS) + key = KEYC_DOUBLECLICK6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_DOUBLECLICK6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_DOUBLECLICK7_PANE; + if (where == STATUS) + key = KEYC_DOUBLECLICK7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_DOUBLECLICK7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_DOUBLECLICK8_PANE; + if (where == STATUS) + key = KEYC_DOUBLECLICK8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_DOUBLECLICK8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_DOUBLECLICK9_PANE; + if (where == STATUS) + key = KEYC_DOUBLECLICK9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_DOUBLECLICK9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_DOUBLECLICK10_PANE; + if (where == STATUS) + key = KEYC_DOUBLECLICK10_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK10_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK10_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK10_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_DOUBLECLICK10_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_DOUBLECLICK11_PANE; + if (where == STATUS) + key = KEYC_DOUBLECLICK11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_DOUBLECLICK11_BORDER; + break; } break; case TRIPLE: switch (MOUSE_BUTTONS(b)) { - case 0: + case MOUSE_BUTTON_1: if (where == PANE) key = KEYC_TRIPLECLICK1_PANE; if (where == STATUS) @@ -1068,7 +1622,7 @@ have_event: if (where == BORDER) key = KEYC_TRIPLECLICK1_BORDER; break; - case 1: + case MOUSE_BUTTON_2: if (where == PANE) key = KEYC_TRIPLECLICK2_PANE; if (where == STATUS) @@ -1082,7 +1636,7 @@ have_event: if (where == BORDER) key = KEYC_TRIPLECLICK2_BORDER; break; - case 2: + case MOUSE_BUTTON_3: if (where == PANE) key = KEYC_TRIPLECLICK3_PANE; if (where == STATUS) @@ -1096,6 +1650,90 @@ have_event: if (where == BORDER) key = KEYC_TRIPLECLICK3_BORDER; break; + case MOUSE_BUTTON_6: + if (where == PANE) + key = KEYC_TRIPLECLICK6_PANE; + if (where == STATUS) + key = KEYC_TRIPLECLICK6_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK6_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK6_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK6_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_TRIPLECLICK6_BORDER; + break; + case MOUSE_BUTTON_7: + if (where == PANE) + key = KEYC_TRIPLECLICK7_PANE; + if (where == STATUS) + key = KEYC_TRIPLECLICK7_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK7_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK7_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK7_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_TRIPLECLICK7_BORDER; + break; + case MOUSE_BUTTON_8: + if (where == PANE) + key = KEYC_TRIPLECLICK8_PANE; + if (where == STATUS) + key = KEYC_TRIPLECLICK8_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK8_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK8_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK8_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_TRIPLECLICK8_BORDER; + break; + case MOUSE_BUTTON_9: + if (where == PANE) + key = KEYC_TRIPLECLICK9_PANE; + if (where == STATUS) + key = KEYC_TRIPLECLICK9_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK9_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK9_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK9_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_TRIPLECLICK9_BORDER; + break; + case MOUSE_BUTTON_10: + if (where == PANE) + key = KEYC_TRIPLECLICK10_PANE; + if (where == STATUS) + key = KEYC_TRIPLECLICK10_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK10_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK10_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK10_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_TRIPLECLICK10_BORDER; + break; + case MOUSE_BUTTON_11: + if (where == PANE) + key = KEYC_TRIPLECLICK11_PANE; + if (where == STATUS) + key = KEYC_TRIPLECLICK11_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK11_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK11_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK11_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_TRIPLECLICK11_BORDER; + break; } break; } @@ -1580,7 +2218,8 @@ server_client_check_pane_buffer(struct window_pane *wp) } wpo = control_pane_offset(c, wp, &flag); if (wpo == NULL) { - off = 0; + if (!flag) + off = 0; continue; } if (!flag) @@ -1655,7 +2294,7 @@ server_client_reset_state(struct client *c) struct window_pane *wp = server_client_get_pane(c), *loop; struct screen *s = NULL; struct options *oo = c->session->options; - int mode = 0, cursor, flags; + int mode = 0, cursor, flags, n; u_int cx = 0, cy = 0, ox, oy, sx, sy; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) @@ -1683,7 +2322,20 @@ server_client_reset_state(struct client *c) tty_margin_off(tty); /* Move cursor to pane cursor and offset. */ - if (c->overlay_draw == NULL) { + if (c->prompt_string != NULL) { + n = options_get_number(c->session->options, "status-position"); + if (n == 0) + cy = 0; + else { + n = status_line_size(c); + if (n == 0) + cy = tty->sy - 1; + else + cy = tty->sy - n; + } + cx = c->prompt_cursor; + mode &= ~MODE_CURSOR; + } else if (c->overlay_draw == NULL) { cursor = 0; tty_window_offset(tty, &ox, &oy, &sx, &sy); if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && @@ -1950,8 +2602,10 @@ server_client_check_redraw(struct client *c) } if (c->flags & CLIENT_ALLREDRAWFLAGS) { - if (options_get_number(s->options, "set-titles")) + if (options_get_number(s->options, "set-titles")) { server_client_set_title(c); + server_client_set_path(c); + } screen_redraw_screen(c); } @@ -1997,6 +2651,26 @@ server_client_set_title(struct client *c) format_free(ft); } +/* Set client path. */ +static void +server_client_set_path(struct client *c) +{ + struct session *s = c->session; + const char *path; + + if (s->curw == NULL) + return; + if (s->curw->window->active->base.path == NULL) + path = ""; + else + path = s->curw->window->active->base.path; + if (c->path == NULL || strcmp(path, c->path) != 0) { + free(c->path); + c->path = xstrdup(path); + tty_set_path(&c->tty, c->path); + } +} + /* Dispatch message from client. */ static void server_client_dispatch(struct imsg *imsg, void *arg) @@ -2099,6 +2773,14 @@ server_client_dispatch(struct imsg *imsg, void *arg) } } +/* Callback when command is not allowed. */ +static enum cmd_retval +server_client_read_only(struct cmdq_item *item, __unused void *data) +{ + cmdq_error(item, "client is read-only"); + return (CMD_RETURN_ERROR); +} + /* Callback when command is done. */ static enum cmd_retval server_client_command_done(struct cmdq_item *item, __unused void *data) @@ -2107,8 +2789,11 @@ server_client_command_done(struct cmdq_item *item, __unused void *data) if (~c->flags & CLIENT_ATTACHED) c->flags |= CLIENT_EXIT; - else if (~c->flags & CLIENT_EXIT) + else if (~c->flags & CLIENT_EXIT) { + if (c->flags & CLIENT_CONTROL) + control_ready(c); tty_send_requests(&c->tty); + } return (CMD_RETURN_NORMAL); } @@ -2123,6 +2808,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) char **argv, *cause; struct cmd_parse_result *pr; struct args_value *values; + struct cmdq_item *new_item; if (c->flags & CLIENT_EXIT) return; @@ -2161,7 +2847,12 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) free(values); cmd_free_argv(argc, argv); - cmdq_append(c, cmdq_get_command(pr->cmdlist, NULL)); + if ((c->flags & CLIENT_READONLY) && + !cmd_list_all_have(pr->cmdlist, CMD_READONLY)) + new_item = cmdq_get_callback(server_client_read_only, NULL); + else + new_item = cmdq_get_command(pr->cmdlist, NULL); + cmdq_append(c, new_item); cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); cmd_list_free(pr->cmdlist); @@ -2399,9 +3090,11 @@ server_client_set_flags(struct client *c, const char *flags) continue; log_debug("client %s set flag %s", c->name, next); - if (not) + if (not) { + if (c->flags & CLIENT_READONLY) + flag &= ~CLIENT_READONLY; c->flags &= ~flag; - else + } else c->flags |= flag; if (flag == CLIENT_CONTROL_NOOUTPUT) control_reset_offsets(c); @@ -2469,7 +3162,7 @@ server_client_add_client_window(struct client *c, u_int id) cw->window = id; RB_INSERT(client_windows, &c->windows, cw); } - return cw; + return (cw); } /* Get client active pane. */ diff --git a/server-fn.c b/server-fn.c index 92793093..2a79f3e3 100644 --- a/server-fn.c +++ b/server-fn.c @@ -310,9 +310,11 @@ server_destroy_pane(struct window_pane *wp, int notify) struct window *w = wp->window; struct screen_write_ctx ctx; struct grid_cell gc; - time_t t; - char tim[26]; int remain_on_exit; + const char *s; + char *expanded; + u_int sx = screen_size_x(&wp->base); + u_int sy = screen_size_y(&wp->base); if (wp->fd != -1) { #ifdef HAVE_UTEMPTER @@ -339,32 +341,26 @@ server_destroy_pane(struct window_pane *wp, int notify) return; wp->flags |= PANE_STATUSDRAWN; + gettimeofday(&wp->dead_time, NULL); if (notify) notify_pane("pane-died", wp); - screen_write_start_pane(&ctx, wp, &wp->base); - screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1); - screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1, 0); - screen_write_linefeed(&ctx, 1, 8); - memcpy(&gc, &grid_default_cell, sizeof gc); - - time(&t); - ctime_r(&t, tim); - tim[strcspn(tim, "\n")] = '\0'; - - if (WIFEXITED(wp->status)) { - screen_write_nputs(&ctx, -1, &gc, - "Pane is dead (status %d, %s)", - WEXITSTATUS(wp->status), - tim); - } else if (WIFSIGNALED(wp->status)) { - screen_write_nputs(&ctx, -1, &gc, - "Pane is dead (signal %s, %s)", - sig2name(WTERMSIG(wp->status)), - tim); + s = options_get_string(wp->options, "remain-on-exit-format"); + if (*s != '\0') { + screen_write_start_pane(&ctx, wp, &wp->base); + screen_write_scrollregion(&ctx, 0, sy - 1); + screen_write_cursormove(&ctx, 0, sy - 1, 0); + screen_write_linefeed(&ctx, 1, 8); + memcpy(&gc, &grid_default_cell, sizeof gc); + + expanded = format_single(NULL, s, NULL, NULL, NULL, wp); + format_draw(&ctx, &gc, sx, expanded, NULL, 0); + free(expanded); + + screen_write_stop(&ctx); } + wp->base.mode &= ~MODE_CURSOR; - screen_write_stop(&ctx); wp->flags |= PANE_REDRAW; return; } @@ -53,6 +53,8 @@ struct cmd_find_state marked_pane; static u_int message_next; struct message_list message_log; +time_t current_time; + static int server_loop(void); static void server_send_exit(void); static void server_accept(int, short, void *); @@ -100,7 +102,7 @@ server_check_marked(void) } /* Create server socket. */ -static int +int server_create_socket(int flags, char **cause) { struct sockaddr_un sa; @@ -211,10 +213,13 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, RB_INIT(&sessions); key_bindings_init(); TAILQ_INIT(&message_log); - gettimeofday(&start_time, NULL); +#ifdef HAVE_SYSTEMD + server_fd = systemd_create_socket(flags, &cause); +#else server_fd = server_create_socket(flags, &cause); +#endif if (server_fd != -1) server_update_socket(); if (~flags & CLIENT_NOFORK) @@ -230,15 +235,19 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, if (cause != NULL) { if (c != NULL) { - cmdq_append(c, cmdq_get_error(cause)); + c->exit_message = cause; c->flags |= CLIENT_EXIT; + } else { + fprintf(stderr, "%s\n", cause); + exit(1); } - free(cause); } evtimer_set(&server_ev_tidy, server_tidy_event, NULL); evtimer_add(&server_ev_tidy, &tv); + server_acl_init(); + server_add_accept(0); proc_loop(server_proc, server_loop); @@ -255,6 +264,8 @@ server_loop(void) struct client *c; u_int items; + current_time = time (NULL); + do { items = cmdq_next(NULL); TAILQ_FOREACH(c, &clients, entry) { @@ -355,9 +366,10 @@ server_update_socket(void) static void server_accept(int fd, short events, __unused void *data) { - struct sockaddr_storage sa; - socklen_t slen = sizeof sa; - int newfd; + struct sockaddr_storage sa; + socklen_t slen = sizeof sa; + int newfd; + struct client *c; server_add_accept(0); if (!(events & EV_READ)) @@ -374,11 +386,16 @@ server_accept(int fd, short events, __unused void *data) } fatal("accept failed"); } + if (server_exit) { close(newfd); return; } - server_client_create(newfd); + c = server_client_create(newfd); + if (!server_acl_join(c)) { + c->exit_message = xstrdup("access not allowed"); + c->flags |= CLIENT_EXIT; + } } /* @@ -27,7 +27,7 @@ #include "tmux.h" struct sessions sessions; -static u_int next_session_id; +u_int next_session_id; struct session_groups session_groups = RB_INITIALIZER(&session_groups); static void session_free(int, short, void *); @@ -501,7 +501,8 @@ session_set_current(struct session *s, struct winlink *wl) winlink_stack_push(&s->lastw, s->curw); s->curw = wl; if (options_get_number(global_options, "focus-events")) { - window_update_focus(old->window); + if (old != NULL) + window_update_focus(old->window); window_update_focus(wl->window); } winlink_clear_flags(wl); @@ -209,7 +209,7 @@ spawn_pane(struct spawn_context *sc, char **cause) struct window_pane *new_wp; struct environ *child; struct environ_entry *ee; - char **argv, *cp, **argvp, *argv0, *cwd; + char **argv, *cp, **argvp, *argv0, *cwd, *new_cwd; const char *cmd, *tmp; int argc; u_int idx; @@ -225,9 +225,15 @@ spawn_pane(struct spawn_context *sc, char **cause) * Work out the current working directory. If respawning, use * the pane's stored one unless specified. */ - if (sc->cwd != NULL) + if (sc->cwd != NULL) { cwd = format_single(item, sc->cwd, c, target->s, NULL, NULL); - else if (~sc->flags & SPAWN_RESPAWN) + if (*cwd != '/') { + xasprintf(&new_cwd, "%s/%s", server_client_get_cwd(c, + target->s), cwd); + free(cwd); + cwd = new_cwd; + } + } else if (~sc->flags & SPAWN_RESPAWN) cwd = xstrdup(server_client_get_cwd(c, target->s)); else cwd = NULL; @@ -335,8 +341,7 @@ spawn_pane(struct spawn_context *sc, char **cause) log_debug("%s: cmd=%s", __func__, cp); free(cp); } - if (cwd != NULL) - log_debug("%s: cwd=%s", __func__, cwd); + log_debug("%s: cwd=%s", __func__, new_wp->cwd); cmd_log_argv(new_wp->argc, new_wp->argv, "%s", __func__); environ_log(child, "%s: environment ", __func__); @@ -382,9 +387,13 @@ spawn_pane(struct spawn_context *sc, char **cause) * Child process. Change to the working directory or home if that * fails. */ - if (chdir(new_wp->cwd) != 0 && - ((tmp = find_home()) == NULL || chdir(tmp) != 0) && - chdir("/") != 0) + if (chdir(new_wp->cwd) == 0) + environ_set(child, "PWD", 0, "%s", new_wp->cwd); + else if ((tmp = find_home()) != NULL || chdir(tmp) == 0) + environ_set(child, "PWD", 0, "%s", tmp); + else if (chdir("/") == 0) + environ_set(child, "PWD", 0, "/"); + else fatal("chdir failed"); /* @@ -439,7 +439,8 @@ status_redraw(struct client *c) screen_write_cursormove(&ctx, 0, i, 0); status_free_ranges(&sle->ranges); - format_draw(&ctx, &gc, width, expanded, &sle->ranges); + format_draw(&ctx, &gc, width, expanded, &sle->ranges, + 0); free(sle->expanded); sle->expanded = expanded; @@ -562,7 +563,7 @@ status_message_redraw(struct client *c) if (c->message_ignore_styles) screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); else - format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL); + format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0); screen_write_stop(&ctx); if (grid_compare(sl->active->grid, old_screen.grid) == 0) { @@ -717,7 +718,7 @@ status_prompt_redraw(struct client *c) memcpy(&cursorgc, &gc, sizeof cursorgc); cursorgc.attr ^= GRID_ATTR_REVERSE; - start = screen_write_strlen("%s", c->prompt_string); + start = format_width(c->prompt_string); if (start > c->tty.sx) start = c->tty.sx; @@ -727,7 +728,7 @@ status_prompt_redraw(struct client *c) for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, 0, lines - 1, 0); - screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); + format_draw(&ctx, &gc, start, c->prompt_string, NULL, 0); screen_write_cursormove(&ctx, start, lines - 1, 0); left = c->tty.sx - start; @@ -747,6 +748,7 @@ status_prompt_redraw(struct client *c) offset = 0; if (pwidth > left) pwidth = left; + c->prompt_cursor = start + c->prompt_index - offset; width = 0; for (i = 0; c->prompt_buffer[i].size != 0; i++) { @@ -809,14 +811,23 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) { if (c->prompt_mode == PROMPT_ENTRY) { switch (key) { + case '\001': /* C-a */ case '\003': /* C-c */ + case '\005': /* C-e */ case '\007': /* C-g */ case '\010': /* C-h */ case '\011': /* Tab */ + case '\013': /* C-k */ + case '\016': /* C-n */ + case '\020': /* C-p */ + case '\024': /* C-t */ case '\025': /* C-u */ case '\027': /* C-w */ + case '\031': /* C-y */ case '\n': case '\r': + case KEYC_LEFT|KEYC_CTRL: + case KEYC_RIGHT|KEYC_CTRL: case KEYC_BSPACE: case KEYC_DC: case KEYC_DOWN: @@ -837,6 +848,9 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) } switch (key) { + case KEYC_BSPACE: + *new_key = KEYC_LEFT; + return (1); case 'A': case 'I': case 'C': @@ -882,7 +896,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) *new_key = 'B'|KEYC_VI; return (1); case 'd': - *new_key = '\025'; + *new_key = '\025'; /* C-u */ return (1); case 'e': *new_key = 'e'|KEYC_VI; @@ -1562,11 +1576,25 @@ status_prompt_add_history(const char *line, u_int type) status_prompt_hsize[type] = newsize; } +/* Add to completion list. */ +static void +status_prompt_add_list(char ***list, u_int *size, const char *s) +{ + u_int i; + + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], s) == 0) + return; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = xstrdup(s); +} + /* Build completion list. */ static char ** status_prompt_complete_list(u_int *size, const char *s, int at_start) { - char **list = NULL; + char **list = NULL, *tmp; const char **layout, *value, *cp; const struct cmd_entry **cmdent; const struct options_table_entry *oe; @@ -1580,15 +1608,11 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) *size = 0; for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { - if (strncmp((*cmdent)->name, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup((*cmdent)->name); - } + if (strncmp((*cmdent)->name, s, slen) == 0) + status_prompt_add_list(&list, size, (*cmdent)->name); if ((*cmdent)->alias != NULL && - strncmp((*cmdent)->alias, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup((*cmdent)->alias); - } + strncmp((*cmdent)->alias, s, slen) == 0) + status_prompt_add_list(&list, size, (*cmdent)->alias); } o = options_get_only(global_options, "command-alias"); if (o != NULL) { @@ -1601,8 +1625,9 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) if (slen > valuelen || strncmp(value, s, slen) != 0) goto next; - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrndup(value, valuelen); + xasprintf(&tmp, "%.*s", (int)valuelen, value); + status_prompt_add_list(&list, size, tmp); + free(tmp); next: a = options_array_next(a); @@ -1610,18 +1635,13 @@ status_prompt_complete_list(u_int *size, const char *s, int at_start) } if (at_start) return (list); - for (oe = options_table; oe->name != NULL; oe++) { - if (strncmp(oe->name, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(oe->name); - } + if (strncmp(oe->name, s, slen) == 0) + status_prompt_add_list(&list, size, oe->name); } for (layout = layouts; *layout != NULL; layout++) { - if (strncmp(*layout, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(*layout); - } + if (strncmp(*layout, s, slen) == 0) + status_prompt_add_list(&list, size, *layout); } return (list); } @@ -1714,7 +1734,7 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size, item.name = list[i]; item.key = '0' + (i - spm->start); item.command = NULL; - menu_add_item(menu, &item, NULL, NULL, NULL); + menu_add_item(menu, &item, NULL, c, NULL); } if (options_get_number(c->session->options, "status-position") == 0) @@ -1784,7 +1804,7 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, item.name = tmp; item.key = '0' + size - 1; item.command = NULL; - menu_add_item(menu, &item, NULL, NULL, NULL); + menu_add_item(menu, &item, NULL, c, NULL); free(tmp); if (size == height) @@ -30,7 +30,7 @@ /* Default style. */ static struct style style_default = { - { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, + { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 }, 0, 8, @@ -43,7 +43,7 @@ then later reattached. .Pp When .Nm -is started it creates a new +is started, it creates a new .Em session with a single .Em window @@ -140,7 +140,10 @@ By default, loads the system configuration file from .Pa @SYSCONFDIR@/tmux.conf , if present, then looks for a user configuration file at -.Pa ~/.tmux.conf . +.Pa ~/.tmux.conf, +.Pa $XDG_CONFIG_HOME/tmux/tmux.conf +or +.Pa ~/.config/tmux/tmux.conf . .Pp The configuration file is a set of .Nm @@ -1034,7 +1037,7 @@ The following commands are available to manage clients and sessions: .Op Fl f Ar flags .Op Fl t Ar target-session .Xc -.D1 (alias: Ic attach ) +.D1 Pq alias: Ic attach If run from outside .Nm , create a new client in the current terminal and attach it to @@ -1121,7 +1124,7 @@ option will not be applied. .Op Fl s Ar target-session .Op Fl t Ar target-client .Xc -.D1 (alias: Ic detach ) +.D1 Pq alias: Ic detach Detach the current client if bound to a key, the client specified with .Fl t , or all clients currently attached to the session specified by @@ -1143,7 +1146,7 @@ run to replace the client. .Tg has .It Ic has-session Op Fl t Ar target-session -.D1 (alias: Ic has ) +.D1 Pq alias: Ic has Report an error and exit with 1 if the specified session does not exist. If it does exist, exit with 0. .It Ic kill-server @@ -1168,7 +1171,7 @@ session. .Op Fl F Ar format .Op Fl t Ar target-session .Xc -.D1 (alias: Ic lsc ) +.D1 Pq alias: Ic lsc List all clients attached to the server. For the meaning of the .Fl F @@ -1183,7 +1186,7 @@ is specified, list only clients connected to that session. .Op Fl F Ar format .Op Ar command .Xc -.D1 (alias: Ic lscm ) +.D1 Pq alias: Ic lscm List the syntax of .Ar command or - if omitted - of all commands supported by @@ -1193,7 +1196,7 @@ or - if omitted - of all commands supported by .Op Fl F Ar format .Op Fl f Ar filter .Xc -.D1 (alias: Ic ls ) +.D1 Pq alias: Ic ls List all sessions managed by the server. .Fl F specifies the format of each line and @@ -1205,7 +1208,7 @@ See the section. .Tg lockc .It Ic lock-client Op Fl t Ar target-client -.D1 (alias: Ic lockc ) +.D1 Pq alias: Ic lockc Lock .Ar target-client , see the @@ -1213,7 +1216,7 @@ see the command. .Tg locks .It Ic lock-session Op Fl t Ar target-session -.D1 (alias: Ic locks ) +.D1 Pq alias: Ic locks Lock all clients attached to .Ar target-session . .Tg new @@ -1230,7 +1233,7 @@ Lock all clients attached to .Op Fl y Ar height .Op Ar shell-command .Xc -.D1 (alias: Ic new ) +.D1 Pq alias: Ic new Create a new session with name .Ar session-name . .Pp @@ -1338,15 +1341,16 @@ and sets an environment variable for the newly created session; it may be specified multiple times. .Tg refresh .It Xo Ic refresh-client -.Op Fl cDlLRSU +.Op Fl cDLRSU .Op Fl A Ar pane:state .Op Fl B Ar name:what:format .Op Fl C Ar size .Op Fl f Ar flags +.Op Fl l Op Ar target-pane .Op Fl t Ar target-client .Op Ar adjustment .Xc -.D1 (alias: Ic refresh ) +.D1 Pq alias: Ic refresh Refresh the current client if bound to a key, or a single client if one is given with .Fl t . @@ -1456,7 +1460,11 @@ sets a comma-separated list of client flags, see .Fl l requests the clipboard from the client using the .Xr xterm 1 -escape sequence and stores it in a new paste buffer. +escape sequence. +If +Ar target-pane +is given, the clipboard is sent (in encoded form), otherwise it is stored in a +new paste buffer. .Pp .Fl L , .Fl R , @@ -1477,15 +1485,53 @@ option. .Op Fl t Ar target-session .Ar new-name .Xc -.D1 (alias: Ic rename ) +.D1 Pq alias: Ic rename Rename the session to .Ar new-name . +.It Xo Ic server-access +.Op Fl adlrw +.Op Ar user +.Xc +Change the access or read/write permission of +.Ar user . +The user running the +.Nm +server (its owner) and the root user cannot be changed and are always +permitted access. +.Pp +.Fl a +and +.Fl d +are used to give or revoke access for the specified user. +If the user is already attached, the +.Fl d +flag causes their clients to be detached. +.Pp +.Fl r +and +.Fl w +change the permissions for +.Ar user : +.Fl r +makes their clients read-only and +.Fl w +writable. +.Fl l +lists current access permissions. +.Pp +By default, the access list is empty and +.Nm +creates sockets with file system permissions preventing access by any user +other than the owner (and root). +These permissions must be changed manually. +Great care should be taken not to allow access to untrusted users even +read-only. .Tg showmsgs .It Xo Ic show-messages .Op Fl JT .Op Fl t Ar target-client .Xc -.D1 (alias: Ic showmsgs ) +.D1 Pq alias: Ic showmsgs Show server messages or information. Messages are stored, up to a maximum of the limit set by the .Ar message-limit @@ -1500,7 +1546,7 @@ show debugging information about jobs and terminals. .Ar path .Ar ... .Xc -.D1 (alias: Ic source ) +.D1 Pq alias: Ic source Execute commands from one or more files specified by .Ar path (which may be @@ -1523,7 +1569,7 @@ the file is parsed but no commands are executed. shows the parsed commands and line numbers if possible. .Tg start .It Ic start-server -.D1 (alias: Ic start ) +.D1 Pq alias: Ic start Start the .Nm server, if not already running, without creating any sessions. @@ -1542,7 +1588,7 @@ $ tmux start \\; show -g .It Xo Ic suspend-client .Op Fl t Ar target-client .Xc -.D1 (alias: Ic suspendc ) +.D1 Pq alias: Ic suspendc Suspend a client by sending .Dv SIGTSTP (tty stop). @@ -1553,7 +1599,7 @@ Suspend a client by sending .Op Fl t Ar target-session .Op Fl T Ar key-table .Xc -.D1 (alias: Ic switchc ) +.D1 Pq alias: Ic switchc Switch the current session for client .Ar target-client to @@ -1773,6 +1819,7 @@ The following commands are supported in copy mode: .It Li "set-mark" Ta "X" Ta "X" .It Li "start-of-line" Ta "0" Ta "C-a" .It Li "stop-selection" Ta "" Ta "" +.It Li "toggle-position" Ta "P" Ta "P" .It Li "top-line" Ta "H" Ta "M-R" .El .Pp @@ -1945,7 +1992,7 @@ Commands related to windows and panes are as follows: .Op Fl s Ar src-pane .Op Fl t Ar dst-window .Xc -.D1 (alias: Ic breakp ) +.D1 Pq alias: Ic breakp Break .Ar src-pane off from its containing window to make it the only pane in @@ -1974,7 +2021,7 @@ but a different format may be specified with .Op Fl S Ar start-line .Op Fl t Ar target-pane .Xc -.D1 (alias: Ic capturep ) +.D1 Pq alias: Ic capturep Capture the contents of a pane. If .Fl p @@ -2068,9 +2115,11 @@ is not given, "detach-client -t '%%'" is used. specifies the initial sort field: one of .Ql name , .Ql size , -.Ql creation , +.Ql creation +(time), or -.Ql activity . +.Ql activity +(time). .Fl r reverses the sort order. .Fl f @@ -2152,7 +2201,8 @@ specifies the initial sort field: one of .Ql index , .Ql name , or -.Ql time . +.Ql time +(activity). .Fl r reverses the sort order. .Fl f @@ -2226,7 +2276,7 @@ This command works only if at least one client is attached. .Op Fl t Ar target-client .Op Ar template .Xc -.D1 (alias: Ic displayp ) +.D1 Pq alias: Ic displayp Display a visible indicator of each pane shown by .Ar target-client . See the @@ -2266,7 +2316,7 @@ other commands are not blocked from running until the indicator is closed. .Op Fl t Ar target-pane .Ar match-string .Xc -.D1 (alias: Ic findw ) +.D1 Pq alias: Ic findw Search for a .Xr fnmatch 3 pattern or, with @@ -2296,7 +2346,7 @@ This command works only if at least one client is attached. .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc -.D1 (alias: Ic joinp ) +.D1 Pq alias: Ic joinp Like .Ic split-window , but instead of splitting @@ -2324,7 +2374,7 @@ the marked pane is used rather than the current pane. .Op Fl a .Op Fl t Ar target-pane .Xc -.D1 (alias: Ic killp ) +.D1 Pq alias: Ic killp Destroy the given pane. If no panes remain in the containing window, it is also destroyed. The @@ -2336,7 +2386,7 @@ option kills all but the pane given with .Op Fl a .Op Fl t Ar target-window .Xc -.D1 (alias: Ic killw ) +.D1 Pq alias: Ic killw Kill the current window or the window at .Ar target-window , removing it from any sessions to which it is linked. @@ -2349,7 +2399,7 @@ option kills all but the window given with .Op Fl deZ .Op Fl t Ar target-window .Xc -.D1 (alias: Ic lastp ) +.D1 Pq alias: Ic lastp Select the last (previously selected) pane. .Fl Z keeps the window zoomed if it was zoomed. @@ -2359,7 +2409,7 @@ enables or disables input to the pane. .Tg last .It Ic last-window Op Fl t Ar target-session -.D1 (alias: Ic last ) +.D1 Pq alias: Ic last Select the last (previously selected) window. If no .Ar target-session @@ -2370,7 +2420,7 @@ is specified, select the last window of the current session. .Op Fl s Ar src-window .Op Fl t Ar dst-window .Xc -.D1 (alias: Ic linkw ) +.D1 Pq alias: Ic linkw Link the window at .Ar src-window to the specified @@ -2402,7 +2452,7 @@ is given, the newly linked window is not selected. .Op Fl f Ar filter .Op Fl t Ar target .Xc -.D1 (alias: Ic lsp ) +.D1 Pq alias: Ic lsp If .Fl a is given, @@ -2431,7 +2481,7 @@ section. .Op Fl f Ar filter .Op Fl t Ar target-session .Xc -.D1 (alias: Ic lsw ) +.D1 Pq alias: Ic lsw If .Fl a is given, list all windows on the server. @@ -2452,7 +2502,7 @@ section. .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc -.D1 (alias: Ic movep ) +.D1 Pq alias: Ic movep Does the same as .Ic join-pane . .Tg movew @@ -2461,7 +2511,7 @@ Does the same as .Op Fl s Ar src-window .Op Fl t Ar dst-window .Xc -.D1 (alias: Ic movew ) +.D1 Pq alias: Ic movew This is similar to .Ic link-window , except the window at @@ -2484,7 +2534,7 @@ option. .Op Fl t Ar target-window .Op Ar shell-command .Xc -.D1 (alias: Ic neww ) +.D1 Pq alias: Ic neww Create a new window. With .Fl a @@ -2559,14 +2609,14 @@ but a different format may be specified with .Fl F . .Tg nextl .It Ic next-layout Op Fl t Ar target-window -.D1 (alias: Ic nextl ) +.D1 Pq alias: Ic nextl Move a window to the next layout and rearrange the panes to fit. .Tg next .It Xo Ic next-window .Op Fl a .Op Fl t Ar target-session .Xc -.D1 (alias: Ic next ) +.D1 Pq alias: Ic next Move to the next window in the session. If .Fl a @@ -2577,7 +2627,7 @@ is used, move to the next window with an alert. .Op Fl t Ar target-pane .Op Ar shell-command .Xc -.D1 (alias: Ic pipep ) +.D1 Pq alias: Ic pipep Pipe output sent by the program in .Ar target-pane to a shell command or vice versa. @@ -2624,14 +2674,14 @@ bind-key C-p pipe-pane -o 'cat >>~/output.#I-#P' .It Xo Ic previous-layout .Op Fl t Ar target-window .Xc -.D1 (alias: Ic prevl ) +.D1 Pq alias: Ic prevl Move to the previous layout in the session. .Tg prev .It Xo Ic previous-window .Op Fl a .Op Fl t Ar target-session .Xc -.D1 (alias: Ic prev ) +.D1 Pq alias: Ic prev Move to the previous window in the session. With .Fl a , @@ -2641,7 +2691,7 @@ move to the previous window with an alert. .Op Fl t Ar target-window .Ar new-name .Xc -.D1 (alias: Ic renamew ) +.D1 Pq alias: Ic renamew Rename the current window, or the window at .Ar target-window if specified, to @@ -2654,7 +2704,7 @@ if specified, to .Op Fl y Ar height .Op Ar adjustment .Xc -.D1 (alias: Ic resizep ) +.D1 Pq alias: Ic resizep Resize a pane, up, down, left or right by .Ar adjustment with @@ -2699,7 +2749,7 @@ history to replace them. .Op Fl y Ar height .Op Ar adjustment .Xc -.D1 (alias: Ic resizew ) +.D1 Pq alias: Ic resizew Resize a window, up, down, left or right by .Ar adjustment with @@ -2732,7 +2782,7 @@ to manual in the window options. .Op Fl t Ar target-pane .Op Ar shell-command .Xc -.D1 (alias: Ic respawnp ) +.D1 Pq alias: Ic respawnp Reactivate a pane in which the command has exited (see the .Ic remain-on-exit window option). @@ -2758,7 +2808,7 @@ command. .Op Fl t Ar target-window .Op Ar shell-command .Xc -.D1 (alias: Ic respawnw ) +.D1 Pq alias: Ic respawnw Reactivate a window in which the command has exited (see the .Ic remain-on-exit window option). @@ -2781,7 +2831,7 @@ command. .Op Fl DUZ .Op Fl t Ar target-window .Xc -.D1 (alias: Ic rotatew ) +.D1 Pq alias: Ic rotatew Rotate the positions of the panes within a window, either upward (numerically lower) with .Fl U @@ -2794,7 +2844,7 @@ keeps the window zoomed if it was zoomed. .Op Fl t Ar target-pane .Op Ar layout-name .Xc -.D1 (alias: Ic selectl ) +.D1 Pq alias: Ic selectl Choose a specific layout for a window. If .Ar layout-name @@ -2817,7 +2867,7 @@ spreads the current pane and any panes next to it out evenly. .Op Fl T Ar title .Op Fl t Ar target-pane .Xc -.D1 (alias: Ic selectp ) +.D1 Pq alias: Ic selectp Make pane .Ar target-pane the active pane in its window. @@ -2861,7 +2911,7 @@ and .Op Fl lnpT .Op Fl t Ar target-window .Xc -.D1 (alias: Ic selectw ) +.D1 Pq alias: Ic selectw Select the window at .Ar target-window . .Fl l , @@ -2889,7 +2939,7 @@ the command behaves like .Op Ar shell-command .Op Fl F Ar format .Xc -.D1 (alias: Ic splitw ) +.D1 Pq alias: Ic splitw Create a new pane by splitting .Ar target-pane : .Fl h @@ -2946,7 +2996,7 @@ command. .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc -.D1 (alias: Ic swapp ) +.D1 Pq alias: Ic swapp Swap two panes. If .Fl U @@ -2975,7 +3025,7 @@ the marked pane is used rather than the current pane. .Op Fl s Ar src-window .Op Fl t Ar dst-window .Xc -.D1 (alias: Ic swapw ) +.D1 Pq alias: Ic swapw This is similar to .Ic link-window , except the source and destination windows are swapped. @@ -2996,7 +3046,7 @@ the window containing the marked pane is used rather than the current window. .Op Fl k .Op Fl t Ar target-window .Xc -.D1 (alias: Ic unlinkw ) +.D1 Pq alias: Ic unlinkw Unlink .Ar target-window . Unless @@ -3069,7 +3119,7 @@ Commands related to key bindings are as follows: .Op Fl T Ar key-table .Ar key command Op Ar arguments .Xc -.D1 (alias: Ic bind ) +.D1 Pq alias: Ic bind Bind key .Ar key to @@ -3127,7 +3177,7 @@ command. .Op Fl P Ar prefix-string Fl T Ar key-table .Op Ar key .Xc -.D1 (alias: Ic lsk ) +.D1 Pq alias: Ic lsk List key bindings. There are two forms: the default lists keys as .Ic bind-key @@ -3164,7 +3214,7 @@ lists the command for keys that do not have a note rather than skipping them. .Op Fl t Ar target-pane .Ar key Ar ... .Xc -.D1 (alias: Ic send ) +.D1 Pq alias: Ic send Send a key or keys to a window. Each argument .Ar key @@ -3215,7 +3265,7 @@ the secondary prefix key, to a window as if it was pressed. .Op Fl T Ar key-table .Ar key .Xc -.D1 (alias: Ic unbind ) +.D1 Pq alias: Ic unbind Unbind the command bound to .Ar key . .Fl n @@ -3311,7 +3361,7 @@ Commands which set options are as follows: .Op Fl t Ar target-pane .Ar option Ar value .Xc -.D1 (alias: Ic set ) +.D1 Pq alias: Ic set Set a pane option with .Fl p , a window option with @@ -3386,7 +3436,7 @@ the result would be the default background and a blue foreground. .Op Fl t Ar target-pane .Op Ar option .Xc -.D1 (alias: Ic show ) +.D1 Pq alias: Ic show Show the pane options (or a single option if .Ar option is provided) with @@ -3612,12 +3662,22 @@ Allows setting the cursor style. Supports extended keys. .It focus Supports focus reporting. +.It hyperlinks +Supports OSC 8 hyperlinks. +.It ignorefkeys +Ignore function keys from +.Xr terminfo 5 +and use the +.Nm +internal set only. .It margins Supports DECSLRM margins. .It mouse Supports .Xr xterm 1 mouse sequences. +.It osc7 +Supports the OSC 7 working directory extension. .It overline Supports the overline SGR attribute. .It rectfill @@ -4108,6 +4168,9 @@ Set clock colour. .Xc Set clock hour format. .Pp +.It Ic fill-character Ar character +Set the character used to fill areas of the terminal unused by a window. +.Pp .It Ic main-pane-height Ar height .It Ic main-pane-width Ar width Set the width or height of the main (left or top) pane in the @@ -4214,6 +4277,7 @@ see the .Sx STYLES section. Attributes are ignored. +.Pp .It Ic pane-base-index Ar index Like .Ic base-index , @@ -4222,6 +4286,12 @@ but set the starting index for pane numbers. .It Ic pane-border-format Ar format Set the text shown in pane border status lines. .Pp +.It Xo Ic pane-border-indicators +.Op Ic off | colour | arrows | both +.Xc +Indicate active pane by colouring only half of the border in windows with +exactly two panes, by displaying arrow markers, by drawing both or neither. +.Pp .It Ic pane-border-lines Ar type Set the type of characters used for drawing pane borders. .Ar type @@ -4258,6 +4328,50 @@ see the section. Attributes are ignored. .Pp +.It Ic popup-style Ar style +Set the popup style. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +Attributes are ignored. +.Pp +.It Ic popup-border-style Ar style +Set the popup border style. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +Attributes are ignored. +.Pp +.It Ic popup-border-lines Ar type +Set the type of characters used for drawing popup borders. +.Ar type +may be one of: +.Bl -tag -width Ds +.It single +single lines using ACS or UTF-8 characters (default) +.It rounded +variation of single with rounded corners using UTF-8 characters +.It double +double lines using UTF-8 characters +.It heavy +heavy lines using UTF-8 characters +.It simple +simple ASCII characters +.It padded +simple ASCII space character +.It none +no border +.El +.Pp +.Ql double +and +.Ql heavy +will fall back to standard ACS line drawing when UTF-8 is not supported. +.Pp .It Ic window-status-activity-style Ar style Set status line style for windows with an activity alert. For how to specify @@ -4351,6 +4465,13 @@ The default is on. Available pane options are: .Pp .Bl -tag -width Ds -compact +.It Xo Ic allow-passthrough +.Op Ic on | off +.Xc +Allow programs in the pane to bypass +.Nm +using a terminal escape sequence (\eePtmux;...\ee\e\e). +.Pp .It Xo Ic allow-rename .Op Ic on | off .Xc @@ -4371,6 +4492,9 @@ The alternate screen feature preserves the contents of the window when an interactive application starts and restores it on exit, so that any output visible before the application starts reappears unchanged after it exits. .Pp +.It Ic cursor-colour Ar colour +Set the colour of the cursor. +.Pp .It Ic pane-colours[] Ar colour The default colour palette. Each entry in the array defines the colour @@ -4378,6 +4502,17 @@ Each entry in the array defines the colour uses when the colour with that index is requested. The index may be from zero to 255. .Pp +.It Ic cursor-style Ar style +Set the style of the cursor. +Available styles are: +.Ic default , +.Ic blinking-block , +.Ic block , +.Ic blinking-underline , +.Ic underline , +.Ic blinking-bar , +.Ic bar . +.Pp .It Xo Ic remain-on-exit .Op Ic on | off | failed .Xc @@ -4390,6 +4525,17 @@ The pane may be reactivated with the .Ic respawn-pane command. .Pp +.It Ic remain-on-exit-format Ar string +Set the text shown at the bottom of exited panes when +.Ic remain-on-exit +is enabled. +.Pp +.It Xo Ic scroll-on-clear +.Op Ic on | off +.Xc +When the entire screen is cleared and this option is on, scroll the contents of +the screen into history before clearing it. +.Pp .It Xo Ic synchronize-panes .Op Ic on | off .Xc @@ -4516,6 +4662,11 @@ Run when a session is renamed. Run when a window is linked into a session. .It window-renamed Run when a window is renamed. +.It window-resized +Run when a window is resized. +This may be after the +.Ar client-resized +hook is run. .It window-unlinked Run when a window is unlinked from a session. .El @@ -4786,6 +4937,10 @@ replaces a numeric argument by its ASCII equivalent, so .Ql #{a:98} results in .Ql b . +.Ql c +replaces a +.Nm +colour by its six-digit hexadecimal RGB value. .Pp A limit may be placed on the length of the resultant string by prefixing it by an @@ -4926,7 +5081,9 @@ commands to finish; instead, the previous result from running the same command i or a placeholder if the command has not been run before. If the command hasn't exited, the most recent line of output will be used, but the status line will not be updated more than once a second. -Commands are executed with the +Commands are executed using +.Pa /bin/sh +and with the .Nm global environment set (see the .Sx GLOBAL AND SESSION ENVIRONMENT @@ -4964,12 +5121,14 @@ The following variables are available, where appropriate: .It Li "client_name" Ta "" Ta "Name of client" .It Li "client_pid" Ta "" Ta "PID of client process" .It Li "client_prefix" Ta "" Ta "1 if prefix key has been pressed" -.It Li "client_readonly" Ta "" Ta "1 if client is readonly" +.It Li "client_readonly" Ta "" Ta "1 if client is read-only" .It Li "client_session" Ta "" Ta "Name of the client's session" .It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any" .It Li "client_termname" Ta "" Ta "Terminal name of client" .It Li "client_termtype" Ta "" Ta "Terminal type of client, if available" .It Li "client_tty" Ta "" Ta "Pseudo terminal of client" +.It Li "client_uid" Ta "" Ta "UID of client process" +.It Li "client_user" Ta "" Ta "User of client process" .It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8" .It Li "client_width" Ta "" Ta "Width of client" .It Li "client_written" Ta "" Ta "Bytes written to client" @@ -5007,6 +5166,7 @@ The following variables are available, where appropriate: .It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" .It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag" .It Li "mouse_button_flag" Ta "" Ta "Pane mouse button flag" +.It Li "mouse_hyperlink" Ta "" Ta "Hyperlink under mouse, if any" .It Li "mouse_line" Ta "" Ta "Line under mouse, if any" .It Li "mouse_sgr_flag" Ta "" Ta "Pane mouse SGR flag" .It Li "mouse_standard_flag" Ta "" Ta "Pane mouse standard flag" @@ -5014,6 +5174,7 @@ The following variables are available, where appropriate: .It Li "mouse_word" Ta "" Ta "Word under mouse, if any" .It Li "mouse_x" Ta "" Ta "Mouse X position, if any" .It Li "mouse_y" Ta "" Ta "Mouse Y position, if any" +.It Li "next_session_id" Ta "" Ta "Unique session ID for next new session" .It Li "origin_flag" Ta "" Ta "Pane origin flag" .It Li "pane_active" Ta "" Ta "1 if active pane" .It Li "pane_at_bottom" Ta "" Ta "1 if pane is at the bottom of window" @@ -5025,7 +5186,9 @@ The following variables are available, where appropriate: .It Li "pane_current_command" Ta "" Ta "Current command if available" .It Li "pane_current_path" Ta "" Ta "Current path if available" .It Li "pane_dead" Ta "" Ta "1 if pane is dead" +.It Li "pane_dead_signal" Ta "" Ta "Exit signal of process in dead pane" .It Li "pane_dead_status" Ta "" Ta "Exit status of process in dead pane" +.It Li "pane_dead_time" Ta "" Ta "Exit time of process in dead pane" .It Li "pane_fg" Ta "" Ta "Pane foreground colour" .It Li "pane_format" Ta "" Ta "1 if format is for a pane" .It Li "pane_height" Ta "" Ta "Height of pane" @@ -5044,6 +5207,7 @@ The following variables are available, where appropriate: .It Li "pane_right" Ta "" Ta "Right of pane" .It Li "pane_search_string" Ta "" Ta "Last search string in copy mode" .It Li "pane_start_command" Ta "" Ta "Command pane started with" +.It Li "pane_start_path" Ta "" Ta "Path pane started with" .It Li "pane_synchronized" Ta "" Ta "1 if pane is synchronized" .It Li "pane_tabs" Ta "" Ta "Pane tab positions" .It Li "pane_title" Ta "#T" Ta "Title of pane (can be set by application)" @@ -5086,6 +5250,8 @@ The following variables are available, where appropriate: .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "start_time" Ta "" Ta "Server start time" +.It Li "uid" Ta "" Ta "Server UID" +.It Li "user" Ta "" Ta "Server user" .It Li "version" Ta "" Ta "Server version" .It Li "window_active" Ta "" Ta "1 if window active" .It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window" @@ -5100,7 +5266,6 @@ The following variables are available, where appropriate: .It Li "window_cell_width" Ta "" Ta "Width of each cell in pixels" .It Li "window_end_flag" Ta "" Ta "1 if window has the highest index" .It Li "window_flags" Ta "#F" Ta "Window flags with # escaped as ##" -.It Li "window_raw_flags" Ta "" Ta "Window flags with nothing escaped" .It Li "window_format" Ta "" Ta "1 if format is for a window" .It Li "window_height" Ta "" Ta "Height of window" .It Li "window_id" Ta "" Ta "Unique window ID" @@ -5115,6 +5280,7 @@ The following variables are available, where appropriate: .It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client" .It Li "window_offset_y" Ta "" Ta "Y offset into window if larger than client" .It Li "window_panes" Ta "" Ta "Number of panes in window" +.It Li "window_raw_flags" Ta "" Ta "Window flags with nothing escaped" .It Li "window_silence_flag" Ta "" Ta "1 if window has silence alert" .It Li "window_stack_index" Ta "" Ta "Index in session most recent stack" .It Li "window_start_flag" Ta "" Ta "1 if window has the lowest index" @@ -5363,7 +5529,7 @@ Commands to alter and view the environment are: .Op Fl t Ar target-session .Ar name Op Ar value .Xc -.D1 (alias: Ic setenv ) +.D1 Pq alias: Ic setenv Set or unset an environment variable. If .Fl g @@ -5389,7 +5555,7 @@ marks the variable as hidden. .Op Fl t Ar target-session .Op Ar variable .Xc -.D1 (alias: Ic showenv ) +.D1 Pq alias: Ic showenv Display the environment for .Ar target-session or the global environment with @@ -5468,11 +5634,11 @@ session option. .Pp Commands related to the status line are as follows: .Bl -tag -width Ds -.Tg clrphist +.Tg clearphist .It Xo Ic clear-prompt-history .Op Fl T Ar prompt-type .Xc -.D1 (alias: Ic clrphist) +.D1 Pq alias: Ic clearphist Clear status prompt history for prompt type .Ar prompt-type . If @@ -5598,7 +5764,7 @@ until it is dismissed. .Op Fl t Ar target-client .Ar command .Xc -.D1 (alias: Ic confirm ) +.D1 Pq alias: Ic confirm Ask for confirmation before executing .Ar command . If @@ -5627,7 +5793,7 @@ until it is dismissed. .Ar command .Ar ... .Xc -.D1 (alias: Ic menu ) +.D1 Pq alias: Ic menu Display a menu on .Ar target-client . .Ar target-pane @@ -5714,7 +5880,7 @@ The following keys are also available: .Op Fl t Ar target-pane .Op Ar message .Xc -.D1 (alias: Ic display ) +.D1 Pq alias: Ic display Display a message. If .Fl p @@ -5726,7 +5892,7 @@ milliseconds. If .Ar delay is not given, the -.Ic message-time +.Ic display-time option is used; a delay of zero waits for a key press. .Ql N ignores key presses and closes only after the delay expires. @@ -5751,16 +5917,21 @@ forwards any input read from stdin to the empty pane given by .Tg popup .It Xo Ic display-popup .Op Fl BCE +.Op Fl b Ar border-lines .Op Fl c Ar target-client .Op Fl d Ar start-directory +.Op Fl e Ar environment .Op Fl h Ar height +.Op Fl s Ar style +.Op Fl S Ar border-style .Op Fl t Ar target-pane +.Op Fl T Ar title .Op Fl w Ar width .Op Fl x Ar position .Op Fl y Ar position .Op Ar shell-command .Xc -.D1 (alias: Ic popup ) +.D1 Pq alias: Ic popup Display a popup running .Ar shell-command on @@ -5790,9 +5961,42 @@ and give the width and height - both may be a percentage (followed by .Ql % ) . If omitted, half of the terminal size is used. +.Pp .Fl B does not surround the popup by a border. .Pp +.Fl b +sets the type of border line for the popup. +When +.Fl B +is specified, the +.Fl b +option is ignored. +See +.Ic popup-border-lines +for possible values for +.Ar border-lines . +.Pp +.Fl s +sets the style for the popup and +.Fl S +sets the style for the popup border. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.Fl e +takes the form +.Ql VARIABLE=value +and sets an environment variable for the popup; it may be specified multiple +times. +.Pp +.Fl T +is a format for the popup title (see +.Sx FORMATS ) . +.Pp The .Fl C flag closes any popup on the client. @@ -5800,7 +6004,7 @@ flag closes any popup on the client. .It Xo Ic show-prompt-history .Op Fl T Ar prompt-type .Xc -.D1 (alias: Ic showphist) +.D1 Pq alias: Ic showphist Display status prompt history for prompt type .Ar prompt-type . If @@ -5909,7 +6113,8 @@ is not given, "paste-buffer -b '%%'" is used. .Pp .Fl O specifies the initial sort field: one of -.Ql time , +.Ql time +(creation), .Ql name or .Ql size . @@ -5927,12 +6132,17 @@ a format for each shortcut key; both are evaluated once for each line. starts without the preview. This command works only if at least one client is attached. .Tg clearhist -.It Ic clear-history Op Fl t Ar target-pane -.D1 (alias: Ic clearhist ) +.It Xo Ic clear-history +.Op Fl H +.Op Fl t Ar target-pane +.Xc +.D1 Pq alias: Ic clearhist Remove and free the history for the specified pane. +.Fl H +also removes all hyperlinks. .Tg deleteb .It Ic delete-buffer Op Fl b Ar buffer-name -.D1 (alias: Ic deleteb ) +.D1 Pq alias: Ic deleteb Delete the buffer named .Ar buffer-name , or the most recently added automatically named buffer if not specified. @@ -5941,7 +6151,7 @@ or the most recently added automatically named buffer if not specified. .Op Fl F Ar format .Op Fl f Ar filter .Xc -.D1 (alias: Ic lsb ) +.D1 Pq alias: Ic lsb List the global buffers. .Fl F specifies the format of each line and @@ -5958,7 +6168,7 @@ section. .Ar path .Xc .Tg loadb -.D1 (alias: Ic loadb ) +.D1 Pq alias: Ic loadb Load the contents of the specified paste buffer from .Ar path . If @@ -5975,7 +6185,7 @@ escape sequence, if possible. .Op Fl s Ar separator .Op Fl t Ar target-pane .Xc -.D1 (alias: Ic pasteb ) +.D1 Pq alias: Ic pasteb Insert the contents of a paste buffer into the specified pane. If not specified, paste into the current one. With @@ -5999,7 +6209,7 @@ buffer if the application has requested bracketed paste mode. .Op Fl b Ar buffer-name .Ar path .Xc -.D1 (alias: Ic saveb ) +.D1 Pq alias: Ic saveb Save the contents of the specified paste buffer to .Ar path . The @@ -6013,7 +6223,7 @@ option appends to rather than overwriting the file. .Op Fl n Ar new-buffer-name .Ar data .Xc -.D1 (alias: Ic setb ) +.D1 Pq alias: Ic setb Set the contents of the specified buffer to .Ar data . If @@ -6034,7 +6244,7 @@ option renames the buffer to .It Xo Ic show-buffer .Op Fl b Ar buffer-name .Xc -.D1 (alias: Ic showb ) +.D1 Pq alias: Ic showb Display the contents of the specified buffer. .El .Sh MISCELLANEOUS @@ -6049,11 +6259,13 @@ Display a large clock. .Ar shell-command command .Op Ar command .Xc -.D1 (alias: Ic if ) +.D1 Pq alias: Ic if Execute the first .Ar command if .Ar shell-command +(run with +.Pa /bin/sh ) returns success or the second .Ar command otherwise. @@ -6076,7 +6288,7 @@ is not executed but considered success if neither empty nor zero (after formats are expanded). .Tg lock .It Ic lock-server -.D1 (alias: Ic lock ) +.D1 Pq alias: Ic lock Lock each client individually by running the command specified by the .Ic lock-command option. @@ -6087,9 +6299,11 @@ option. .Op Fl t Ar target-pane .Op Ar shell-command .Xc -.D1 (alias: Ic run ) +.D1 Pq alias: Ic run Execute .Ar shell-command +using +.Pa /bin/sh or (with .Fl C ) a @@ -6119,7 +6333,7 @@ If the command fails, the exit status is also displayed. .Op Fl L | S | U .Ar channel .Xc -.D1 (alias: Ic wait ) +.D1 Pq alias: Ic wait When used without options, prevents the client from exiting until woken using .Ic wait-for .Fl S @@ -6191,6 +6405,11 @@ to change the cursor colour from inside .Bd -literal -offset indent $ printf '\e033]12;red\e033\e\e' .Ed +.Pp +The colour is an +.Xr X 7 +colour, see +.Xr XParseColor 3 . .It Em \&Cmg, \&Clmg, \&Dsmg , \&Enmg Set, clear, disable or enable DECSLRM margins. These are set automatically if the terminal reports it is @@ -6208,6 +6427,8 @@ Disable and enable focus reporting. These are set automatically if the .Em XT capability is present. +.It Em \&Hls +Set or clear a hyperlink annotation. .It Em \&Rect Tell .Nm @@ -6234,6 +6455,11 @@ $ printf '\e033[4 q' If .Em Se is not set, \&Ss with argument 0 will be used to reset the cursor style instead. +.It Em \&Swd +Set the opening sequence for the working directory notification. +The sequence is terminated using the standard +.Em fsl +capability. .It Em \&Sync Start (parameter is 1) or end (parameter is 2) a synchronized update. .It Em \&Tc @@ -6320,6 +6546,8 @@ The client is now attached to the session with ID .Ar session-id , which is named .Ar name . +.It Ic %config-error Ar error +An error has happened in a configuration file. .It Ic %continue Ar pane-id The pane has been continued after being paused (if the .Ar pause-after @@ -6406,6 +6634,14 @@ are for future use and should be ignored. The window with ID .Ar window-id was created but is not linked to the current session. +.It Ic %unlinked-window-close Ar window-id +The window with ID +.Ar window-id , +which is not linked to the current session, was closed. +.It Ic %unlinked-window-renamed Ar window-id +The window with ID +.Ar window-id , +which is not linked to the current session, was renamed. .It Ic %window-add Ar window-id The window with ID .Ar window-id @@ -6464,7 +6700,7 @@ are replaced with underscores For input, .Nm always runs with a UTF-8 locale. -If en_US.UTF-8 is provided by the operating system it is used and +If en_US.UTF-8 is provided by the operating system, it is used and .Ev LC_CTYPE is ignored for input. Otherwise, @@ -6513,6 +6749,8 @@ options. .Sh FILES .Bl -tag -width "@SYSCONFDIR@/tmux.confXXX" -compact .It Pa ~/.tmux.conf +.It Pa $XDG_CONFIG_HOME/tmux/tmux.conf +.It Pa ~/.config/tmux/tmux.conf Default .Nm configuration file. @@ -325,7 +325,7 @@ find_home(void) const char * getversion(void) { - return TMUX_VERSION; + return (TMUX_VERSION); } int @@ -50,6 +50,8 @@ struct control_state; struct environ; struct format_job_tree; struct format_tree; +struct hyperlinks_uri; +struct hyperlinks; struct input_ctx; struct job; struct menu_data; @@ -202,26 +204,68 @@ enum { KEYC_MOUSE_KEY(MOUSEDOWN1), KEYC_MOUSE_KEY(MOUSEDOWN2), KEYC_MOUSE_KEY(MOUSEDOWN3), + KEYC_MOUSE_KEY(MOUSEDOWN6), + KEYC_MOUSE_KEY(MOUSEDOWN7), + KEYC_MOUSE_KEY(MOUSEDOWN8), + KEYC_MOUSE_KEY(MOUSEDOWN9), + KEYC_MOUSE_KEY(MOUSEDOWN10), + KEYC_MOUSE_KEY(MOUSEDOWN11), KEYC_MOUSE_KEY(MOUSEUP1), KEYC_MOUSE_KEY(MOUSEUP2), KEYC_MOUSE_KEY(MOUSEUP3), + KEYC_MOUSE_KEY(MOUSEUP6), + KEYC_MOUSE_KEY(MOUSEUP7), + KEYC_MOUSE_KEY(MOUSEUP8), + KEYC_MOUSE_KEY(MOUSEUP9), + KEYC_MOUSE_KEY(MOUSEUP10), + KEYC_MOUSE_KEY(MOUSEUP11), KEYC_MOUSE_KEY(MOUSEDRAG1), KEYC_MOUSE_KEY(MOUSEDRAG2), KEYC_MOUSE_KEY(MOUSEDRAG3), + KEYC_MOUSE_KEY(MOUSEDRAG6), + KEYC_MOUSE_KEY(MOUSEDRAG7), + KEYC_MOUSE_KEY(MOUSEDRAG8), + KEYC_MOUSE_KEY(MOUSEDRAG9), + KEYC_MOUSE_KEY(MOUSEDRAG10), + KEYC_MOUSE_KEY(MOUSEDRAG11), KEYC_MOUSE_KEY(MOUSEDRAGEND1), KEYC_MOUSE_KEY(MOUSEDRAGEND2), KEYC_MOUSE_KEY(MOUSEDRAGEND3), + KEYC_MOUSE_KEY(MOUSEDRAGEND6), + KEYC_MOUSE_KEY(MOUSEDRAGEND7), + KEYC_MOUSE_KEY(MOUSEDRAGEND8), + KEYC_MOUSE_KEY(MOUSEDRAGEND9), + KEYC_MOUSE_KEY(MOUSEDRAGEND10), + KEYC_MOUSE_KEY(MOUSEDRAGEND11), KEYC_MOUSE_KEY(WHEELUP), KEYC_MOUSE_KEY(WHEELDOWN), KEYC_MOUSE_KEY(SECONDCLICK1), KEYC_MOUSE_KEY(SECONDCLICK2), KEYC_MOUSE_KEY(SECONDCLICK3), + KEYC_MOUSE_KEY(SECONDCLICK6), + KEYC_MOUSE_KEY(SECONDCLICK7), + KEYC_MOUSE_KEY(SECONDCLICK8), + KEYC_MOUSE_KEY(SECONDCLICK9), + KEYC_MOUSE_KEY(SECONDCLICK10), + KEYC_MOUSE_KEY(SECONDCLICK11), KEYC_MOUSE_KEY(DOUBLECLICK1), KEYC_MOUSE_KEY(DOUBLECLICK2), KEYC_MOUSE_KEY(DOUBLECLICK3), + KEYC_MOUSE_KEY(DOUBLECLICK6), + KEYC_MOUSE_KEY(DOUBLECLICK7), + KEYC_MOUSE_KEY(DOUBLECLICK8), + KEYC_MOUSE_KEY(DOUBLECLICK9), + KEYC_MOUSE_KEY(DOUBLECLICK10), + KEYC_MOUSE_KEY(DOUBLECLICK11), KEYC_MOUSE_KEY(TRIPLECLICK1), KEYC_MOUSE_KEY(TRIPLECLICK2), KEYC_MOUSE_KEY(TRIPLECLICK3), + KEYC_MOUSE_KEY(TRIPLECLICK6), + KEYC_MOUSE_KEY(TRIPLECLICK7), + KEYC_MOUSE_KEY(TRIPLECLICK8), + KEYC_MOUSE_KEY(TRIPLECLICK9), + KEYC_MOUSE_KEY(TRIPLECLICK10), + KEYC_MOUSE_KEY(TRIPLECLICK11), /* Backspace key. */ KEYC_BSPACE, @@ -324,6 +368,7 @@ enum tty_code_code { TTYC_ENFCS, TTYC_ENMG, TTYC_FSL, + TTYC_HLS, TTYC_HOME, TTYC_HPA, TTYC_ICH, @@ -498,6 +543,7 @@ enum tty_code_code { TTYC_SMULX, TTYC_SMXX, TTYC_SS, + TTYC_SWD, TTYC_SYNC, TTYC_TC, TTYC_TSL, @@ -531,12 +577,19 @@ enum tty_code_code { #define MODE_CRLF 0x4000 #define MODE_KEXTENDED 0x8000 #define MODE_CURSOR_VERY_VISIBLE 0x10000 +#define MODE_CURSOR_BLINKING_SET 0x20000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) #define MOTION_MOUSE_MODES (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) #define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE) +/* Mouse protocol constants. */ +#define MOUSE_PARAM_MAX 0xff +#define MOUSE_PARAM_UTF8_MAX 0x7ff +#define MOUSE_PARAM_BTN_OFF 0x20 +#define MOUSE_PARAM_POS_OFF 0x21 + /* A single UTF-8 character. */ typedef u_int utf8_char; @@ -614,6 +667,24 @@ struct colour_palette { #define GRID_LINE_EXTENDED 0x2 #define GRID_LINE_DEAD 0x4 +#define CELL_INSIDE 0 +#define CELL_TOPBOTTOM 1 +#define CELL_LEFTRIGHT 2 +#define CELL_TOPLEFT 3 +#define CELL_TOPRIGHT 4 +#define CELL_BOTTOMLEFT 5 +#define CELL_BOTTOMRIGHT 6 +#define CELL_TOPJOIN 7 +#define CELL_BOTTOMJOIN 8 +#define CELL_LEFTJOIN 9 +#define CELL_RIGHTJOIN 10 +#define CELL_JOIN 11 +#define CELL_OUTSIDE 12 + +#define CELL_BORDERS " xqlkmjwvtun~" +#define SIMPLE_BORDERS " |-+++++++++." +#define PADDED_BORDERS " " + /* Grid cell data. */ struct grid_cell { struct utf8_data data; @@ -622,6 +693,7 @@ struct grid_cell { int fg; int bg; int us; + u_int link; }; /* Grid extended cell entry. */ @@ -632,6 +704,7 @@ struct grid_extd_entry { int fg; int bg; int us; + u_int link; } __packed; /* Grid cell entry. */ @@ -650,15 +723,16 @@ struct grid_cell_entry { /* Grid line. */ struct grid_line { + struct grid_cell_entry *celldata; u_int cellused; u_int cellsize; - struct grid_cell_entry *celldata; - u_int extdsize; struct grid_extd_entry *extddata; + u_int extdsize; int flags; -} __packed; + time_t time; +}; /* Entire grid of cells. */ struct grid { @@ -762,12 +836,15 @@ struct screen { u_int cy; /* cursor y */ enum screen_cursor_style cstyle; /* cursor style */ - char *ccolour; /* cursor colour */ + enum screen_cursor_style default_cstyle; + int ccolour; /* cursor colour */ + int default_ccolour; u_int rupper; /* scroll region top */ u_int rlower; /* scroll region bottom */ int mode; + int default_mode; u_int saved_cx; u_int saved_cy; @@ -779,6 +856,8 @@ struct screen { struct screen_sel *sel; struct screen_write_cline *write_list; + + struct hyperlinks *hyperlinks; }; /* Screen write context. */ @@ -800,6 +879,33 @@ struct screen_write_ctx { u_int bg; }; +/* Box border lines option. */ +enum box_lines { + BOX_LINES_DEFAULT = -1, + BOX_LINES_SINGLE, + BOX_LINES_DOUBLE, + BOX_LINES_HEAVY, + BOX_LINES_SIMPLE, + BOX_LINES_ROUNDED, + BOX_LINES_PADDED, + BOX_LINES_NONE +}; + +/* Pane border lines option. */ +enum pane_lines { + PANE_LINES_SINGLE, + PANE_LINES_DOUBLE, + PANE_LINES_HEAVY, + PANE_LINES_SIMPLE, + PANE_LINES_NUMBER +}; + +/* Pane border indicator option. */ +#define PANE_BORDER_OFF 0 +#define PANE_BORDER_COLOUR 1 +#define PANE_BORDER_ARROWS 2 +#define PANE_BORDER_BOTH 3 + /* Screen redraw context. */ struct screen_redraw_ctx { struct client *c; @@ -808,7 +914,7 @@ struct screen_redraw_ctx { int statustop; int pane_status; - int pane_lines; + enum pane_lines pane_lines; struct grid_cell no_pane_gc; int no_pane_gc_set; @@ -936,6 +1042,7 @@ struct window_pane { pid_t pid; char tty[TTY_NAME_MAX]; int status; + struct timeval dead_time; int fd; struct bufferevent *event; @@ -978,40 +1085,41 @@ RB_HEAD(window_pane_tree, window_pane); /* Window structure. */ struct window { - u_int id; - void *latest; + u_int id; + void *latest; - char *name; - struct event name_event; - struct timeval name_time; + char *name; + struct event name_event; + struct timeval name_time; - struct event alerts_timer; - struct event offset_timer; + struct event alerts_timer; + struct event offset_timer; - struct timeval activity_time; + struct timeval activity_time; - struct window_pane *active; - struct window_pane *last; - struct window_panes panes; + struct window_pane *active; + struct window_pane *last; + struct window_panes panes; - int lastlayout; - struct layout_cell *layout_root; - struct layout_cell *saved_layout_root; - char *old_layout; + int lastlayout; + struct layout_cell *layout_root; + struct layout_cell *saved_layout_root; + char *old_layout; - u_int sx; - u_int sy; - u_int manual_sx; - u_int manual_sy; - u_int xpixel; - u_int ypixel; + u_int sx; + u_int sy; + u_int manual_sx; + u_int manual_sy; + u_int xpixel; + u_int ypixel; - u_int new_sx; - u_int new_sy; - u_int new_xpixel; - u_int new_ypixel; + u_int new_sx; + u_int new_sy; + u_int new_xpixel; + u_int new_ypixel; - int flags; + struct utf8_data *fill_character; + int flags; #define WINDOW_BELL 0x1 #define WINDOW_ACTIVITY 0x2 #define WINDOW_SILENCE 0x4 @@ -1020,15 +1128,15 @@ struct window { #define WINDOW_RESIZE 0x20 #define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE) - int alerts_queued; - TAILQ_ENTRY(window) alerts_entry; + int alerts_queued; + TAILQ_ENTRY(window) alerts_entry; - struct options *options; + struct options *options; - u_int references; - TAILQ_HEAD(, winlink) winlinks; + u_int references; + TAILQ_HEAD(, winlink) winlinks; - RB_ENTRY(window) entry; + RB_ENTRY(window) entry; }; RB_HEAD(windows, window); @@ -1062,13 +1170,6 @@ TAILQ_HEAD(winlink_stack, winlink); #define PANE_STATUS_TOP 1 #define PANE_STATUS_BOTTOM 2 -/* Pane border lines option. */ -#define PANE_LINES_SINGLE 0 -#define PANE_LINES_DOUBLE 1 -#define PANE_LINES_HEAVY 2 -#define PANE_LINES_SIMPLE 3 -#define PANE_LINES_NUMBER 4 - /* Layout direction. */ enum layout_type { LAYOUT_LEFTRIGHT, @@ -1157,21 +1258,33 @@ struct session { RB_HEAD(sessions, session); /* Mouse button masks. */ -#define MOUSE_MASK_BUTTONS 3 +#define MOUSE_MASK_BUTTONS 195 #define MOUSE_MASK_SHIFT 4 #define MOUSE_MASK_META 8 #define MOUSE_MASK_CTRL 16 #define MOUSE_MASK_DRAG 32 -#define MOUSE_MASK_WHEEL 64 #define MOUSE_MASK_MODIFIERS (MOUSE_MASK_SHIFT|MOUSE_MASK_META|MOUSE_MASK_CTRL) -/* Mouse wheel states. */ -#define MOUSE_WHEEL_UP 0 -#define MOUSE_WHEEL_DOWN 1 +/* Mouse wheel type. */ +#define MOUSE_WHEEL_UP 64 +#define MOUSE_WHEEL_DOWN 65 + +/* Mouse button type. */ +#define MOUSE_BUTTON_1 0 +#define MOUSE_BUTTON_2 1 +#define MOUSE_BUTTON_3 2 +#define MOUSE_BUTTON_6 66 +#define MOUSE_BUTTON_7 67 +#define MOUSE_BUTTON_8 128 +#define MOUSE_BUTTON_9 129 +#define MOUSE_BUTTON_10 130 +#define MOUSE_BUTTON_11 131 /* Mouse helpers. */ #define MOUSE_BUTTONS(b) ((b) & MOUSE_MASK_BUTTONS) -#define MOUSE_WHEEL(b) ((b) & MOUSE_MASK_WHEEL) +#define MOUSE_WHEEL(b) \ + (((b) & MOUSE_MASK_BUTTONS) == MOUSE_WHEEL_UP || \ + ((b) & MOUSE_MASK_BUTTONS) == MOUSE_WHEEL_DOWN) #define MOUSE_DRAG(b) ((b) & MOUSE_MASK_DRAG) #define MOUSE_RELEASE(b) (((b) & MOUSE_MASK_BUTTONS) == 3) @@ -1236,6 +1349,7 @@ LIST_HEAD(tty_terms, tty_term); struct tty { struct client *client; struct event start_timer; + struct event clipboard_timer; u_int sx; u_int sy; @@ -1245,7 +1359,7 @@ struct tty { u_int cx; u_int cy; enum screen_cursor_style cstyle; - char *ccolour; + int ccolour; int oflag; u_int oox; @@ -1276,10 +1390,10 @@ struct tty { #define TTY_NOCURSOR 0x1 #define TTY_FREEZE 0x2 #define TTY_TIMER 0x4 -/* 0x8 unused */ +#define TTY_NOBLOCK 0x8 #define TTY_STARTED 0x10 #define TTY_OPENED 0x20 -/* 0x40 unused */ +#define TTY_OSC52QUERY 0x40 #define TTY_BLOCK 0x80 #define TTY_HAVEDA 0x100 #define TTY_HAVEXDA 0x200 @@ -1316,6 +1430,7 @@ struct tty_ctx { u_int num; void *ptr; + void *ptr2; /* * Cursor and region position before the screen was updated - this is @@ -1568,10 +1683,18 @@ struct client_window { }; RB_HEAD(client_windows, client_window); +/* Visible areas not obstructed by overlays. */ +#define OVERLAY_MAX_RANGES 3 +struct overlay_ranges { + u_int px[OVERLAY_MAX_RANGES]; + u_int nx[OVERLAY_MAX_RANGES]; +}; + /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef int (*overlay_check_cb)(struct client *, void *, u_int, u_int); +typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int, + struct overlay_ranges *); typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, u_int *); typedef void (*overlay_draw_cb)(struct client *, void *, @@ -1580,50 +1703,51 @@ typedef int (*overlay_key_cb)(struct client *, void *, struct key_event *); typedef void (*overlay_free_cb)(struct client *, void *); typedef void (*overlay_resize_cb)(struct client *, void *); struct client { - const char *name; - struct tmuxpeer *peer; - struct cmdq_list *queue; + const char *name; + struct tmuxpeer *peer; + struct cmdq_list *queue; - struct client_windows windows; + struct client_windows windows; - struct control_state *control_state; - u_int pause_age; + struct control_state *control_state; + u_int pause_age; - pid_t pid; - int fd; - int out_fd; - struct event event; - int retval; + pid_t pid; + int fd; + int out_fd; + struct event event; + int retval; - struct timeval creation_time; - struct timeval activity_time; + struct timeval creation_time; + struct timeval activity_time; - struct environ *environ; + struct environ *environ; struct format_job_tree *jobs; - char *title; - const char *cwd; + char *title; + char *path; + const char *cwd; - char *term_name; - int term_features; - char *term_type; - char **term_caps; - u_int term_ncaps; + char *term_name; + int term_features; + char *term_type; + char **term_caps; + u_int term_ncaps; - char *ttyname; - struct tty tty; + char *ttyname; + struct tty tty; - size_t written; - size_t discarded; - size_t redraw; + size_t written; + size_t discarded; + size_t redraw; - struct event repeat_timer; + struct event repeat_timer; - struct event click_timer; - u_int click_button; - struct mouse_event click_event; + struct event click_timer; + u_int click_button; + struct mouse_event click_event; - struct status_line status; + struct status_line status; #define CLIENT_TERMINAL 0x1 #define CLIENT_LOGIN 0x2 @@ -1660,6 +1784,7 @@ struct client { #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL #define CLIENT_WINDOWSIZECHANGED 0x400000000ULL +#define CLIENT_CLIPBOARDBUFFER 0x800000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ @@ -1671,69 +1796,79 @@ struct client { (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ CLIENT_EXIT) +#define CLIENT_NODETACHFLAGS \ + (CLIENT_DEAD| \ + CLIENT_EXIT) #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ CLIENT_EXIT) - uint64_t flags; + uint64_t flags; enum { CLIENT_EXIT_RETURN, CLIENT_EXIT_SHUTDOWN, CLIENT_EXIT_DETACH - } exit_type; - enum msgtype exit_msgtype; - char *exit_session; - char *exit_message; - - struct key_table *keytable; - - uint64_t redraw_panes; - - int message_ignore_keys; - int message_ignore_styles; - char *message_string; - struct event message_timer; - - char *prompt_string; - struct utf8_data *prompt_buffer; - char *prompt_last; - size_t prompt_index; - prompt_input_cb prompt_inputcb; - prompt_free_cb prompt_freecb; - void *prompt_data; - u_int prompt_hindex[PROMPT_NTYPES]; - enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode; - struct utf8_data *prompt_saved; + } exit_type; + enum msgtype exit_msgtype; + char *exit_session; + char *exit_message; + + struct key_table *keytable; + + uint64_t redraw_panes; + + int message_ignore_keys; + int message_ignore_styles; + char *message_string; + struct event message_timer; + + char *prompt_string; + struct utf8_data *prompt_buffer; + char *prompt_last; + size_t prompt_index; + prompt_input_cb prompt_inputcb; + prompt_free_cb prompt_freecb; + void *prompt_data; + u_int prompt_hindex[PROMPT_NTYPES]; + enum { + PROMPT_ENTRY, + PROMPT_COMMAND + } prompt_mode; + struct utf8_data *prompt_saved; #define PROMPT_SINGLE 0x1 #define PROMPT_NUMERIC 0x2 #define PROMPT_INCREMENTAL 0x4 #define PROMPT_NOFORMAT 0x8 #define PROMPT_KEY 0x10 - int prompt_flags; - enum prompt_type prompt_type; + int prompt_flags; + enum prompt_type prompt_type; + int prompt_cursor; - struct session *session; - struct session *last_session; + struct session *session; + struct session *last_session; - int references; + int references; + + void *pan_window; + u_int pan_ox; + u_int pan_oy; - void *pan_window; - u_int pan_ox; - u_int pan_oy; + overlay_check_cb overlay_check; + overlay_mode_cb overlay_mode; + overlay_draw_cb overlay_draw; + overlay_key_cb overlay_key; + overlay_free_cb overlay_free; + overlay_resize_cb overlay_resize; + void *overlay_data; + struct event overlay_timer; - overlay_check_cb overlay_check; - overlay_mode_cb overlay_mode; - overlay_draw_cb overlay_draw; - overlay_key_cb overlay_key; - overlay_free_cb overlay_free; - overlay_resize_cb overlay_resize; - void *overlay_data; - struct event overlay_timer; + struct client_files files; - struct client_files files; + u_int *clipboard_panes; + u_int clipboard_npanes; - TAILQ_ENTRY(client) entry; + TAILQ_ENTRY(client) entry; }; TAILQ_HEAD(clients, client); @@ -1905,8 +2040,10 @@ struct tmuxpeer *proc_add_peer(struct tmuxproc *, int, void (*)(struct imsg *, void *), void *); void proc_remove_peer(struct tmuxpeer *); void proc_kill_peer(struct tmuxpeer *); +void proc_flush_peer(struct tmuxpeer *); void proc_toggle_log(struct tmuxproc *); pid_t proc_fork_and_daemon(int *); +uid_t proc_get_peer_uid(struct tmuxpeer *); /* cfg.c */ extern int cfg_finished; @@ -1930,6 +2067,7 @@ u_int paste_buffer_order(struct paste_buffer *); time_t paste_buffer_created(struct paste_buffer *); const char *paste_buffer_data(struct paste_buffer *, size_t *); struct paste_buffer *paste_walk(struct paste_buffer *); +int paste_is_empty(void); struct paste_buffer *paste_get_top(const char **); struct paste_buffer *paste_get_name(const char *); void paste_free(struct paste_buffer *); @@ -1966,6 +2104,7 @@ void format_add_cb(struct format_tree *, const char *, format_cb); void format_log_debug(struct format_tree *, const char *); void format_each(struct format_tree *, void (*)(const char *, const char *, void *), void *); +char *format_pretty_time(time_t, int); char *format_expand_time(struct format_tree *, const char *); char *format_expand(struct format_tree *, const char *); char *format_single(struct cmdq_item *, const char *, @@ -1988,12 +2127,14 @@ void format_defaults_paste_buffer(struct format_tree *, struct paste_buffer *); void format_lost_client(struct client *); char *format_grid_word(struct grid *, u_int, u_int); +char *format_grid_hyperlink(struct grid *, u_int, u_int, + struct screen *); char *format_grid_line(struct grid *, u_int); /* format-draw.c */ void format_draw(struct screen_write_ctx *, const struct grid_cell *, u_int, const char *, - struct style_ranges *); + struct style_ranges *, int); u_int format_width(const char *); char *format_trim_left(const char *, u_int); char *format_trim_right(const char *, u_int); @@ -2059,6 +2200,8 @@ struct style *options_string_to_style(struct options *, const char *, int options_from_string(struct options *, const struct options_table_entry *, const char *, const char *, int, char **); +int options_find_choice(const struct options_table_entry *, + const char *, char **); void options_push_changes(const char *); int options_remove_or_default(struct options_entry *, int, char **); @@ -2074,9 +2217,9 @@ typedef void (*job_free_cb) (void *); #define JOB_NOWAIT 0x1 #define JOB_KEEPWRITE 0x2 #define JOB_PTY 0x4 -struct job *job_run(const char *, int, char **, struct session *, - const char *, job_update_cb, job_complete_cb, job_free_cb, - void *, int, int, int); +struct job *job_run(const char *, int, char **, struct environ *, + struct session *, const char *, job_update_cb, + job_complete_cb, job_free_cb, void *, int, int, int); void job_free(struct job *); int job_transfer(struct job *, pid_t *, char *, size_t); void job_resize(struct job *, u_int, u_int); @@ -2113,11 +2256,13 @@ void tty_update_window_offset(struct window *); void tty_update_client_offset(struct client *); void tty_raw(struct tty *, const char *); void tty_attributes(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *); + const struct grid_cell *, struct colour_palette *, + struct hyperlinks *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); void tty_cursor(struct tty *, u_int, u_int); +void tty_clipboard_query(struct tty *); void tty_putcode(struct tty *, enum tty_code_code); void tty_putcode1(struct tty *, enum tty_code_code, int); void tty_putcode2(struct tty *, enum tty_code_code, int, int); @@ -2129,7 +2274,8 @@ void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); void tty_cell(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *); + const struct grid_cell *, struct colour_palette *, + struct hyperlinks *); int tty_init(struct tty *, struct client *); void tty_resize(struct tty *); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); @@ -2137,6 +2283,7 @@ void tty_start_tty(struct tty *); void tty_send_requests(struct tty *); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); +void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *); @@ -2146,7 +2293,7 @@ int tty_open(struct tty *, char **); void tty_close(struct tty *); void tty_free(struct tty *); void tty_update_features(struct tty *); -void tty_set_selection(struct tty *, const char *, size_t); +void tty_set_selection(struct tty *, const char *, const char *, size_t); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2209,6 +2356,9 @@ void tty_default_features(int *, const char *, u_int); int tty_acs_needed(struct tty *); const char *tty_acs_get(struct tty *, u_char); int tty_acs_reverse_get(struct tty *, const char *, size_t); +const struct utf8_data *tty_acs_double_borders(int); +const struct utf8_data *tty_acs_heavy_borders(int); +const struct utf8_data *tty_acs_rounded_borders(int); /* tty-keys.c */ void tty_keys_build(struct tty *); @@ -2248,10 +2398,16 @@ struct args_value *args_first_value(struct args *, u_char); struct args_value *args_next_value(struct args_value *); long long args_strtonum(struct args *, u_char, long long, long long, char **); +long long args_strtonum_and_expand(struct args *, u_char, long long, + long long, struct cmdq_item *, char **); long long args_percentage(struct args *, u_char, long long, long long, long long, char **); long long args_string_percentage(const char *, long long, long long, long long, char **); +long long args_percentage_and_expand(struct args *, u_char, long long, + long long, long long, struct cmdq_item *, char **); +long long args_string_percentage_and_expand(const char *, long long, + long long, long long, struct cmdq_item *, char **); /* cmd-find.c */ int cmd_find_target(struct cmd_find_state *, struct cmdq_item *, @@ -2445,6 +2601,7 @@ extern struct tmuxproc *server_proc; extern struct clients clients; extern struct cmd_find_state marked_pane; extern struct message_list message_log; +extern time_t current_time; void server_set_marked(struct session *, struct winlink *, struct window_pane *); void server_clear_marked(void); @@ -2455,6 +2612,7 @@ int server_start(struct tmuxproc *, int, struct event_base *, int, char *); void server_update_socket(void); void server_add_accept(int); void printflike(1, 2) server_add_message(const char *, ...); +int server_create_socket(int, char **); /* server-client.c */ RB_PROTOTYPE(client_windows, client_window, entry, server_client_window_cmp); @@ -2463,6 +2621,8 @@ void server_client_set_overlay(struct client *, u_int, overlay_check_cb, overlay_mode_cb, overlay_draw_cb, overlay_key_cb, overlay_free_cb, overlay_resize_cb, void *); void server_client_clear_overlay(struct client *); +void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int, + u_int, struct overlay_ranges *); void server_client_set_key_table(struct client *, const char *); const char *server_client_get_key_table(struct client *); int server_client_check_nested(struct client *); @@ -2558,6 +2718,8 @@ void input_parse_pane(struct window_pane *); void input_parse_buffer(struct window_pane *, u_char *, size_t); void input_parse_screen(struct input_ctx *, struct screen *, screen_write_init_ctx_cb, void *, u_char *, size_t); +void input_reply_clipboard(struct bufferevent *, const char *, size_t, + const char *); /* input-key.c */ void input_key_build(void); @@ -2570,6 +2732,7 @@ int input_key_get_mouse(struct screen *, struct mouse_event *, u_int, int colour_find_rgb(u_char, u_char, u_char); int colour_join_rgb(u_char, u_char, u_char); void colour_split_rgb(int, u_char *, u_char *, u_char *); +int colour_force_rgb(int); const char *colour_tostring(int); int colour_fromstring(const char *s); int colour_256toRGB(int); @@ -2613,7 +2776,7 @@ void grid_clear_lines(struct grid *, u_int, u_int, u_int); void grid_move_lines(struct grid *, u_int, u_int, u_int, u_int); void grid_move_cells(struct grid *, u_int, u_int, u_int, u_int, u_int); char *grid_string_cells(struct grid *, u_int, u_int, u_int, - struct grid_cell **, int, int, int); + struct grid_cell **, int, int, int, struct screen *); void grid_duplicate_lines(struct grid *, u_int, struct grid *, u_int, u_int); void grid_reflow(struct grid *, u_int); @@ -2690,7 +2853,8 @@ void screen_write_hline(struct screen_write_ctx *, u_int, int, int); void screen_write_vline(struct screen_write_ctx *, u_int, int, int); void screen_write_menu(struct screen_write_ctx *, struct menu *, int, const struct grid_cell *); -void screen_write_box(struct screen_write_ctx *, u_int, u_int); +void screen_write_box(struct screen_write_ctx *, u_int, u_int, int, + const struct grid_cell *, const char *); void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, u_int); void screen_write_backspace(struct screen_write_ctx *); @@ -2725,7 +2889,8 @@ void screen_write_collect_end(struct screen_write_ctx *); void screen_write_collect_add(struct screen_write_ctx *, const struct grid_cell *); void screen_write_cell(struct screen_write_ctx *, const struct grid_cell *); -void screen_write_setselection(struct screen_write_ctx *, u_char *, u_int); +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); void screen_write_alternateon(struct screen_write_ctx *, struct grid_cell *, int); @@ -2741,8 +2906,9 @@ void screen_init(struct screen *, u_int, u_int, u_int); void screen_reinit(struct screen *); void screen_free(struct screen *); void screen_reset_tabs(struct screen *); -void screen_set_cursor_style(struct screen *, u_int); -void screen_set_cursor_colour(struct screen *, const char *); +void screen_reset_hyperlinks(struct screen *); +void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *); +void screen_set_cursor_colour(struct screen *, int); int screen_set_title(struct screen *, const char *); void screen_set_path(struct screen *, const char *); void screen_push_title(struct screen *); @@ -2847,6 +3013,8 @@ void *window_pane_get_new_data(struct window_pane *, struct window_pane_offset *, size_t *); void window_pane_update_used_data(struct window_pane *, struct window_pane_offset *, size_t); +void window_set_fill_character(struct window *); +void window_pane_default_cursor(struct window_pane *); /* layout.c */ u_int layout_count_cells(struct layout_cell *); @@ -2883,7 +3051,7 @@ void layout_spread_out(struct window_pane *); /* layout-custom.c */ char *layout_dump(struct layout_cell *); -int layout_parse(struct window *, const char *); +int layout_parse(struct window *, const char *, char **); /* layout-set.c */ int layout_set_lookup(const char *); @@ -2948,8 +3116,9 @@ extern const struct window_mode window_client_mode; /* window-copy.c */ extern const struct window_mode window_copy_mode; extern const struct window_mode window_view_mode; -void printflike(2, 3) window_copy_add(struct window_pane *, const char *, ...); -void printflike(2, 0) window_copy_vadd(struct window_pane *, const char *, +void printflike(3, 4) window_copy_add(struct window_pane *, int, const char *, + ...); +void printflike(3, 0) window_copy_vadd(struct window_pane *, int, const char *, va_list); void window_copy_pageup(struct window_pane *, int); void window_copy_start_drag(struct client *, struct mouse_event *); @@ -2967,6 +3136,7 @@ char *parse_window_name(const char *); /* control.c */ void control_discard(struct client *); void control_start(struct client *); +void control_ready(struct client *); void control_stop(struct client *); void control_set_pane_on(struct client *, struct window_pane *); void control_set_pane_off(struct client *, struct window_pane *); @@ -3000,6 +3170,7 @@ void control_notify_session_window_changed(struct session *); /* session.c */ extern struct sessions sessions; +extern u_int next_session_id; int session_cmp(struct session *, struct session *); RB_PROTOTYPE(sessions, session, entry, session_cmp); int session_alive(struct session *); @@ -3093,7 +3264,8 @@ int menu_display(struct menu *, int, struct cmdq_item *, u_int, u_int, struct client *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); -int menu_check_cb(struct client *, void *, u_int, u_int); +void menu_check_cb(struct client *, void *, u_int, u_int, u_int, + struct overlay_ranges *); void menu_draw_cb(struct client *, void *, struct screen_redraw_ctx *); void menu_free_cb(struct client *, void *); @@ -3102,13 +3274,14 @@ int menu_key_cb(struct client *, void *, struct key_event *); /* popup.c */ #define POPUP_CLOSEEXIT 0x1 #define POPUP_CLOSEEXITZERO 0x2 -#define POPUP_NOBORDER 0x4 -#define POPUP_INTERNAL 0x8 +#define POPUP_INTERNAL 0x4 typedef void (*popup_close_cb)(int, void *); typedef void (*popup_finish_edit_cb)(char *, size_t, void *); -int popup_display(int, struct cmdq_item *, u_int, u_int, u_int, - u_int, const char *, int, char **, const char *, - struct client *, struct session *, popup_close_cb, void *); +int popup_display(int, int, struct cmdq_item *, u_int, u_int, + u_int, u_int, struct environ *, const char *, int, char **, + const char *, const char *, struct client *, + struct session *, const char *, const char *, + popup_close_cb, void *); int popup_editor(struct client *, const char *, size_t, popup_finish_edit_cb, void *); @@ -3130,4 +3303,24 @@ struct window_pane *spawn_pane(struct spawn_context *, char **); /* regsub.c */ char *regsub(const char *, const char *, const char *, int); +/* server-acl.c */ +void server_acl_init(void); +struct server_acl_user *server_acl_user_find(uid_t); +void server_acl_display(struct cmdq_item *); +void server_acl_user_allow(uid_t); +void server_acl_user_deny(uid_t); +void server_acl_user_allow_write(uid_t); +void server_acl_user_deny_write(uid_t); +int server_acl_join(struct client *); +uid_t server_acl_get_uid(struct server_acl_user *); + +/* hyperlink.c */ +u_int hyperlinks_put(struct hyperlinks *, const char *, + const char *); +int hyperlinks_get(struct hyperlinks *, u_int, + const char **, const char **, const char **); +struct hyperlinks *hyperlinks_init(void); +void hyperlinks_reset(struct hyperlinks *); +void hyperlinks_free(struct hyperlinks *); + #endif /* TMUX_H */ diff --git a/tools/ansicode.txt b/tools/ansicode.txt index 8767b9e7..a9c81a96 100644 --- a/tools/ansicode.txt +++ b/tools/ansicode.txt @@ -19,7 +19,7 @@ Standardized Video Terminals" in the April-1984 issue of BYTE magazine. ANSI X3.4-1977 defines the 7-bit ASCII character set (C0 and G0). It was written in 1968, revised in 1977, and explains the decisions made in laying out the ASCII code. In particular, it explains why ANSI chose to make ASCII -incompatible with EBCDIC in order to make it self-consistant. +incompatible with EBCDIC in order to make it self-consistent. ANSI X3.41-1974 introduces the idea of an 8-bit ASCII character set (C1 and G1 in addition to the existing C0 and G0). It describes how to use the 8-bit @@ -184,7 +184,7 @@ Oct Hex Name * (* marks function used in DEC VT series or LA series terminals) 230 98 X Reserved for for future standard 231 99 Y Reserved 232 9A Z * Reserved, but causes DEC terminals to respond with DA codes -233 9B [ CSI * Control Sequence Introducer (described in a seperate table) +233 9B [ CSI * Control Sequence Introducer (described in a separate table) 234 9C \ ST * String Terminator (VT125 exits graphics) 235 9D ] OSC Operating System Command (reprograms intelligent terminal) 236 9E ^ PM Privacy Message (password verification), terminated by ST @@ -268,7 +268,7 @@ Oct Hex * (* marks function used in DEC VT series or LA series terminals) 073 3B ; 074 3C < * DECANSI - Switch from VT52 mode to VT100 mode 075 3D = * DECKPAM - Set keypad to applications mode (ESCape instead of digits) -076 3E > * DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq) +076 3E > * DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq) 077 3F ? DCS Device Control Strings used by DEC terminals (ends with ST) @@ -294,7 +294,7 @@ Pt = Start VT105 graphics on a VT125 ============================================================================== - Indepenent control functions (from Appendix E of X3.64-1977). + Independent control functions (from Appendix E of X3.64-1977). These four controls have the same meaning regardless of the current definition of the C0 and C1 control sets. Each control is a two-character ESCape sequence, the 2nd character is lowercase. @@ -449,7 +449,7 @@ Oct Hex * (* marks function used in DEC VT series or LA series terminals) * [16h = TTM - Transmit Termination Mode, send scrolling region [17h = SATM - Send Area Transmit Mode, send entire buffer [18h = TSM - Tabulation Stop Mode, lines are independent - [19h = EBM - Editing Boundry Mode, all of memory affected + [19h = EBM - Editing Boundary Mode, all of memory affected * [20h = LNM - Linefeed Newline Mode, LF interpreted as CR LF * [?1h = DECCKM - Cursor Keys Mode, send ESC O A for cursor up * [?2h = DECANM - ANSI Mode, use ESC < to switch VT52 to ANSI @@ -25,7 +25,7 @@ /* Table mapping ACS entries to UTF-8. */ struct tty_acs_entry { - u_char key; + u_char key; const char *string; }; static const struct tty_acs_entry tty_acs_table[] = { @@ -61,7 +61,7 @@ static const struct tty_acs_entry tty_acs_table[] = { { 'x', "\342\224\202" }, /* vertical line */ { 'y', "\342\211\244" }, /* less-than-or-equal-to */ { 'z', "\342\211\245" }, /* greater-than-or-equal-to */ - { '{', "\317\200" }, /* greek pi */ + { '{', "\317\200" }, /* greek pi */ { '|', "\342\211\240" }, /* not-equal */ { '}', "\302\243" }, /* UK pound sign */ { '~', "\302\267" } /* bullet */ @@ -110,6 +110,78 @@ static const struct tty_acs_reverse_entry tty_acs_reverse3[] = { { "\342\225\254", 'n' }, }; +/* UTF-8 double borders. */ +static const struct utf8_data tty_acs_double_borders_list[] = { + { "", 0, 0, 0 }, + { "\342\225\221", 0, 3, 1 }, /* U+2551 */ + { "\342\225\220", 0, 3, 1 }, /* U+2550 */ + { "\342\225\224", 0, 3, 1 }, /* U+2554 */ + { "\342\225\227", 0, 3, 1 }, /* U+2557 */ + { "\342\225\232", 0, 3, 1 }, /* U+255A */ + { "\342\225\235", 0, 3, 1 }, /* U+255D */ + { "\342\225\246", 0, 3, 1 }, /* U+2566 */ + { "\342\225\251", 0, 3, 1 }, /* U+2569 */ + { "\342\225\240", 0, 3, 1 }, /* U+2560 */ + { "\342\225\243", 0, 3, 1 }, /* U+2563 */ + { "\342\225\254", 0, 3, 1 }, /* U+256C */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ +}; + +/* UTF-8 heavy borders. */ +static const struct utf8_data tty_acs_heavy_borders_list[] = { + { "", 0, 0, 0 }, + { "\342\224\203", 0, 3, 1 }, /* U+2503 */ + { "\342\224\201", 0, 3, 1 }, /* U+2501 */ + { "\342\224\217", 0, 3, 1 }, /* U+250F */ + { "\342\224\223", 0, 3, 1 }, /* U+2513 */ + { "\342\224\227", 0, 3, 1 }, /* U+2517 */ + { "\342\224\233", 0, 3, 1 }, /* U+251B */ + { "\342\224\263", 0, 3, 1 }, /* U+2533 */ + { "\342\224\273", 0, 3, 1 }, /* U+253B */ + { "\342\224\243", 0, 3, 1 }, /* U+2523 */ + { "\342\224\253", 0, 3, 1 }, /* U+252B */ + { "\342\225\213", 0, 3, 1 }, /* U+254B */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ +}; + +/* UTF-8 rounded borders. */ +static const struct utf8_data tty_acs_rounded_borders_list[] = { + { "", 0, 0, 0 }, + { "\342\224\202", 0, 3, 1 }, /* U+2502 */ + { "\342\224\200", 0, 3, 1 }, /* U+2500 */ + { "\342\225\255", 0, 3, 1 }, /* U+256D */ + { "\342\225\256", 0, 3, 1 }, /* U+256E */ + { "\342\225\260", 0, 3, 1 }, /* U+2570 */ + { "\342\225\257", 0, 3, 1 }, /* U+256F */ + { "\342\224\263", 0, 3, 1 }, /* U+2533 */ + { "\342\224\273", 0, 3, 1 }, /* U+253B */ + { "\342\224\243", 0, 3, 1 }, /* U+2523 */ + { "\342\224\253", 0, 3, 1 }, /* U+252B */ + { "\342\225\213", 0, 3, 1 }, /* U+254B */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ +}; + +/* Get cell border character for double style. */ +const struct utf8_data * +tty_acs_double_borders(int cell_type) +{ + return (&tty_acs_double_borders_list[cell_type]); +} + +/* Get cell border character for heavy style. */ +const struct utf8_data * +tty_acs_heavy_borders(int cell_type) +{ + return (&tty_acs_heavy_borders_list[cell_type]); +} + +/* Get cell border character for rounded style. */ +const struct utf8_data * +tty_acs_rounded_borders(int cell_type) +{ + return (&tty_acs_rounded_borders_list[cell_type]); +} + static int tty_acs_cmp(const void *key, const void *value) { diff --git a/tty-features.c b/tty-features.c index 48ac51be..64f0039a 100644 --- a/tty-features.c +++ b/tty-features.c @@ -21,6 +21,12 @@ #include <stdlib.h> #include <string.h> +#if defined(HAVE_CURSES_H) +#include <curses.h> +#elif defined(HAVE_NCURSES_H) +#include <ncurses.h> +#endif + #include "tmux.h" /* @@ -53,6 +59,18 @@ static const struct tty_feature tty_feature_title = { 0 }; +/* Terminal has OSC 7 working directory. */ +static const char *tty_feature_osc7_capabilities[] = { + "Swd=\\E]7;", + "fsl=\\a", + NULL +}; +static const struct tty_feature tty_feature_osc7 = { + "osc7", + tty_feature_osc7_capabilities, + 0 +}; + /* Terminal has mouse support. */ static const char *tty_feature_mouse_capabilities[] = { "kmous=\\E[M", @@ -75,6 +93,21 @@ static const struct tty_feature tty_feature_clipboard = { 0 }; +/* Terminal supports OSC 8 hyperlinks. */ +static const char *tty_feature_hyperlinks_capabilities[] = { +#if defined (__OpenBSD__) || (defined(NCURSES_VERSION_MAJOR) && \ + (NCURSES_VERSION_MAJOR > 5 || \ + (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 8))) + "*:Hls=\\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\\E\\\\", +#endif + NULL +}; +static const struct tty_feature tty_feature_hyperlinks = { + "hyperlinks", + tty_feature_hyperlinks_capabilities, + 0 +}; + /* * Terminal supports RGB colour. This replaces setab and setaf also since * terminals with RGB have versions that do not allow setting colours from the @@ -238,17 +271,94 @@ static const struct tty_feature tty_feature_rectfill = { TERM_DECFRA }; +/* Use builtin function keys only. */ +static const char *tty_feature_ignorefkeys_capabilities[] = { + "kf0@", + "kf1@", + "kf2@", + "kf3@", + "kf4@", + "kf5@", + "kf6@", + "kf7@", + "kf8@", + "kf9@", + "kf10@", + "kf11@", + "kf12@", + "kf13@", + "kf14@", + "kf15@", + "kf16@", + "kf17@", + "kf18@", + "kf19@", + "kf20@", + "kf21@", + "kf22@", + "kf23@", + "kf24@", + "kf25@", + "kf26@", + "kf27@", + "kf28@", + "kf29@", + "kf30@", + "kf31@", + "kf32@", + "kf33@", + "kf34@", + "kf35@", + "kf36@", + "kf37@", + "kf38@", + "kf39@", + "kf40@", + "kf41@", + "kf42@", + "kf43@", + "kf44@", + "kf45@", + "kf46@", + "kf47@", + "kf48@", + "kf49@", + "kf50@", + "kf51@", + "kf52@", + "kf53@", + "kf54@", + "kf55@", + "kf56@", + "kf57@", + "kf58@", + "kf59@", + "kf60@", + "kf61@", + "kf62@", + "kf63@", + NULL +}; +static const struct tty_feature tty_feature_ignorefkeys = { + "ignorefkeys", + tty_feature_ignorefkeys_capabilities, + 0 +}; + /* Available terminal features. */ static const struct tty_feature *tty_features[] = { &tty_feature_256, &tty_feature_bpaste, &tty_feature_ccolour, &tty_feature_clipboard, + &tty_feature_hyperlinks, &tty_feature_cstyle, &tty_feature_extkeys, &tty_feature_focus, + &tty_feature_ignorefkeys, &tty_feature_margins, &tty_feature_mouse, + &tty_feature_osc7, &tty_feature_overline, &tty_feature_rectfill, &tty_feature_rgb, @@ -356,14 +466,14 @@ tty_default_features(int *feat, const char *name, u_int version) }, { .name = "tmux", .features = TTY_FEATURES_BASE_MODERN_XTERM - ",ccolour,cstyle,focus,overline,usstyle" + ",ccolour,cstyle,focus,overline,usstyle,hyperlinks" }, { .name = "rxvt-unicode", - .features = "256,bpaste,ccolour,cstyle,mouse,title" + .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys" }, { .name = "iTerm2", .features = TTY_FEATURES_BASE_MODERN_XTERM - ",cstyle,extkeys,margins,sync" + ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" }, { .name = "XTerm", /* @@ -126,7 +126,7 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033\033[C", KEYC_RIGHT|KEYC_CURSOR|KEYC_META }, { "\033\033[D", KEYC_LEFT|KEYC_CURSOR|KEYC_META }, - /* Other (xterm) "cursor" keys. */ + /* Other xterm keys. */ { "\033OH", KEYC_HOME }, { "\033OF", KEYC_END }, @@ -139,7 +139,7 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033\033[H", KEYC_HOME|KEYC_META|KEYC_IMPLIED_META }, { "\033\033[F", KEYC_END|KEYC_META|KEYC_IMPLIED_META }, - /* rxvt-style arrow + modifier keys. */ + /* rxvt arrow keys. */ { "\033Oa", KEYC_UP|KEYC_CTRL }, { "\033Ob", KEYC_DOWN|KEYC_CTRL }, { "\033Oc", KEYC_RIGHT|KEYC_CTRL }, @@ -150,7 +150,31 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[c", KEYC_RIGHT|KEYC_SHIFT }, { "\033[d", KEYC_LEFT|KEYC_SHIFT }, - /* rxvt-style function + modifier keys (C = ^, S = $, C-S = @). */ + /* rxvt function keys. */ + { "\033[11~", KEYC_F1 }, + { "\033[12~", KEYC_F2 }, + { "\033[13~", KEYC_F3 }, + { "\033[14~", KEYC_F4 }, + { "\033[15~", KEYC_F5 }, + { "\033[17~", KEYC_F6 }, + { "\033[18~", KEYC_F7 }, + { "\033[19~", KEYC_F8 }, + { "\033[20~", KEYC_F9 }, + { "\033[21~", KEYC_F10 }, + + { "\033[23~", KEYC_F1|KEYC_SHIFT }, + { "\033[24~", KEYC_F2|KEYC_SHIFT }, + { "\033[25~", KEYC_F3|KEYC_SHIFT }, + { "\033[26~", KEYC_F4|KEYC_SHIFT }, + { "\033[28~", KEYC_F5|KEYC_SHIFT }, + { "\033[29~", KEYC_F6|KEYC_SHIFT }, + { "\033[31~", KEYC_F7|KEYC_SHIFT }, + { "\033[32~", KEYC_F8|KEYC_SHIFT }, + { "\033[33~", KEYC_F9|KEYC_SHIFT }, + { "\033[34~", KEYC_F10|KEYC_SHIFT }, + { "\033[23$", KEYC_F11|KEYC_SHIFT }, + { "\033[24$", KEYC_F12|KEYC_SHIFT }, + { "\033[11^", KEYC_F1|KEYC_CTRL }, { "\033[12^", KEYC_F2|KEYC_CTRL }, { "\033[13^", KEYC_F3|KEYC_CTRL }, @@ -163,31 +187,6 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[21^", KEYC_F10|KEYC_CTRL }, { "\033[23^", KEYC_F11|KEYC_CTRL }, { "\033[24^", KEYC_F12|KEYC_CTRL }, - { "\033[2^", KEYC_IC|KEYC_CTRL }, - { "\033[3^", KEYC_DC|KEYC_CTRL }, - { "\033[7^", KEYC_HOME|KEYC_CTRL }, - { "\033[8^", KEYC_END|KEYC_CTRL }, - { "\033[6^", KEYC_NPAGE|KEYC_CTRL }, - { "\033[5^", KEYC_PPAGE|KEYC_CTRL }, - - { "\033[11$", KEYC_F1|KEYC_SHIFT }, - { "\033[12$", KEYC_F2|KEYC_SHIFT }, - { "\033[13$", KEYC_F3|KEYC_SHIFT }, - { "\033[14$", KEYC_F4|KEYC_SHIFT }, - { "\033[15$", KEYC_F5|KEYC_SHIFT }, - { "\033[17$", KEYC_F6|KEYC_SHIFT }, - { "\033[18$", KEYC_F7|KEYC_SHIFT }, - { "\033[19$", KEYC_F8|KEYC_SHIFT }, - { "\033[20$", KEYC_F9|KEYC_SHIFT }, - { "\033[21$", KEYC_F10|KEYC_SHIFT }, - { "\033[23$", KEYC_F11|KEYC_SHIFT }, - { "\033[24$", KEYC_F12|KEYC_SHIFT }, - { "\033[2$", KEYC_IC|KEYC_SHIFT }, - { "\033[3$", KEYC_DC|KEYC_SHIFT }, - { "\033[7$", KEYC_HOME|KEYC_SHIFT }, - { "\033[8$", KEYC_END|KEYC_SHIFT }, - { "\033[6$", KEYC_NPAGE|KEYC_SHIFT }, - { "\033[5$", KEYC_PPAGE|KEYC_SHIFT }, { "\033[11@", KEYC_F1|KEYC_CTRL|KEYC_SHIFT }, { "\033[12@", KEYC_F2|KEYC_CTRL|KEYC_SHIFT }, @@ -201,12 +200,6 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[21@", KEYC_F10|KEYC_CTRL|KEYC_SHIFT }, { "\033[23@", KEYC_F11|KEYC_CTRL|KEYC_SHIFT }, { "\033[24@", KEYC_F12|KEYC_CTRL|KEYC_SHIFT }, - { "\033[2@", KEYC_IC|KEYC_CTRL|KEYC_SHIFT }, - { "\033[3@", KEYC_DC|KEYC_CTRL|KEYC_SHIFT }, - { "\033[7@", KEYC_HOME|KEYC_CTRL|KEYC_SHIFT }, - { "\033[8@", KEYC_END|KEYC_CTRL|KEYC_SHIFT }, - { "\033[6@", KEYC_NPAGE|KEYC_CTRL|KEYC_SHIFT }, - { "\033[5@", KEYC_PPAGE|KEYC_CTRL|KEYC_SHIFT }, /* Focus tracking. */ { "\033[I", KEYC_FOCUS_IN }, @@ -798,6 +791,8 @@ partial_key: /* Get the time period. */ delay = options_get_number(global_options, "escape-time"); + if (delay == 0) + delay = 1; tv.tv_sec = delay / 1000; tv.tv_usec = (delay % 1000) * 1000L; @@ -939,34 +934,16 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, nkey = number; /* Update the modifiers. */ - switch (modifiers) { - case 2: - nkey |= KEYC_SHIFT; - break; - case 3: - nkey |= (KEYC_META|KEYC_IMPLIED_META); - break; - case 4: - nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META); - break; - case 5: - nkey |= KEYC_CTRL; - break; - case 6: - nkey |= (KEYC_SHIFT|KEYC_CTRL); - break; - case 7: - nkey |= (KEYC_META|KEYC_CTRL); - break; - case 8: - nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL); - break; - case 9: - nkey |= (KEYC_META|KEYC_IMPLIED_META); - break; - default: - *key = KEYC_NONE; - break; + if (modifiers > 0) { + modifiers--; + if (modifiers & 1) + nkey |= KEYC_SHIFT; + if (modifiers & 2) + nkey |= (KEYC_META|KEYC_IMPLIED_META); /* Alt */ + if (modifiers & 4) + nkey |= KEYC_CTRL; + if (modifiers & 8) + nkey |= (KEYC_META|KEYC_IMPLIED_META); /* Meta */ } /* @@ -1061,17 +1038,13 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, log_debug("%s: mouse input: %.*s", c->name, (int)*size, buf); /* Check and return the mouse input. */ - if (b < 32) + if (b < MOUSE_PARAM_BTN_OFF || + x < MOUSE_PARAM_POS_OFF || + y < MOUSE_PARAM_POS_OFF) return (-1); - b -= 32; - if (x >= 33) - x -= 33; - else - x = 256 - x; - if (y >= 33) - y -= 33; - else - y = 256 - y; + b -= MOUSE_PARAM_BTN_OFF; + x -= MOUSE_PARAM_POS_OFF; + y -= MOUSE_PARAM_POS_OFF; } else if (buf[2] == '<') { /* Read the three inputs. */ *size = 3; @@ -1118,7 +1091,7 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, /* Type is M for press, m for release. */ sgr_type = ch; if (sgr_type == 'm') - b |= 3; + b = 3; /* * Some terminals (like PuTTY 0.63) mistakenly send @@ -1126,7 +1099,7 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, * Discard it before it reaches any program running inside * tmux. */ - if (sgr_type == 'm' && (sgr_b & 64)) + if (sgr_type == 'm' && MOUSE_WHEEL(sgr_b)) return (-2); } else return (-1); @@ -1154,12 +1127,14 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, * partial. */ static int -tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, - size_t *size) +tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) { - size_t end, terminator, needed; - char *copy, *out; - int outlen; + struct client *c = tty->client; + struct window_pane *wp; + size_t end, terminator, needed; + char *copy, *out; + int outlen; + u_int i; *size = 0; @@ -1204,6 +1179,9 @@ tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, buf += 5; end -= 5; + /* Adjust end so that it points to the start of the terminator. */ + end -= terminator - 1; + /* Get the second argument. */ while (end != 0 && *buf != ';') { buf++; @@ -1214,6 +1192,12 @@ tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, buf++; end--; + /* If we did not request this, ignore it. */ + if (~tty->flags & TTY_OSC52QUERY) + return (0); + tty->flags &= ~TTY_OSC52QUERY; + evtimer_del(&tty->clipboard_timer); + /* It has to be a string so copy it. */ copy = xmalloc(end + 1); memcpy(copy, buf, end); @@ -1229,9 +1213,20 @@ tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, } free(copy); - /* Create a new paste buffer. */ + /* Create a new paste buffer and forward to panes. */ log_debug("%s: %.*s", __func__, outlen, out); - paste_add(NULL, out, outlen); + if (c->flags & CLIENT_CLIPBOARDBUFFER) { + paste_add(NULL, out, outlen); + c->flags &= ~CLIENT_CLIPBOARDBUFFER; + } + for (i = 0; i < c->clipboard_npanes; i++) { + wp = window_pane_find_by_id(c->clipboard_panes[i]); + if (wp != NULL) + input_reply_clipboard(wp->event, out, outlen, "\033\\"); + } + free(c->clipboard_panes); + c->clipboard_panes = NULL; + c->clipboard_npanes = 0; return (0); } @@ -103,6 +103,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, + [TTYC_HLS] = { TTYCODE_STRING, "Hls" }, [TTYC_HOME] = { TTYCODE_STRING, "home" }, [TTYC_HPA] = { TTYCODE_STRING, "hpa" }, [TTYC_ICH1] = { TTYCODE_STRING, "ich1" }, @@ -277,6 +278,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_SMUL] = { TTYCODE_STRING, "smul" }, [TTYC_SMXX] = { TTYCODE_STRING, "smxx" }, [TTYC_SS] = { TTYCODE_STRING, "Ss" }, + [TTYC_SWD] = { TTYCODE_STRING, "Swd" }, [TTYC_SYNC] = { TTYCODE_STRING, "Sync" }, [TTYC_TC] = { TTYCODE_FLAG, "Tc" }, [TTYC_TSL] = { TTYCODE_STRING, "tsl" }, @@ -38,7 +38,7 @@ static int tty_client_ready(struct client *); static void tty_set_italics(struct tty *); static int tty_try_colour(struct tty *, int, const char *); -static void tty_force_cursor_colour(struct tty *, const char *); +static void tty_force_cursor_colour(struct tty *, int); static void tty_cursor_pane(struct tty *, const struct tty_ctx *, u_int, u_int); static void tty_cursor_pane_unless_wrap(struct tty *, @@ -69,8 +69,10 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static void tty_default_attributes(struct tty *, const struct grid_cell *, - struct colour_palette *, u_int); + struct colour_palette *, u_int, struct hyperlinks *); static int tty_check_overlay(struct tty *, u_int, u_int); +static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, + struct overlay_ranges *); #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) @@ -81,6 +83,8 @@ static int tty_check_overlay(struct tty *, u_int, u_int); #define TTY_BLOCK_START(tty) (1 + ((tty)->sx * (tty)->sy) * 8) #define TTY_BLOCK_STOP(tty) (1 + ((tty)->sx * (tty)->sy) / 8) +#define TTY_QUERY_TIMEOUT 5 + void tty_create_log(void) { @@ -103,7 +107,7 @@ tty_init(struct tty *tty, struct client *c) tty->client = c; tty->cstyle = SCREEN_CURSOR_DEFAULT; - tty->ccolour = xstrdup(""); + tty->ccolour = -1; if (tcgetattr(c->fd, &tty->tio) != 0) return (-1); @@ -204,6 +208,11 @@ tty_block_maybe(struct tty *tty) size_t size = EVBUFFER_LENGTH(tty->out); struct timeval tv = { .tv_usec = TTY_BLOCK_INTERVAL }; + if (size == 0) + tty->flags &= ~TTY_NOBLOCK; + else if (tty->flags & TTY_NOBLOCK) + return (0); + if (size < TTY_BLOCK_START(tty)) return (0); @@ -300,7 +309,7 @@ tty_start_tty(struct tty *tty) { struct client *c = tty->client; struct termios tio; - struct timeval tv = { .tv_sec = 1 }; + struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT }; setblocking(c->fd, 0); event_add(&tty->event_in, NULL); @@ -339,8 +348,8 @@ tty_start_tty(struct tty *tty) tty->flags |= TTY_STARTED; tty_invalidate(tty); - if (*tty->ccolour != '\0') - tty_force_cursor_colour(tty, ""); + if (tty->ccolour != -1) + tty_force_cursor_colour(tty, -1); tty->mouse_drag_flag = 0; tty->mouse_drag_update = NULL; @@ -404,7 +413,7 @@ tty_stop_tty(struct tty *tty) } if (tty->mode & MODE_BRACKETPASTE) tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); - if (*tty->ccolour != '\0') + if (tty->ccolour != -1) tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM)); @@ -449,7 +458,6 @@ void tty_free(struct tty *tty) { tty_close(tty); - free(tty->ccolour); } void @@ -647,40 +655,79 @@ tty_set_title(struct tty *tty, const char *title) tty_putcode(tty, TTYC_FSL); } +void +tty_set_path(struct tty *tty, const char *title) +{ + if (!tty_term_has(tty->term, TTYC_SWD) || + !tty_term_has(tty->term, TTYC_FSL)) + return; + + tty_putcode(tty, TTYC_SWD); + tty_puts(tty, title); + tty_putcode(tty, TTYC_FSL); +} + static void -tty_force_cursor_colour(struct tty *tty, const char *ccolour) +tty_force_cursor_colour(struct tty *tty, int c) { - if (*ccolour == '\0') + u_char r, g, b; + char s[13]; + + if (c != -1) + c = colour_force_rgb(c); + if (c == tty->ccolour) + return; + if (c == -1) tty_putcode(tty, TTYC_CR); - else - tty_putcode_ptr1(tty, TTYC_CS, ccolour); - free(tty->ccolour); - tty->ccolour = xstrdup(ccolour); + else { + colour_split_rgb(c, &r, &g, &b); + xsnprintf(s, sizeof s, "rgb:%02hhx/%02hhx/%02hhx", r, g, b); + tty_putcode_ptr1(tty, TTYC_CS, s); + } + tty->ccolour = c; } -static void -tty_update_cursor(struct tty *tty, int mode, int changed, struct screen *s) +static int +tty_update_cursor(struct tty *tty, int mode, struct screen *s) { - enum screen_cursor_style cstyle; + enum screen_cursor_style cstyle; + int ccolour, changed, cmode = mode; /* Set cursor colour if changed. */ - if (s != NULL && strcmp(s->ccolour, tty->ccolour) != 0) - tty_force_cursor_colour(tty, s->ccolour); + if (s != NULL) { + ccolour = s->ccolour; + if (s->ccolour == -1) + ccolour = s->default_ccolour; + tty_force_cursor_colour(tty, ccolour); + } /* If cursor is off, set as invisible. */ - if (~mode & MODE_CURSOR) { - if (changed & MODE_CURSOR) + if (~cmode & MODE_CURSOR) { + if (tty->mode & MODE_CURSOR) tty_putcode(tty, TTYC_CIVIS); - return; + return (cmode); } /* Check if blinking or very visible flag changed or style changed. */ if (s == NULL) cstyle = tty->cstyle; - else + else { cstyle = s->cstyle; + if (cstyle == SCREEN_CURSOR_DEFAULT) { + if (~cmode & MODE_CURSOR_BLINKING_SET) { + if (s->default_mode & MODE_CURSOR_BLINKING) + cmode |= MODE_CURSOR_BLINKING; + else + cmode &= ~MODE_CURSOR_BLINKING; + } + cstyle = s->default_cstyle; + } + } + + /* If nothing changed, do nothing. */ + changed = cmode ^ tty->mode; if ((changed & CURSOR_MODES) == 0 && cstyle == tty->cstyle) - return; + return (cmode); /* * Set cursor style. If an explicit style has been set with DECSCUSR, @@ -698,49 +745,56 @@ tty_update_cursor(struct tty *tty, int mode, int changed, struct screen *s) else tty_putcode1(tty, TTYC_SS, 0); } - if (mode & (MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)) + if (cmode & (MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_BLOCK: if (tty_term_has(tty->term, TTYC_SS)) { - if (mode & MODE_CURSOR_BLINKING) + if (cmode & MODE_CURSOR_BLINKING) tty_putcode1(tty, TTYC_SS, 1); else tty_putcode1(tty, TTYC_SS, 2); - } else if (mode & MODE_CURSOR_BLINKING) + } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_UNDERLINE: if (tty_term_has(tty->term, TTYC_SS)) { - if (mode & MODE_CURSOR_BLINKING) + if (cmode & MODE_CURSOR_BLINKING) tty_putcode1(tty, TTYC_SS, 3); else tty_putcode1(tty, TTYC_SS, 4); - } else if (mode & MODE_CURSOR_BLINKING) + } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_BAR: if (tty_term_has(tty->term, TTYC_SS)) { - if (mode & MODE_CURSOR_BLINKING) + if (cmode & MODE_CURSOR_BLINKING) tty_putcode1(tty, TTYC_SS, 5); else tty_putcode1(tty, TTYC_SS, 6); - } else if (mode & MODE_CURSOR_BLINKING) + } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; } tty->cstyle = cstyle; + return (cmode); } void tty_update_mode(struct tty *tty, int mode, struct screen *s) { + struct tty_term *term = tty->term; struct client *c = tty->client; int changed; if (tty->flags & TTY_NOCURSOR) mode &= ~MODE_CURSOR; + if (tty_update_cursor(tty, mode, s) & MODE_CURSOR_BLINKING) + mode |= MODE_CURSOR_BLINKING; + else + mode &= ~MODE_CURSOR_BLINKING; + changed = mode ^ tty->mode; if (log_get_level() != 0 && changed != 0) { log_debug("%s: current mode %s", c->name, @@ -749,30 +803,21 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) screen_mode_to_string(mode)); } - tty_update_cursor(tty, mode, changed, s); - if ((changed & ALL_MOUSE_MODES) && - tty_term_has(tty->term, TTYC_KMOUS)) { + if ((changed & ALL_MOUSE_MODES) && tty_term_has(term, TTYC_KMOUS)) { /* - * If the mouse modes have changed, clear any that are set and - * apply again. There are differences in how terminals track - * the various bits. + * If the mouse modes have changed, clear then all and apply + * again. There are differences in how terminals track the + * various bits. */ - if (tty->mode & MODE_MOUSE_SGR) - tty_puts(tty, "\033[?1006l"); - if (tty->mode & MODE_MOUSE_STANDARD) - tty_puts(tty, "\033[?1000l"); - if (tty->mode & MODE_MOUSE_BUTTON) - tty_puts(tty, "\033[?1002l"); - if (tty->mode & MODE_MOUSE_ALL) - tty_puts(tty, "\033[?1003l"); + tty_puts(tty, "\033[?1006l\033[?1000l\033[?1002l\033[?1003l"); if (mode & ALL_MOUSE_MODES) tty_puts(tty, "\033[?1006h"); - if (mode & MODE_MOUSE_STANDARD) - tty_puts(tty, "\033[?1000h"); - if (mode & MODE_MOUSE_BUTTON) - tty_puts(tty, "\033[?1002h"); if (mode & MODE_MOUSE_ALL) - tty_puts(tty, "\033[?1003h"); + tty_puts(tty, "\033[?1000h\033[?1002h\033[?1003h"); + else if (mode & MODE_MOUSE_BUTTON) + tty_puts(tty, "\033[?1000h\033[?1002h"); + else if (mode & MODE_MOUSE_STANDARD) + tty_puts(tty, "\033[?1000h"); } if (changed & MODE_BRACKETPASTE) { if (mode & MODE_BRACKETPASTE) @@ -904,7 +949,9 @@ tty_update_window_offset(struct window *w) struct client *c; TAILQ_FOREACH(c, &clients, entry) { - if (c->session != NULL && c->session->curw->window == w) + if (c->session != NULL && + c->session->curw != NULL && + c->session->curw->window == w) tty_update_client_offset(c); } } @@ -1046,8 +1093,9 @@ static void tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, u_int px, u_int nx, u_int bg) { - struct client *c = tty->client; - u_int i; + struct client *c = tty->client; + struct overlay_ranges r; + u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1083,18 +1131,13 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, * Couldn't use an escape sequence, use spaces. Clear only the visible * bit if there is an overlay. */ - for (i = 0; i < nx; i++) { - if (!tty_check_overlay(tty, px + i, py)) - break; - } - tty_cursor(tty, px, py); - tty_repeat_space(tty, i); - for (; i < nx; i++) { - if (tty_check_overlay(tty, px + i, py)) - break; + tty_check_overlay_range(tty, px, py, nx, &r); + for (i = 0; i < OVERLAY_MAX_RANGES; i++) { + if (r.nx[i] == 0) + continue; + tty_cursor(tty, r.px[i], py); + tty_repeat_space(tty, r.nx[i]); } - tty_cursor(tty, px + i, py); - tty_repeat_space(tty, nx - i); } /* Clear a line, adjusting to visible part of pane. */ @@ -1306,14 +1349,44 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc) return (&new); } +/* + * Check if a single character is obstructed by the overlay and return a + * boolean. + */ static int tty_check_overlay(struct tty *tty, u_int px, u_int py) { + struct overlay_ranges r; + + /* + * A unit width range will always return nx[2] == 0 from a check, even + * with multiple overlays, so it's sufficient to check just the first + * two entries. + */ + tty_check_overlay_range(tty, px, py, 1, &r); + if (r.nx[0] + r.nx[1] == 0) + return (0); + return (1); +} + +/* Return parts of the input range which are visible. */ +static void +tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx, + struct overlay_ranges *r) +{ struct client *c = tty->client; - if (c->overlay_check == NULL) - return (1); - return (c->overlay_check(c, c->overlay_data, px, py)); + if (c->overlay_check == NULL) { + r->px[0] = px; + r->nx[0] = nx; + r->px[1] = 0; + r->nx[1] = 0; + r->px[2] = 0; + r->nx[2] = 0; + return; + } + + c->overlay_check(c, c->overlay_data, px, py, nx, r); } void @@ -1326,11 +1399,12 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, const struct grid_cell *gcp; struct grid_line *gl; struct client *c = tty->client; - u_int i, j, ux, sx, width, hidden; + struct overlay_ranges r; + u_int i, j, ux, sx, width, hidden, eux, nxx; + u_int cellsize; int flags, cleared = 0, wrapped = 0; char buf[512]; size_t len; - u_int cellsize; log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, px, py, nx, atx, aty); @@ -1381,7 +1455,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, tty_term_has(tty->term, TTYC_EL1) && !tty_fake_bce(tty, defaults, 8) && c->overlay_check == NULL) { - tty_default_attributes(tty, defaults, palette, 8); + tty_default_attributes(tty, defaults, palette, 8, + s->hyperlinks); tty_cursor(tty, nx - 1, aty); tty_putcode(tty, TTYC_EL1); cleared = 1; @@ -1406,9 +1481,11 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, gcp->fg != last.fg || gcp->bg != last.bg || gcp->us != last.us || + gcp->link != last.link || ux + width + gcp->data.width > nx || (sizeof buf) - len < gcp->data.size)) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared", __func__, len); tty_clear_line(tty, defaults, aty, atx + ux, @@ -1430,33 +1507,37 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, else memcpy(&last, gcp, sizeof last); + tty_check_overlay_range(tty, atx + ux, aty, gcp->data.width, + &r); hidden = 0; - for (j = 0; j < gcp->data.width; j++) { - if (!tty_check_overlay(tty, atx + ux + j, aty)) - hidden++; - } + for (j = 0; j < OVERLAY_MAX_RANGES; j++) + hidden += r.nx[j]; + hidden = gcp->data.width - hidden; if (hidden != 0 && hidden == gcp->data.width) { if (~gcp->flags & GRID_FLAG_PADDING) ux += gcp->data.width; } else if (hidden != 0 || ux + gcp->data.width > nx) { if (~gcp->flags & GRID_FLAG_PADDING) { - tty_attributes(tty, &last, defaults, palette); - tty_cursor(tty, atx + ux, aty); - for (j = 0; j < gcp->data.width; j++) { - if (ux > nx) - break; - if (tty_check_overlay(tty, atx + ux, - aty)) - tty_putc(tty, ' '); - else { - tty_cursor(tty, atx + ux + 1, - aty); + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); + for (j = 0; j < OVERLAY_MAX_RANGES; j++) { + if (r.nx[j] == 0) + continue; + /* Effective width drawn so far. */ + eux = r.px[j] - atx; + if (eux < nx) { + tty_cursor(tty, r.px[j], aty); + nxx = nx - eux; + if (r.nx[j] > nxx) + r.nx[j] = nxx; + tty_repeat_space(tty, r.nx[j]); + ux = eux + r.nx[j]; } - ux++; } } } else if (gcp->attr & GRID_ATTR_CHARSET) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, + s->hyperlinks); tty_cursor(tty, atx + ux, aty); for (j = 0; j < gcp->data.size; j++) tty_putc(tty, gcp->data.data[j]); @@ -1468,7 +1549,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } } if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { - tty_attributes(tty, &last, defaults, palette); + tty_attributes(tty, &last, defaults, palette, s->hyperlinks); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared (end)", __func__, len); tty_clear_line(tty, defaults, aty, atx + ux, width, @@ -1484,7 +1565,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, if (!cleared && ux < nx) { log_debug("%s: %u to end of line (%zu cleared)", __func__, nx - ux, len); - tty_default_attributes(tty, defaults, palette, 8); + tty_default_attributes(tty, defaults, palette, 8, + s->hyperlinks); tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); } @@ -1570,7 +1652,8 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1592,7 +1675,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1602,7 +1686,8 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg); } @@ -1624,7 +1709,8 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1651,7 +1737,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1664,7 +1751,8 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); } @@ -1674,7 +1762,8 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { u_int nx = ctx->sx - ctx->ocx; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); } @@ -1682,7 +1771,8 @@ tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) { - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); } @@ -1708,7 +1798,8 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1739,7 +1830,8 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1779,7 +1871,8 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1819,7 +1912,8 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1838,7 +1932,8 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1862,7 +1957,8 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1886,7 +1982,8 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) { u_int px, py, nx, ny; - tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1909,7 +2006,8 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) return; } - tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette); + tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); @@ -1926,7 +2024,8 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { const struct grid_cell *gcp = ctx->cell; struct screen *s = ctx->s; - u_int i, px, py; + struct overlay_ranges r; + u_int px, py, i, vis = 0; px = ctx->xoff + ctx->ocx - ctx->wox; py = ctx->yoff + ctx->ocy - ctx->woy; @@ -1936,13 +2035,13 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) /* Handle partially obstructed wide characters. */ if (gcp->data.width > 1) { - for (i = 0; i < gcp->data.width; i++) { - if (!tty_check_overlay(tty, px + i, py)) { - tty_draw_line(tty, s, s->cx, s->cy, - gcp->data.width, px, py, &ctx->defaults, - ctx->palette); - return; - } + tty_check_overlay_range(tty, px, py, gcp->data.width, &r); + for (i = 0; i < OVERLAY_MAX_RANGES; i++) + vis += r.nx[i]; + if (vis < gcp->data.width) { + tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width, + px, py, &ctx->defaults, ctx->palette); + return; } } @@ -1954,13 +2053,16 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette); + tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette, + ctx->s->hyperlinks); } void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - u_int i, hide = 0; + struct overlay_ranges r; + u_int i, px, py, cx; + char *cp = ctx->ptr; if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, ctx->num, 1)) return; @@ -1983,30 +2085,32 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); - tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette); - for (i = 0; i < ctx->num; i++) { - if (!tty_check_overlay(tty, tty->cx + i, tty->cy)) - break; - } - tty_putn(tty, ctx->ptr, i, i); - for (; i < ctx->num; i++) { - if (tty_check_overlay(tty, tty->cx + hide, tty->cy)) - break; - hide++; + /* Get tty position from pane position for overlay check. */ + px = ctx->xoff + ctx->ocx - ctx->wox; + py = ctx->yoff + ctx->ocy - ctx->woy; + + tty_check_overlay_range(tty, px, py, ctx->num, &r); + for (i = 0; i < OVERLAY_MAX_RANGES; i++) { + if (r.nx[i] == 0) + continue; + /* Convert back to pane position for printing. */ + cx = r.px[i] - ctx->xoff + ctx->wox; + tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy); + tty_putn(tty, cp + r.px[i] - px, r.nx[i], r.nx[i]); } - tty_cursor(tty, tty->cx + hide, tty->cy); - tty_putn(tty, (char *)ctx->ptr + i, ctx->num - i, ctx->num - i); } void tty_cmd_setselection(struct tty *tty, const struct tty_ctx *ctx) { - tty_set_selection(tty, ctx->ptr, ctx->num); + tty_set_selection(tty, ctx->ptr2, ctx->ptr, ctx->num); } void -tty_set_selection(struct tty *tty, const char *buf, size_t len) +tty_set_selection(struct tty *tty, const char *flags, const char *buf, + size_t len) { char *encoded; size_t size; @@ -2020,8 +2124,8 @@ tty_set_selection(struct tty *tty, const char *buf, size_t len) encoded = xmalloc(size); b64_ntop(buf, len, encoded, size); - tty_putcode_ptr2(tty, TTYC_MS, "", encoded); - tty->client->redraw = EVBUFFER_LENGTH(tty->out); + tty->flags |= TTY_NOBLOCK; + tty_putcode_ptr2(tty, TTYC_MS, flags, encoded); free(encoded); } @@ -2029,6 +2133,7 @@ tty_set_selection(struct tty *tty, const char *buf, size_t len) void tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) { + tty->flags |= TTY_NOBLOCK; tty_add(tty, ctx->ptr, ctx->num); tty_invalidate(tty); } @@ -2054,7 +2159,8 @@ tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) void tty_cell(struct tty *tty, const struct grid_cell *gc, - const struct grid_cell *defaults, struct colour_palette *palette) + const struct grid_cell *defaults, struct colour_palette *palette, + struct hyperlinks *hl) { const struct grid_cell *gcp; @@ -2070,11 +2176,11 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); - tty_attributes(tty, gcp, defaults, palette); + tty_attributes(tty, gcp, defaults, palette, hl); /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { - tty_attributes(tty, gcp, defaults, palette); + tty_attributes(tty, gcp, defaults, palette, hl); if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) return; tty_putc(tty, *gcp->data.data); @@ -2091,6 +2197,8 @@ tty_reset(struct tty *tty) struct grid_cell *gc = &tty->cell; if (!grid_cells_equal(gc, &grid_default_cell)) { + if (gc->link != 0) + tty_putcode_ptr2(tty, TTYC_HLS, "", ""); if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_RMACS); tty_putcode(tty, TTYC_SGR0); @@ -2243,17 +2351,25 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) if (tty->flags & TTY_BLOCK) return; - if (cx > tty->sx - 1) - cx = tty->sx - 1; - thisx = tty->cx; thisy = tty->cy; + /* + * If in the automargin space, and want to be there, do not move. + * Otherwise, force the cursor to be in range (and complain). + */ + if (cx == thisx && cy == thisy && cx == tty->sx) + return; + if (cx > tty->sx - 1) { + log_debug("%s: x too big %u > %u", __func__, cx, tty->sx - 1); + cx = tty->sx - 1; + } + /* No change. */ if (cx == thisx && cy == thisy) return; - /* Very end of the line, just use absolute movement. */ + /* Currently at the very end of the line - use absolute movement. */ if (thisx > tty->sx - 1) goto absolute; @@ -2372,9 +2488,29 @@ out: tty->cy = cy; } +static void +tty_hyperlink(struct tty *tty, const struct grid_cell *gc, + struct hyperlinks *hl) +{ + const char *uri, *id; + + if (gc->link == tty->cell.link) + return; + tty->cell.link = gc->link; + + if (hl == NULL) + return; + + if (gc->link == 0 || !hyperlinks_get(hl, gc->link, &uri, NULL, &id)) + tty_putcode_ptr2(tty, TTYC_HLS, "", ""); + else + tty_putcode_ptr2(tty, TTYC_HLS, id, uri); +} + void tty_attributes(struct tty *tty, const struct grid_cell *gc, - const struct grid_cell *defaults, struct colour_palette *palette) + const struct grid_cell *defaults, struct colour_palette *palette, + struct hyperlinks *hl) { struct grid_cell *tc = &tty->cell, gc2; int changed; @@ -2392,7 +2528,8 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, if (gc2.attr == tty->last_cell.attr && gc2.fg == tty->last_cell.fg && gc2.bg == tty->last_cell.bg && - gc2.us == tty->last_cell.us) + gc2.us == tty->last_cell.us && + gc2.link == tty->last_cell.link) return; /* @@ -2469,6 +2606,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_SMACS); + /* Set hyperlink if any. */ + tty_hyperlink(tty, gc, hl); + memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell); } @@ -2834,11 +2974,39 @@ tty_default_colours(struct grid_cell *gc, struct window_pane *wp) static void tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, - struct colour_palette *palette, u_int bg) + struct colour_palette *palette, u_int bg, struct hyperlinks *hl) { struct grid_cell gc; memcpy(&gc, &grid_default_cell, sizeof gc); gc.bg = bg; - tty_attributes(tty, &gc, defaults, palette); + tty_attributes(tty, &gc, defaults, palette, hl); +} + +static void +tty_clipboard_query_callback(__unused int fd, __unused short events, void *data) +{ + struct tty *tty = data; + struct client *c = tty->client; + + c->flags &= ~CLIENT_CLIPBOARDBUFFER; + free(c->clipboard_panes); + c->clipboard_panes = NULL; + c->clipboard_npanes = 0; + + tty->flags &= ~TTY_OSC52QUERY; +} + +void +tty_clipboard_query(struct tty *tty) +{ + struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT }; + + if ((~tty->flags & TTY_STARTED) || (tty->flags & TTY_OSC52QUERY)) + return; + tty_putcode_ptr2(tty, TTYC_MS, "", "?"); + + tty->flags |= TTY_OSC52QUERY; + evtimer_set(&tty->clipboard_timer, tty_clipboard_query_callback, tty); + evtimer_add(&tty->clipboard_timer, &tv); } @@ -237,21 +237,6 @@ utf8_width(struct utf8_data *ud, int *width) if (*width >= 0 && *width <= 0xff) return (UTF8_DONE); log_debug("UTF-8 %.*s, wcwidth() %d", (int)ud->size, ud->data, *width); - -#ifndef __OpenBSD__ - /* - * Many platforms (particularly and inevitably OS X) have no width for - * relatively common characters (wcwidth() returns -1); assume width 1 - * in this case. This will be wrong for genuinely nonprintable - * characters, but they should be rare. We may pass through stuff that - * ideally we would block, but this is no worse than sending the same - * to the terminal without tmux. - */ - if (*width < 0) { - *width = 1; - return (UTF8_DONE); - } -#endif return (UTF8_ERROR); } diff --git a/window-buffer.c b/window-buffer.c index 07851e75..2f52ee3f 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -308,7 +308,7 @@ window_buffer_get_key(void *modedata, void *itemdata, u_int line) } pb = paste_get_name(item->name); if (pb == NULL) - return KEYC_NONE; + return (KEYC_NONE); ft = format_create(NULL, NULL, FORMAT_NONE, 0); format_defaults(ft, NULL, NULL, 0, NULL); @@ -320,7 +320,7 @@ window_buffer_get_key(void *modedata, void *itemdata, u_int line) key = key_string_lookup_string(expanded); free(expanded); format_free(ft); - return key; + return (key); } static struct screen * diff --git a/window-client.c b/window-client.c index 00f36c7c..8d501b0d 100644 --- a/window-client.c +++ b/window-client.c @@ -281,7 +281,7 @@ window_client_get_key(void *modedata, void *itemdata, u_int line) key = key_string_lookup_string(expanded); free(expanded); format_free(ft); - return key; + return (key); } static struct screen * diff --git a/window-copy.c b/window-copy.c index c1a31b48..b955d222 100644 --- a/window-copy.c +++ b/window-copy.c @@ -222,6 +222,8 @@ struct window_copy_mode_data { struct screen *backing; int backing_written; /* backing display started */ + struct screen *writing; + struct input_ctx *ictx; int viewmode; /* view mode entered */ @@ -467,13 +469,16 @@ window_copy_view_init(struct window_mode_entry *wme, struct window_pane *wp = wme->wp; struct window_copy_mode_data *data; struct screen *base = &wp->base; - struct screen *s; + u_int sx = screen_size_x(base); data = window_copy_common_init(wme); data->viewmode = 1; - data->backing = s = xmalloc(sizeof *data->backing); - screen_init(s, screen_size_x(base), screen_size_y(base), UINT_MAX); + data->backing = xmalloc(sizeof *data->backing); + screen_init(data->backing, sx, screen_size_y(base), UINT_MAX); + data->writing = xmalloc(sizeof *data->writing); + screen_init(data->writing, sx, screen_size_y(base), 0); + data->ictx = input_init(NULL, NULL, NULL); data->mx = data->cx; data->my = screen_hsize(data->backing) + data->cy - data->oy; data->showmark = 0; @@ -492,6 +497,12 @@ window_copy_free(struct window_mode_entry *wme) free(data->searchstr); free(data->jumpchar); + if (data->writing != NULL) { + screen_free(data->writing); + free(data->writing); + } + if (data->ictx != NULL) + input_free(data->ictx); screen_free(data->backing); free(data->backing); @@ -500,41 +511,67 @@ window_copy_free(struct window_mode_entry *wme) } void -window_copy_add(struct window_pane *wp, const char *fmt, ...) +window_copy_add(struct window_pane *wp, int parse, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - window_copy_vadd(wp, fmt, ap); + window_copy_vadd(wp, parse, fmt, ap); va_end(ap); } +static void +window_copy_init_ctx_cb(__unused struct screen_write_ctx *ctx, + struct tty_ctx *ttyctx) +{ + memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); + ttyctx->palette = NULL; + ttyctx->redraw_cb = NULL; + ttyctx->set_client_cb = NULL; + ttyctx->arg = NULL; +} + void -window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) +window_copy_vadd(struct window_pane *wp, int parse, const char *fmt, va_list ap) { struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); struct window_copy_mode_data *data = wme->data; struct screen *backing = data->backing; - struct screen_write_ctx back_ctx, ctx; + struct screen *writing = data->writing; + struct screen_write_ctx writing_ctx, backing_ctx, ctx; struct grid_cell gc; u_int old_hsize, old_cy; + u_int sx = screen_size_x(backing); + char *text; - memcpy(&gc, &grid_default_cell, sizeof gc); + if (parse) { + vasprintf(&text, fmt, ap); + screen_write_start(&writing_ctx, writing); + screen_write_reset(&writing_ctx); + input_parse_screen(data->ictx, writing, window_copy_init_ctx_cb, + data, text, strlen(text)); + free(text); + } old_hsize = screen_hsize(data->backing); - screen_write_start(&back_ctx, backing); + screen_write_start(&backing_ctx, backing); if (data->backing_written) { /* * On the second or later line, do a CRLF before writing * (so it's on a new line). */ - screen_write_carriagereturn(&back_ctx); - screen_write_linefeed(&back_ctx, 0, 8); + screen_write_carriagereturn(&backing_ctx); + screen_write_linefeed(&backing_ctx, 0, 8); } else data->backing_written = 1; old_cy = backing->cy; - screen_write_vnputs(&back_ctx, 0, &gc, fmt, ap); - screen_write_stop(&back_ctx); + if (parse) + screen_write_fast_copy(&backing_ctx, writing, 0, 0, sx, 1); + else { + memcpy(&gc, &grid_default_cell, sizeof gc); + screen_write_vnputs(&backing_ctx, 0, &gc, fmt, ap); + } + screen_write_stop(&backing_ctx); data->oy += screen_hsize(data->backing) - old_hsize; @@ -1273,6 +1310,16 @@ window_copy_cmd_halfpage_up(struct window_copy_cmd_state *cs) } static enum window_copy_cmd_action +window_copy_cmd_toggle_position(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->hide_position = !data->hide_position; + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action window_copy_cmd_history_bottom(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; @@ -2817,6 +2864,12 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_stop_selection }, + { .command = "toggle-position", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_toggle_position + }, { .command = "top-line", .minargs = 0, .maxargs = 0, @@ -3148,6 +3201,11 @@ window_copy_cellstring(const struct grid_line *gl, u_int px, size_t *size, } utf8_to_data(gl->extddata[gce->offset].data, &ud); + if (ud.size == 0) { + *size = 0; + *allocated = 0; + return (NULL); + } *size = ud.size; *allocated = 1; @@ -4034,8 +4092,9 @@ window_copy_write_line(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct options *oo = wp->window->options; + struct grid_line *gl; struct grid_cell gc, mgc, cgc, mkgc; - char hdr[512]; + char hdr[512], tmp[256], *t; size_t size = 0; u_int hsize = screen_hsize(data->backing); @@ -4049,23 +4108,29 @@ window_copy_write_line(struct window_mode_entry *wme, mkgc.flags |= GRID_FLAG_NOPALETTE; if (py == 0 && s->rupper < s->rlower && !data->hide_position) { + gl = grid_get_line(data->backing->grid, hsize - data->oy); + if (gl->time == 0) + xsnprintf(tmp, sizeof tmp, "[%u/%u]", data->oy, hsize); + else { + t = format_pretty_time(gl->time, 1); + xsnprintf(tmp, sizeof tmp, "%s [%u/%u]", t, data->oy, + hsize); + free(t); + } + if (data->searchmark == NULL) { if (data->timeout) { size = xsnprintf(hdr, sizeof hdr, - "(timed out) [%u/%u]", data->oy, hsize); - } else { - size = xsnprintf(hdr, sizeof hdr, - "[%u/%u]", data->oy, hsize); - } + "(timed out) %s", tmp); + } else + size = xsnprintf(hdr, sizeof hdr, "%s", tmp); } else { - if (data->searchcount == -1) { - size = xsnprintf(hdr, sizeof hdr, - "[%u/%u]", data->oy, hsize); - } else { + if (data->searchcount == -1) + size = xsnprintf(hdr, sizeof hdr, "%s", tmp); + else { size = xsnprintf(hdr, sizeof hdr, - "(%d%s results) [%u/%u]", data->searchcount, - data->searchmore ? "+" : "", data->oy, - hsize); + "(%d%s results) %s", data->searchcount, + data->searchmore ? "+" : "", tmp); } } if (size > screen_size_x(s)) @@ -4512,7 +4577,7 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, if (options_get_number(global_options, "set-clipboard") != 0) { screen_write_start_pane(&ctx, wp, NULL); - screen_write_setselection(&ctx, buf, len); + screen_write_setselection(&ctx, "", buf, len); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); } @@ -4531,8 +4596,8 @@ window_copy_pipe_run(struct window_mode_entry *wme, struct session *s, if (cmd == NULL || *cmd == '\0') cmd = options_get_string(global_options, "copy-command"); if (cmd != NULL && *cmd != '\0') { - job = job_run(cmd, 0, NULL, s, NULL, NULL, NULL, NULL, NULL, - JOB_NOWAIT, -1, -1); + job = job_run(cmd, 0, NULL, NULL, s, NULL, NULL, NULL, NULL, + NULL, JOB_NOWAIT, -1, -1); bufferevent_write(job_get_event(job), buf, *len); } return (buf); @@ -4586,7 +4651,7 @@ window_copy_append_selection(struct window_mode_entry *wme) if (options_get_number(global_options, "set-clipboard") != 0) { screen_write_start_pane(&ctx, wp, NULL); - screen_write_setselection(&ctx, buf, len); + screen_write_setselection(&ctx, "", buf, len); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); } diff --git a/window-customize.c b/window-customize.c index 0f09eba8..4a16e90c 100644 --- a/window-customize.c +++ b/window-customize.c @@ -398,11 +398,11 @@ window_customize_build_options(struct window_customize_modedata *data, for (i = 0; i < size; i++) { if (oo2 != NULL) - o = options_get(oo0, list[i]); + o = options_get(oo2, list[i]); if (o == NULL && oo1 != NULL) o = options_get(oo1, list[i]); if (o == NULL) - o = options_get(oo2, list[i]); + o = options_get(oo0, list[i]); if (options_owner(o) == oo2) scope = scope2; else if (options_owner(o) == oo1) @@ -680,9 +680,7 @@ window_customize_draw_option(struct window_customize_modedata *data, } ft = format_create_from_state(NULL, NULL, &fs); - if (oe == NULL) - text = "This is a user option."; - else if (oe->text == NULL) + if (oe == NULL || oe->text == NULL) text = "This option doesn't have a description."; else text = oe->text; diff --git a/window-tree.c b/window-tree.c index 4998e1aa..1be47017 100644 --- a/window-tree.c +++ b/window-tree.c @@ -272,9 +272,10 @@ window_tree_cmp_window(const void *a0, const void *b0) static int window_tree_cmp_pane(const void *a0, const void *b0) { - const struct window_pane *const *a = a0; - const struct window_pane *const *b = b0; - int result; + struct window_pane **a = (struct window_pane **)a0; + struct window_pane **b = (struct window_pane **)b0; + int result; + u_int ai, bi; if (window_tree_sort->field == WINDOW_TREE_BY_TIME) result = (*a)->active_point - (*b)->active_point; @@ -283,7 +284,9 @@ window_tree_cmp_pane(const void *a0, const void *b0) * Panes don't have names, so use number order for any other * sort field. */ - result = (*a)->id - (*b)->id; + window_pane_index(*a, &ai); + window_pane_index(*b, &bi); + result = ai - bi; } if (window_tree_sort->reversed) result = -result; @@ -519,7 +522,8 @@ window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py, if (ox > 1 && ox + len < sx - 1 && sy >= 3) { screen_write_cursormove(ctx, px + ox - 1, py + oy - 1, 0); - screen_write_box(ctx, len + 2, 3); + screen_write_box(ctx, len + 2, 3, BOX_LINES_DEFAULT, NULL, + NULL); } screen_write_cursormove(ctx, px + ox, py + oy, 0); screen_write_puts(ctx, gc, "%s", label); @@ -894,7 +898,7 @@ window_tree_get_key(void *modedata, void *itemdata, u_int line) key = key_string_lookup_string(expanded); free(expanded); format_free(ft); - return key; + return (key); } static struct screen * @@ -1242,12 +1246,17 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, item = mode_tree_get_current(data->data); finished = mode_tree_key(data->data, c, &key, m, &x, &y); + +again: if (item != (new_item = mode_tree_get_current(data->data))) { item = new_item; data->offset = 0; } - if (KEYC_IS_MOUSE(key) && m != NULL) + if (KEYC_IS_MOUSE(key) && m != NULL) { key = window_tree_mouse(data, key, x, item); + goto again; + } + switch (key) { case '<': data->offset--; @@ -329,6 +329,7 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) w->id = next_window_id++; RB_INSERT(windows, &windows, w); + window_set_fill_character(w); window_update_activity(w); log_debug("%s: @%u create %ux%u (%ux%u)", __func__, w->id, sx, sy, @@ -360,6 +361,7 @@ window_destroy(struct window *w) event_del(&w->offset_timer); options_free(w->options); + free(w->fill_character); free(w->name); free(w); @@ -761,6 +763,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) if (w->active != NULL) { w->active->flags |= PANE_CHANGED; notify_window("window-pane-changed", w); + window_update_focus(w); } } else if (wp == w->last) w->last = NULL; @@ -937,6 +940,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) screen_init(&wp->base, sx, sy, hlimit); wp->screen = &wp->base; + window_pane_default_cursor(wp); screen_init(&wp->status_screen, 1, 1, 0); @@ -1053,7 +1057,7 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) if (sx == wp->sx && sy == wp->sy) return; - r = xmalloc (sizeof *r); + r = xmalloc(sizeof *r); r->sx = sx; r->sy = sy; r->osx = wp->sx; @@ -1598,3 +1602,34 @@ window_pane_update_used_data(struct window_pane *wp, size = EVBUFFER_LENGTH(wp->event->input) - used; wpo->used += size; } + +void +window_set_fill_character(struct window *w) +{ + const char *value; + struct utf8_data *ud; + + free(w->fill_character); + w->fill_character = NULL; + + value = options_get_string(w->options, "fill-character"); + if (*value != '\0' && utf8_isvalid(value)) { + ud = utf8_fromcstr(value); + if (ud != NULL && ud[0].width == 1) + w->fill_character = ud; + } +} + +void +window_pane_default_cursor(struct window_pane *wp) +{ + struct screen *s = wp->screen; + int c; + + c = options_get_number(wp->options, "cursor-colour"); + s->default_ccolour = c; + + c = options_get_number(wp->options, "cursor-style"); + s->default_mode = 0; + screen_set_cursor_style(c, &s->default_cstyle, &s->default_mode); +} |