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 diff --git a/CHANGES b/CHANGES index 159ab905..e5942216 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,62 @@ +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). @@ -1245,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 @@ -3221,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 fdfc8d42..9bbd21d4 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 \ image.c \ @@ -173,6 +175,7 @@ dist_tmux_SOURCES = \ screen-redraw.c \ screen-write.c \ screen.c \ + server-acl.c \ server-client.c \ server-fn.c \ server.c \ diff --git a/arguments.c b/arguments.c index 91c470e2..3315cef2 100644 --- a/arguments.c +++ b/arguments.c @@ -831,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) { @@ -842,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, @@ -854,6 +895,10 @@ args_percentage(struct args *args, u_char flag, long long minval, *cause = xstrdup("missing"); return (0); } + if (TAILQ_EMPTY(&entry->values)) { + *cause = xstrdup("empty"); + return (0); + } value = TAILQ_LAST(&entry->values, args_values)->string; return (args_string_percentage(value, minval, maxval, curval, cause)); } @@ -868,6 +913,10 @@ args_string_percentage(const char *value, long long minval, long long maxval, size_t valuelen = strlen(value); char *copy; + if (valuelen == 0) { + *cause = xstrdup("empty"); + return (0); + } if (value[valuelen - 1] == '%') { copy = xstrdup(value); copy[valuelen - 1] = '\0'; @@ -898,3 +947,74 @@ 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); + } + if (TAILQ_EMPTY(&entry->values)) { + *cause = xstrdup("empty"); + 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); +} diff --git a/cfg.c b/cfg.c index 5b64c2aa..5109c8c9 100644 --- a/cfg.c +++ b/cfg.c @@ -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; diff --git a/client.c b/client.c index 08708c21..df6cee94 100644 --- a/client.c +++ b/client.c @@ -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) { 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-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-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,23 +98,30 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; + /* 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 { + if (type == LAYOUT_TOPBOTTOM) + curval = dst_wp->sy; + else + curval = dst_wp->sx; + } + } + size = -1; if (args_has(args, 'l')) { - if (type == LAYOUT_TOPBOTTOM) { - size = args_percentage(args, 'l', 0, INT_MAX, - dst_wp->sy, &cause); - } else { - size = args_percentage(args, 'l', 0, INT_MAX, - dst_wp->sx, &cause); - } + 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, 100, &cause); - if (cause == NULL) { - if (type == LAYOUT_TOPBOTTOM) - size = (dst_wp->sy * percentage) / 100; - else - size = (dst_wp->sx * percentage) / 100; - } + 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); 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 aac23a0f..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) { 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 #include +#include #include #include #include +#include #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 b2665ad9..6b947280 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -185,7 +185,7 @@ cmd_refresh_client_clipboard(struct cmd *self, struct cmdq_item *item) } if (i != tc->clipboard_npanes) return (CMD_RETURN_NORMAL); - tc->clipboard_panes = xreallocarray (tc->clipboard_panes, + tc->clipboard_panes = xreallocarray(tc->clipboard_panes, tc->clipboard_npanes + 1, sizeof *tc->clipboard_panes); tc->clipboard_panes[tc->clipboard_npanes++] = fs.wp->id; } diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 5e914e65..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 @@ -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 d6a95434..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); 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 + * + * 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 +#include + +#include +#include +#include +#include +#include + +#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-split-window.c b/cmd-split-window.c index 9947dfd3..637bad30 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -65,67 +65,46 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) 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 (args_has(args, 'f')) { - if (type == LAYOUT_TOPBOTTOM) - size = (w->sy * percentage) / 100; - else - size = (w->sx * percentage) / 100; - } else { - if (type == LAYOUT_TOPBOTTOM) - size = (wp->sy * percentage) / 100; - else - size = (wp->sx * percentage) / 100; - } - } 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); - } - } - } 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 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 = (w->sy * percentage) / 100; + curval = w->sy; else - size = (w->sx * percentage) / 100; + curval = w->sx; } else { if (type == LAYOUT_TOPBOTTOM) - size = (wp->sy * percentage) / 100; + curval = wp->sy; else - size = (wp->sx * percentage) / 100; + curval = wp->sx; } - } else - size = -1; + } + + 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); + 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)) diff --git a/cmd.c b/cmd.c index f8027dfe..ed4f9940 100644 --- a/cmd.c +++ b/cmd.c @@ -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, diff --git a/compat/getpeereid.c b/compat/getpeereid.c index 5a593c07..b79f420a 100644 --- a/compat/getpeereid.c +++ b/compat/getpeereid.c @@ -18,6 +18,7 @@ #include #include +#include #ifdef HAVE_UCRED_H #include @@ -38,9 +39,6 @@ getpeereid(int s, uid_t *uid, gid_t *gid) *gid = uc.gid; return (0); #elif defined(HAVE_GETPEERUCRED) -int -getpeereid(int s, uid_t *uid, gid_t *gid) -{ ucred_t *ucred = NULL; if (getpeerucred(s, &ucred) == -1) @@ -51,9 +49,9 @@ getpeereid(int s, uid_t *uid, gid_t *gid) return (-1); ucred_free(ucred); return (0); -} #else - errno = EOPNOTSUPP; - return (-1); + *uid = geteuid(); + *gid = getegid(); + return (0); #endif } diff --git a/configure.ac b/configure.ac index 276f71c8..8e846042 100644 --- a/configure.ac +++ b/configure.ac @@ -267,6 +267,12 @@ if test "x$found_libevent" = xno; then AC_MSG_ERROR("libevent not found") fi +# Look for yacc. +AC_CHECK_PROG(found_yacc, $YACC, yes, no) +if test "x$found_yacc" = xno; then + AC_MSG_ERROR("yacc not found") +fi + # Look for ncurses or curses. Try pkg-config first then directly for the # library. PKG_CHECK_MODULES( diff --git a/control-notify.c b/control-notify.c index 6ff0e436..a252dd05 100644 --- a/control-notify.c +++ b/control-notify.c @@ -234,3 +234,16 @@ control_notify_session_window_changed(struct session *s) s->curw->window->id); } } + +void +control_notify_paste_buffer_changed(const char *name) +{ + struct client *c; + + TAILQ_FOREACH(c, &clients, entry) { + if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) + continue; + + control_write(c, "%%paste-changed %s", name); + } +} diff --git a/control.c b/control.c index 73286e00..578d04cb 100644 --- a/control.c +++ b/control.c @@ -775,13 +775,16 @@ 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 (cs->read_event == NULL) + fatalx("out of memory"); if (c->flags & CLIENT_CONTROLCONTROL) cs->write_event = cs->read_event; else { cs->write_event = bufferevent_new(c->out_fd, NULL, control_write_callback, control_error_callback, c); + if (cs->write_event == NULL) + fatalx("out of memory"); } bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, 0); @@ -792,6 +795,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/environ.c b/environ.c index 74d672e0..5abf383c 100644 --- a/environ.c +++ b/environ.c @@ -182,9 +182,11 @@ void environ_update(struct options *oo, struct environ *src, struct environ *dst) { struct environ_entry *envent; + struct environ_entry *envent1; struct options_entry *o; struct options_array_item *a; union options_value *ov; + int found; o = options_get(oo, "update-environment"); if (o == NULL) @@ -192,14 +194,15 @@ environ_update(struct options *oo, struct environ *src, struct environ *dst) a = options_array_first(o); while (a != NULL) { ov = options_array_item_value(a); - RB_FOREACH(envent, environ, src) { - if (fnmatch(ov->string, envent->name, 0) == 0) - break; + found = 0; + RB_FOREACH_SAFE(envent, environ, src, envent1) { + if (fnmatch(ov->string, envent->name, 0) == 0) { + environ_set(dst, envent->name, 0, "%s", envent->value); + found = 1; + } } - if (envent == NULL) + if (!found) environ_clear(dst, ov->string); - else - environ_set(dst, envent->name, 0, "%s", envent->value); a = options_array_next(a); } } diff --git a/file.c b/file.c index b2f155fe..04a907bf 100644 --- a/file.c +++ b/file.c @@ -585,6 +585,8 @@ file_write_open(struct client_files *files, struct tmuxpeer *peer, cf->event = bufferevent_new(cf->fd, NULL, file_write_callback, file_write_error_callback, cf); + if (cf->event == NULL) + fatalx("out of memory"); bufferevent_enable(cf->event, EV_WRITE); goto reply; @@ -744,6 +746,8 @@ file_read_open(struct client_files *files, struct tmuxpeer *peer, cf->event = bufferevent_new(cf->fd, file_read_callback, NULL, file_read_error_callback, cf); + if (cf->event == NULL) + fatalx("out of memory"); bufferevent_enable(cf->event, EV_READ); return; diff --git a/format.c b/format.c index 2e4bceb5..8041f728 100644 --- a/format.c +++ b/format.c @@ -801,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) @@ -1131,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) @@ -2597,7 +2630,7 @@ format_cb_user(__unused struct format_tree *ft) if ((pw = getpwuid(getuid())) != NULL) return (xstrdup(pw->pw_name)); - return NULL; + return (NULL); } /* Format table type. */ @@ -2775,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 }, @@ -2898,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 }, @@ -3370,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) @@ -3387,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)); } @@ -3492,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); @@ -3522,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); @@ -4586,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]; @@ -4681,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); @@ -4715,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); @@ -5040,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)); } diff --git a/grid.c b/grid.c index 1109ac58..b1afd398 100644 --- a/grid.c +++ b/grid.c @@ -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. */ @@ -876,18 +885,47 @@ grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc, } } +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; @@ -977,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) { @@ -1011,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 @@ -1037,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 + * Copyright (c) 2022 Jeff Chiang + * + * 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 + +#include +#include + +#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 feb62f6d..ebf61333 100644 --- a/input-keys.c +++ b/input-keys.c @@ -606,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; diff --git a/input.c b/input.c index 39441dd1..347e57ab 100644 --- a/input.c +++ b/input.c @@ -135,6 +135,7 @@ 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 *); @@ -1078,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); @@ -1798,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 @@ -2235,25 +2241,30 @@ input_enter_dcs(struct input_ctx *ictx) static int input_dcs_dispatch(struct input_ctx *ictx) { - struct screen_write_ctx *sctx = &ictx->ctx; struct window_pane *wp = ictx->wp; + struct options *oo = wp->options; + struct screen_write_ctx *sctx = &ictx->ctx; struct window *w = wp->window; u_char *buf = ictx->input_buf; size_t len = ictx->input_len; const char prefix[] = "tmux;"; const u_int prefixlen = (sizeof prefix) - 1; struct sixel_image *si; + long long allow_passthrough = 0; if (wp == NULL) return (0); if (ictx->flags & INPUT_DISCARD) return (0); - if (!options_get_number(ictx->wp->options, "allow-passthrough")) + allow_passthrough = options_get_number(oo, "allow-passthrough"); + if (!allow_passthrough) return (0); log_debug("%s: \"%s\"", __func__, buf); - if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) - screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen); + if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) { + screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen, + allow_passthrough == 2); + } if (buf[0] == 'q') { si = sixel_parse(buf, len, w->xpixel, w->ypixel); @@ -2297,6 +2308,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++; @@ -2321,6 +2334,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; @@ -2565,6 +2581,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) @@ -2698,6 +2755,9 @@ input_osc_52(struct input_ctx *ictx, const char *p) 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; @@ -2712,6 +2772,12 @@ 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) buf = paste_buffer_data(pb, &len); @@ -2733,7 +2799,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); diff --git a/key-bindings.c b/key-bindings.c index 95171966..528e0b73 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}" \ " ''" \ @@ -341,7 +344,7 @@ key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data) void key_bindings_init(void) { - static const char *defaults[] = { + static const char *const defaults[] = { /* Prefix keys. */ "bind -N 'Send the prefix key' C-b { send-prefix }", "bind -N 'Rotate through the panes' C-o { rotate-window }", @@ -602,6 +605,7 @@ key_bindings_init(void) "bind -Tcopy-mode-vi h { send -X cursor-left }", "bind -Tcopy-mode-vi j { send -X cursor-down }", "bind -Tcopy-mode-vi k { send -X cursor-up }", + "bind -Tcopy-mode-vi z { send -X scroll-middle }", "bind -Tcopy-mode-vi l { send -X cursor-right }", "bind -Tcopy-mode-vi n { send -X search-again }", "bind -Tcopy-mode-vi o { send -X other-end }", @@ -613,6 +617,8 @@ key_bindings_init(void) "bind -Tcopy-mode-vi '{' { send -X previous-paragraph }", "bind -Tcopy-mode-vi '}' { send -X next-paragraph }", "bind -Tcopy-mode-vi % { send -X next-matching-bracket }", + "bind -Tcopy-mode-vi Home { send -X start-of-line }", + "bind -Tcopy-mode-vi End { send -X end-of-line }", "bind -Tcopy-mode-vi MouseDown1Pane { select-pane }", "bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }", "bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }", 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); diff --git a/log.c b/log.c index d4436672..0e0d1d1a 100644 --- a/log.c +++ b/log.c @@ -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); diff --git a/menu.c b/menu.c index c17d10b1..4aad1d8c 100644 --- a/menu.c +++ b/menu.c @@ -56,7 +56,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, { struct menu_item *new_item; const char *key = NULL, *cmd, *suffix = ""; - char *s, *name; + char *s, *trimmed, *name; u_int width, max_width; int line; size_t keylen, slen; @@ -103,11 +103,13 @@ menu_add_item(struct menu *menu, const struct menu_item *item, max_width--; suffix = ">"; } - if (key != NULL) - xasprintf(&name, "%.*s%s#[default] #[align=right](%s)", - (int)max_width, s, suffix, key); - else - xasprintf(&name, "%.*s%s", (int)max_width, s, 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", trimmed, suffix); + free(trimmed); new_item->name = name; free(s); @@ -158,11 +160,16 @@ 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); } @@ -316,27 +323,64 @@ menu_key_cb(struct client *c, void *data, struct key_event *event) } while ((name == NULL || *name == '-') && md->choice != old); c->flags |= CLIENT_REDRAWOVERLAY; return (0); - case 'g': case KEYC_PPAGE: case '\002': /* C-b */ - if (md->choice > 5) - md->choice -= 5; - else + if (md->choice < 6) md->choice = 0; - while (md->choice != count && (name == NULL || *name == '-')) + else { + i = 5; + while (i > 0) { + md->choice--; + name = menu->items[md->choice].name; + if (md->choice != 0 && + (name != NULL && *name != '-')) + i--; + else if (md->choice == 0) + break; + } + } + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case KEYC_NPAGE: + if (md->choice > count - 6) { + md->choice = count - 1; + name = menu->items[md->choice].name; + } else { + i = 5; + while (i > 0) { + md->choice++; + name = menu->items[md->choice].name; + if (md->choice != count - 1 && + (name != NULL && *name != '-')) + i++; + else if (md->choice == count - 1) + break; + } + } + while (name == NULL || *name == '-') { + md->choice--; + name = menu->items[md->choice].name; + } + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case 'g': + case KEYC_HOME: + md->choice = 0; + name = menu->items[md->choice].name; + while (name == NULL || *name == '-') { md->choice++; - if (md->choice == count) - md->choice = -1; + name = menu->items[md->choice].name; + } c->flags |= CLIENT_REDRAWOVERLAY; break; case 'G': - case KEYC_NPAGE: - if (md->choice > count - 6) - md->choice = count - 1; - else - md->choice += 5; - while (md->choice != -1 && (name == NULL || *name == '-')) + case KEYC_END: + md->choice = count - 1; + name = menu->items[md->choice].name; + while (name == NULL || *name == '-') { md->choice--; + name = menu->items[md->choice].name; + } c->flags |= CLIENT_REDRAWOVERLAY; break; case '\006': /* C-f */ diff --git a/notify.c b/notify.c index baeb0600..dd20898c 100644 --- a/notify.c +++ b/notify.c @@ -32,6 +32,7 @@ struct notify_entry { struct session *session; struct window *window; int pane; + const char *pbname; }; static struct cmdq_item * @@ -46,7 +47,7 @@ notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne, if (log_get_level() != 0) { s = cmd_list_print(cmdlist, 0); log_debug("%s: hook %s is: %s", __func__, ne->name, s); - free (s); + free(s); } new_item = cmdq_get_command(cmdlist, state); return (cmdq_insert_after(item, new_item)); @@ -149,6 +150,8 @@ notify_callback(struct cmdq_item *item, void *data) control_notify_session_closed(ne->session); if (strcmp(ne->name, "session-window-changed") == 0) control_notify_session_window_changed(ne->session); + if (strcmp(ne->name, "paste-buffer-changed") == 0) + control_notify_paste_buffer_changed(ne->pbname); notify_insert_hook(item, ne); @@ -164,6 +167,7 @@ notify_callback(struct cmdq_item *item, void *data) format_free(ne->formats); free((void *)ne->name); + free((void *)ne->pbname); free(ne); return (CMD_RETURN_NORMAL); @@ -171,7 +175,8 @@ notify_callback(struct cmdq_item *item, void *data) static void notify_add(const char *name, struct cmd_find_state *fs, struct client *c, - struct session *s, struct window *w, struct window_pane *wp) + struct session *s, struct window *w, struct window_pane *wp, + const char *pbname) { struct notify_entry *ne; struct cmdq_item *item; @@ -187,6 +192,7 @@ notify_add(const char *name, struct cmd_find_state *fs, struct client *c, ne->session = s; ne->window = w; ne->pane = (wp != NULL ? wp->id : -1); + ne->pbname = (pbname != NULL ? xstrdup(pbname) : NULL); ne->formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS); format_add(ne->formats, "hook", "%s", name); @@ -248,7 +254,7 @@ notify_client(const char *name, struct client *c) struct cmd_find_state fs; cmd_find_from_client(&fs, c, 0); - notify_add(name, &fs, c, NULL, NULL, NULL); + notify_add(name, &fs, c, NULL, NULL, NULL, NULL); } void @@ -260,7 +266,7 @@ notify_session(const char *name, struct session *s) cmd_find_from_session(&fs, s, 0); else cmd_find_from_nothing(&fs, 0); - notify_add(name, &fs, NULL, s, NULL, NULL); + notify_add(name, &fs, NULL, s, NULL, NULL, NULL); } void @@ -269,7 +275,7 @@ notify_winlink(const char *name, struct winlink *wl) struct cmd_find_state fs; cmd_find_from_winlink(&fs, wl, 0); - notify_add(name, &fs, NULL, wl->session, wl->window, NULL); + notify_add(name, &fs, NULL, wl->session, wl->window, NULL, NULL); } void @@ -278,7 +284,7 @@ notify_session_window(const char *name, struct session *s, struct window *w) struct cmd_find_state fs; cmd_find_from_session_window(&fs, s, w, 0); - notify_add(name, &fs, NULL, s, w, NULL); + notify_add(name, &fs, NULL, s, w, NULL, NULL); } void @@ -287,7 +293,7 @@ notify_window(const char *name, struct window *w) struct cmd_find_state fs; cmd_find_from_window(&fs, w, 0); - notify_add(name, &fs, NULL, NULL, w, NULL); + notify_add(name, &fs, NULL, NULL, w, NULL, NULL); } void @@ -296,5 +302,14 @@ notify_pane(const char *name, struct window_pane *wp) struct cmd_find_state fs; cmd_find_from_pane(&fs, wp, 0); - notify_add(name, &fs, NULL, NULL, NULL, wp); + notify_add(name, &fs, NULL, NULL, NULL, wp, NULL); +} + +void +notify_paste_buffer(const char *pbname) +{ + struct cmd_find_state fs; + + cmd_find_clear_state(&fs, 0); + notify_add("paste-buffer-changed", &fs, NULL, NULL, NULL, NULL, pbname); } diff --git a/options-table.c b/options-table.c index 17be7ec4..f7e91da1 100644 --- a/options-table.c +++ b/options-table.c @@ -87,6 +87,9 @@ static const char *options_table_detach_on_destroy_list[] = { static const char *options_table_extended_keys_list[] = { "off", "on", "always", NULL }; +static const char *options_table_allow_passthrough_list[] = { + "off", "on", "all", NULL +}; /* Status line format. */ #define OPTIONS_TABLE_STATUS_FORMAT1 \ @@ -362,7 +365,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." @@ -802,11 +806,14 @@ const struct options_table_entry options_table[] = { }, { .name = "allow-passthrough", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .choices = options_table_allow_passthrough_list, .default_num = 0, .text = "Whether applications are allowed to use the escape sequence " - "to bypass tmux." + "to bypass tmux. Can be 'off' (disallowed), 'on' (allowed " + "if the pane is visible), or 'all' (allowed even if the pane " + "is invisible)." }, { .name = "allow-rename", diff --git a/options.c b/options.c index f9cf2afd..fef5637e 100644 --- a/options.c +++ b/options.c @@ -1106,7 +1106,6 @@ options_push_changes(const char *name) struct session *s; struct window *w; struct window_pane *wp; - int c; log_debug("%s: %s", __func__, name); @@ -1119,18 +1118,12 @@ options_push_changes(const char *name) } } if (strcmp(name, "cursor-colour") == 0) { - RB_FOREACH(wp, window_pane_tree, &all_window_panes) { - c = options_get_number(wp->options, name); - wp->screen->default_ccolour = c; - } + 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) { - wp->screen->default_mode = 0; - screen_set_cursor_style(options_get_number(wp->options, - name), &wp->screen->default_cstyle, - &wp->screen->default_mode); - } + 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) diff --git a/paste.c b/paste.c index 51ae2b1d..51c6b751 100644 --- a/paste.c +++ b/paste.c @@ -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) @@ -142,6 +150,8 @@ paste_get_name(const char *name) void paste_free(struct paste_buffer *pb) { + notify_paste_buffer(pb->name); + RB_REMOVE(paste_name_tree, &paste_by_name, pb); RB_REMOVE(paste_time_tree, &paste_by_time, pb); if (pb->automatic) @@ -198,6 +208,8 @@ paste_add(const char *prefix, char *data, size_t size) pb->order = paste_next_order++; RB_INSERT(paste_name_tree, &paste_by_name, pb); RB_INSERT(paste_time_tree, &paste_by_time, pb); + + notify_paste_buffer(pb->name); } /* Rename a paste buffer. */ @@ -245,6 +257,9 @@ paste_rename(const char *oldname, const char *newname, char **cause) RB_INSERT(paste_name_tree, &paste_by_name, pb); + notify_paste_buffer(oldname); + notify_paste_buffer(newname); + return (0); } @@ -293,6 +308,8 @@ paste_set(char *data, size_t size, const char *name, char **cause) RB_INSERT(paste_name_tree, &paste_by_name, pb); RB_INSERT(paste_time_tree, &paste_by_time, pb); + notify_paste_buffer(name); + return (0); } @@ -303,6 +320,8 @@ paste_replace(struct paste_buffer *pb, char *data, size_t size) free(pb->data); pb->data = data; pb->size = size; + + notify_paste_buffer(pb->name); } /* Convert start of buffer into a nice string. */ diff --git a/proc.c b/proc.c index a9b1473e..67ec214a 100644 --- a/proc.c +++ b/proc.c @@ -202,7 +202,7 @@ proc_start(const char *name) #endif , event_get_version(), event_get_method() #ifdef HAVE_UTF8PROC - , utf8proc_version () + , utf8proc_version() #endif ); @@ -349,6 +349,12 @@ proc_kill_peer(struct tmuxpeer *peer) peer->flags |= PEER_BAD; } +void +proc_flush_peer(struct tmuxpeer *peer) +{ + imsg_flush(&peer->ibuf); +} + void proc_toggle_log(struct tmuxproc *tp) { 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 <$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 <$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 diff --git a/screen-redraw.c b/screen-redraw.c index 5e67f7c4..06ed8990 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -738,7 +738,7 @@ screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) } } - tty_cell(tty, &gc, &grid_default_cell, NULL); + 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 b517bbdd..439d0532 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2143,12 +2143,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); @@ -2156,13 +2158,15 @@ screen_write_setselection(struct screen_write_ctx *ctx, u_char *str, u_int len) /* Write unmodified string. */ void -screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len) +screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len, + int allow_invisible_panes) { struct tty_ctx ttyctx; screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; ttyctx.num = len; + ttyctx.allow_invisible_panes = allow_invisible_panes; tty_write(tty_cmd_rawstring, &ttyctx); } diff --git a/screen.c b/screen.c index ca5f8574..2ee86b0f 100644 --- a/screen.c +++ b/screen.c @@ -90,6 +90,7 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) TAILQ_INIT(&s->images); s->write_list = NULL; + s->hyperlinks = NULL; screen_reinit(s); } @@ -104,7 +105,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; @@ -120,6 +121,17 @@ screen_reinit(struct screen *s) screen_clear_selection(s); screen_free_titles(s); image_free_all(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. */ @@ -138,6 +150,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); image_free_all(s); } @@ -666,9 +680,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 + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#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 cea2e691..e86426a5 100644 --- a/server-client.c +++ b/server-client.c @@ -2218,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) @@ -2772,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) @@ -2780,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); } @@ -2796,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; @@ -2834,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); @@ -3072,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); @@ -3142,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.c b/server.c index 5b70dcd0..6fef468b 100644 --- a/server.c +++ b/server.c @@ -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 *); @@ -211,7 +213,6 @@ 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 @@ -245,6 +246,8 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, 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); @@ -261,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) { @@ -361,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)) @@ -380,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; + } } /* diff --git a/spawn.c b/spawn.c index c8ac1bd8..e63133fe 100644 --- a/spawn.c +++ b/spawn.c @@ -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"); /* diff --git a/status.c b/status.c index 8d702de1..929276d2 100644 --- a/status.c +++ b/status.c @@ -1576,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; @@ -1594,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) { @@ -1615,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); @@ -1624,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); } diff --git a/style.c b/style.c index 89a4e63a..8407dc68 100644 --- a/style.c +++ b/style.c @@ -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, diff --git a/tmux.1 b/tmux.1 index b0ed13a3..ed327479 100644 --- a/tmux.1 +++ b/tmux.1 @@ -217,8 +217,6 @@ that is set does not contain .Qq UTF-8 or .Qq UTF8 . -This is equivalent to -.Fl T Ar UTF-8 . .It Fl T Ar features Set terminal features for the client. This is a comma-separated list of features. @@ -1488,6 +1486,44 @@ option. .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 @@ -1775,6 +1811,7 @@ The following commands are supported in copy mode: .It Li "search-forward " Ta "/" Ta "" .It Li "search-forward-incremental " Ta "" Ta "C-s" .It Li "search-forward-text " Ta "" Ta "" +.It Li "scroll-middle" Ta "z" Ta "" .It Li "search-reverse" Ta "N" Ta "N" .It Li "select-line" Ta "V" Ta "" .It Li "select-word" Ta "" Ta "" @@ -2077,9 +2114,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 @@ -2161,7 +2200,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 @@ -3621,6 +3661,14 @@ 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 @@ -4417,11 +4465,17 @@ Available pane options are: .Pp .Bl -tag -width Ds -compact .It Xo Ic allow-passthrough -.Op Ic on | off +.Op Ic on | off | all .Xc Allow programs in the pane to bypass .Nm using a terminal escape sequence (\eePtmux;...\ee\e\e). +If set to +.Ic on , +passthrough sequences will be allowed only if the pane is visible. +If set to +.Ic all , +they will be allowed even if the pane is invisible. .Pp .It Xo Ic allow-rename .Op Ic on | off @@ -5072,7 +5126,7 @@ 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" @@ -5117,6 +5171,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" @@ -5157,6 +5212,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)" @@ -5583,11 +5639,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 Pq alias: Ic clrphist +.D1 Pq alias: Ic clearphist Clear status prompt history for prompt type .Ar prompt-type . If @@ -5841,7 +5897,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. @@ -6062,7 +6118,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 . @@ -6080,9 +6137,14 @@ 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 +.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 Pq alias: Ic deleteb @@ -6370,6 +6432,12 @@ 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 \&Nobr +Tell +.Nm +that the terminal does not use bright colors for bold display. .It Em \&Rect Tell .Nm @@ -6489,6 +6557,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 @@ -6532,6 +6602,10 @@ escapes non-printable characters and backslash as octal \\xxx. The pane with ID .Ar pane-id has changed mode. +.It Ic %paste-buffer-changed Ar name +Paste buffer +.Ar name +has been changed. .It Ic %pause Ar pane-id The pane has been paused (if the .Ar pause-after diff --git a/tmux.c b/tmux.c index 11c368ff..b9f2be30 100644 --- a/tmux.c +++ b/tmux.c @@ -325,7 +325,7 @@ find_home(void) const char * getversion(void) { - return TMUX_VERSION; + return (TMUX_VERSION); } int diff --git a/tmux.h b/tmux.h index 1f0bd726..52852817 100644 --- a/tmux.h +++ b/tmux.h @@ -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; @@ -367,6 +369,7 @@ enum tty_code_code { TTYC_ENFCS, TTYC_ENMG, TTYC_FSL, + TTYC_HLS, TTYC_HOME, TTYC_HPA, TTYC_ICH, @@ -513,6 +516,7 @@ enum tty_code_code { TTYC_KUP6, TTYC_KUP7, TTYC_MS, + TTYC_NOBR, TTYC_OL, TTYC_OP, TTYC_RECT, @@ -583,6 +587,12 @@ enum tty_code_code { #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; @@ -686,6 +696,7 @@ struct grid_cell { int fg; int bg; int us; + u_int link; }; /* Grid extended cell entry. */ @@ -696,6 +707,7 @@ struct grid_extd_entry { int fg; int bg; int us; + u_int link; } __packed; /* Grid cell entry. */ @@ -722,6 +734,7 @@ struct grid_line { u_int extdsize; int flags; + time_t time; }; /* Entire grid of cells. */ @@ -862,6 +875,8 @@ struct screen { struct images images; struct screen_write_cline *write_list; + + struct hyperlinks *hyperlinks; }; /* Screen write context. */ @@ -1436,39 +1451,47 @@ struct tty_ctx { u_int num; void *ptr; int more; + void *ptr2; + + /* + * Whether this command should be sent even when the pane is not + * visible (used for a passthrough sequence when allow-passthrough is + * "all"). + */ + int allow_invisible_panes; /* * Cursor and region position before the screen was updated - this is * where the command should be applied; the values in the screen have * already been updated. */ - u_int ocx; - u_int ocy; + u_int ocx; + u_int ocy; - u_int orupper; - u_int orlower; + u_int orupper; + u_int orlower; /* Target region (usually pane) offset and size. */ - u_int xoff; - u_int yoff; - u_int rxoff; - u_int ryoff; - u_int sx; - u_int sy; + u_int xoff; + u_int yoff; + u_int rxoff; + u_int ryoff; + u_int sx; + u_int sy; /* The background colour used for clearing (erasing). */ - u_int bg; + u_int bg; /* The default colours and palette. */ - struct grid_cell defaults; - struct colour_palette *palette; + struct grid_cell defaults; + struct colour_palette *palette; /* Containing region (usually window) offset and size. */ - int bigger; - u_int wox; - u_int woy; - u_int wsx; - u_int wsy; + int bigger; + u_int wox; + u_int woy; + u_int wsx; + u_int wsy; }; /* Saved message entry. */ @@ -2045,6 +2068,7 @@ 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 *); @@ -2071,6 +2095,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 *); @@ -2107,6 +2132,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 *, @@ -2129,6 +2155,8 @@ 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 */ @@ -2147,6 +2175,7 @@ void notify_winlink(const char *, struct winlink *); void notify_session_window(const char *, struct session *, struct window *); void notify_window(const char *, struct window *); void notify_pane(const char *, struct window_pane *); +void notify_paste_buffer(const char *); /* options.c */ struct options *options_create(struct options *); @@ -2256,7 +2285,8 @@ 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 *); @@ -2273,7 +2303,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); @@ -2292,7 +2323,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 *); @@ -2398,10 +2429,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 *, @@ -2595,6 +2632,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); @@ -2769,7 +2807,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); @@ -2882,8 +2920,10 @@ 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_rawstring(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, + int); void screen_write_sixelimage(struct screen_write_ctx *, struct sixel_image *, u_int); void screen_write_alternateon(struct screen_write_ctx *, @@ -2900,6 +2940,7 @@ 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_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 *); @@ -3007,6 +3048,7 @@ void *window_pane_get_new_data(struct window_pane *, 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 *); @@ -3043,7 +3085,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 *); @@ -3108,8 +3150,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 *); @@ -3127,6 +3170,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 *); @@ -3157,6 +3201,7 @@ void control_notify_session_renamed(struct session *); void control_notify_session_created(struct session *); void control_notify_session_closed(struct session *); void control_notify_session_window_changed(struct session *); +void control_notify_paste_buffer_changed(const char *); /* session.c */ extern struct sessions sessions; @@ -3311,4 +3356,24 @@ char *sixel_print(struct sixel_image *, struct sixel_image *, size_t *); struct screen *sixel_to_screen(struct sixel_image *); +/* 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 5a9c79e0..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 @@ -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 diff --git a/tty-features.c b/tty-features.c index 4d83a465..8ed61e9a 100644 --- a/tty-features.c +++ b/tty-features.c @@ -21,6 +21,12 @@ #include #include +#if defined(HAVE_CURSES_H) +#include +#elif defined(HAVE_NCURSES_H) +#include +#endif + #include "tmux.h" /* @@ -36,13 +42,13 @@ /* A named terminal feature. */ struct tty_feature { - const char *name; - const char **capabilities; - int flags; + const char *name; + const char *const *capabilities; + int flags; }; /* Terminal has xterm(1) title setting. */ -static const char *tty_feature_title_capabilities[] = { +static const char *const tty_feature_title_capabilities[] = { "tsl=\\E]0;", /* should be using TS really */ "fsl=\\a", NULL @@ -54,7 +60,7 @@ static const struct tty_feature tty_feature_title = { }; /* Terminal has OSC 7 working directory. */ -static const char *tty_feature_osc7_capabilities[] = { +static const char *const tty_feature_osc7_capabilities[] = { "Swd=\\E]7;", "fsl=\\a", NULL @@ -66,7 +72,7 @@ static const struct tty_feature tty_feature_osc7 = { }; /* Terminal has mouse support. */ -static const char *tty_feature_mouse_capabilities[] = { +static const char *const tty_feature_mouse_capabilities[] = { "kmous=\\E[M", NULL }; @@ -77,7 +83,7 @@ static const struct tty_feature tty_feature_mouse = { }; /* Terminal can set the clipboard with OSC 52. */ -static const char *tty_feature_clipboard_capabilities[] = { +static const char *const tty_feature_clipboard_capabilities[] = { "Ms=\\E]52;%p1%s;%p2%s\\a", NULL }; @@ -87,12 +93,27 @@ 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 * 256 palette. */ -static const char *tty_feature_rgb_capabilities[] = { +static const char *const tty_feature_rgb_capabilities[] = { "AX", "setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm", "setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm", @@ -107,7 +128,7 @@ static const struct tty_feature tty_feature_rgb = { }; /* Terminal supports 256 colours. */ -static const char *tty_feature_256_capabilities[] = { +static const char *const tty_feature_256_capabilities[] = { "AX", "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", @@ -120,7 +141,7 @@ static const struct tty_feature tty_feature_256 = { }; /* Terminal supports overline. */ -static const char *tty_feature_overline_capabilities[] = { +static const char *const tty_feature_overline_capabilities[] = { "Smol=\\E[53m", NULL }; @@ -131,7 +152,7 @@ static const struct tty_feature tty_feature_overline = { }; /* Terminal supports underscore styles. */ -static const char *tty_feature_usstyle_capabilities[] = { +static const char *const tty_feature_usstyle_capabilities[] = { "Smulx=\\E[4::%p1%dm", "Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m", "ol=\\E[59m", @@ -144,7 +165,7 @@ static const struct tty_feature tty_feature_usstyle = { }; /* Terminal supports bracketed paste. */ -static const char *tty_feature_bpaste_capabilities[] = { +static const char *const tty_feature_bpaste_capabilities[] = { "Enbp=\\E[?2004h", "Dsbp=\\E[?2004l", NULL @@ -156,7 +177,7 @@ static const struct tty_feature tty_feature_bpaste = { }; /* Terminal supports focus reporting. */ -static const char *tty_feature_focus_capabilities[] = { +static const char *const tty_feature_focus_capabilities[] = { "Enfcs=\\E[?1004h", "Dsfcs=\\E[?1004l", NULL @@ -168,7 +189,7 @@ static const struct tty_feature tty_feature_focus = { }; /* Terminal supports cursor styles. */ -static const char *tty_feature_cstyle_capabilities[] = { +static const char *const tty_feature_cstyle_capabilities[] = { "Ss=\\E[%p1%d q", "Se=\\E[2 q", NULL @@ -180,7 +201,7 @@ static const struct tty_feature tty_feature_cstyle = { }; /* Terminal supports cursor colours. */ -static const char *tty_feature_ccolour_capabilities[] = { +static const char *const tty_feature_ccolour_capabilities[] = { "Cs=\\E]12;%p1%s\\a", "Cr=\\E]112\\a", NULL @@ -192,7 +213,7 @@ static const struct tty_feature tty_feature_ccolour = { }; /* Terminal supports strikethrough. */ -static const char *tty_feature_strikethrough_capabilities[] = { +static const char *const tty_feature_strikethrough_capabilities[] = { "smxx=\\E[9m", NULL }; @@ -203,7 +224,7 @@ static const struct tty_feature tty_feature_strikethrough = { }; /* Terminal supports synchronized updates. */ -static const char *tty_feature_sync_capabilities[] = { +static const char *const tty_feature_sync_capabilities[] = { "Sync=\\EP=%p1%ds\\E\\\\", NULL }; @@ -214,7 +235,7 @@ static const struct tty_feature tty_feature_sync = { }; /* Terminal supports extended keys. */ -static const char *tty_feature_extkeys_capabilities[] = { +static const char *const tty_feature_extkeys_capabilities[] = { "Eneks=\\E[>4;1m", "Dseks=\\E[>4m", NULL @@ -226,7 +247,7 @@ static const struct tty_feature tty_feature_extkeys = { }; /* Terminal supports DECSLRM margins. */ -static const char *tty_feature_margins_capabilities[] = { +static const char *const tty_feature_margins_capabilities[] = { "Enmg=\\E[?69h", "Dsmg=\\E[?69l", "Clmg=\\E[s", @@ -240,7 +261,7 @@ static const struct tty_feature tty_feature_margins = { }; /* Terminal supports DECFRA rectangle fill. */ -static const char *tty_feature_rectfill_capabilities[] = { +static const char *const tty_feature_rectfill_capabilities[] = { "Rect", NULL }; @@ -250,15 +271,91 @@ static const struct tty_feature tty_feature_rectfill = { TERM_DECFRA }; +/* Use builtin function keys only. */ +static const char *const 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[] = { +static const struct tty_feature *const 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, @@ -323,9 +420,9 @@ tty_get_features(int feat) int tty_apply_features(struct tty_term *term, int feat) { - const struct tty_feature *tf; - const char **capability; - u_int i; + const struct tty_feature *tf; + const char *const *capability; + u_int i; if (feat == 0) return (0); @@ -356,7 +453,7 @@ tty_apply_features(struct tty_term *term, int feat) void tty_default_features(int *feat, const char *name, u_int version) { - static struct { + static const struct { const char *name; u_int version; const char *features; @@ -369,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,usstyle,sync" + ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" }, { .name = "XTerm", /* diff --git a/tty-keys.c b/tty-keys.c index 02372aa1..1e8cd99c 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -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; diff --git a/tty-term.c b/tty-term.c index 1acd2dda..c514030a 100644 --- a/tty-term.c +++ b/tty-term.c @@ -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" }, @@ -249,6 +250,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_KUP6] = { TTYCODE_STRING, "kUP6" }, [TTYC_KUP7] = { TTYCODE_STRING, "kUP7" }, [TTYC_MS] = { TTYCODE_STRING, "Ms" }, + [TTYC_NOBR] = { TTYCODE_STRING, "Nobr" }, [TTYC_OL] = { TTYCODE_STRING, "ol" }, [TTYC_OP] = { TTYCODE_STRING, "op" }, [TTYC_RECT] = { TTYCODE_STRING, "Rect" }, diff --git a/tty.c b/tty.c index c631e5ad..1fe367de 100644 --- a/tty.c +++ b/tty.c @@ -69,7 +69,7 @@ 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 *); @@ -671,7 +671,7 @@ static void tty_force_cursor_colour(struct tty *tty, int c) { u_char r, g, b; - char s[13] = ""; + char s[13]; if (c != -1) c = colour_force_rgb(c); @@ -814,7 +814,7 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) tty_puts(tty, "\033[?1006h"); if (mode & MODE_MOUSE_ALL) tty_puts(tty, "\033[?1000h\033[?1002h\033[?1003h"); - if (mode & MODE_MOUSE_BUTTON) + else if (mode & MODE_MOUSE_BUTTON) tty_puts(tty, "\033[?1000h\033[?1002h"); else if (mode & MODE_MOUSE_STANDARD) tty_puts(tty, "\033[?1000h"); @@ -1455,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; @@ -1480,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, @@ -1515,7 +1518,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, 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_attributes(tty, &last, defaults, palette, + s->hyperlinks); for (j = 0; j < OVERLAY_MAX_RANGES; j++) { if (r.nx[j] == 0) continue; @@ -1532,7 +1536,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } } } 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]); @@ -1544,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, @@ -1560,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); } @@ -1652,13 +1658,20 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), if (ctx->set_client_cb == NULL) return; TAILQ_FOREACH(c, &clients, entry) { - if (!tty_client_ready(c)) - continue; - state = ctx->set_client_cb(ctx, c); - if (state == -1) - break; - if (state == 0) - continue; + if (ctx->allow_invisible_panes) { + if (c->session == NULL || + c->tty.term == NULL || + c->flags & CLIENT_SUSPENDED) + continue; + } else { + if (!tty_client_ready(c)) + continue; + state = ctx->set_client_cb(ctx, c); + if (state == -1) + break; + if (state == 0) + continue; + } cmdfn(&c->tty, ctx); } } @@ -1678,7 +1691,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); @@ -1700,7 +1714,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); @@ -1710,7 +1725,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); } @@ -1732,7 +1748,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); @@ -1759,7 +1776,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); @@ -1772,7 +1790,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); } @@ -1782,7 +1801,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); } @@ -1790,7 +1810,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); } @@ -1816,7 +1837,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); @@ -1847,7 +1869,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); @@ -1887,7 +1910,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); @@ -1927,7 +1951,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); @@ -1946,7 +1971,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); @@ -1970,7 +1996,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); @@ -1994,7 +2021,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); @@ -2017,7 +2045,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); @@ -2063,7 +2092,8 @@ 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 @@ -2094,7 +2124,7 @@ 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); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette, ctx->s->hyperlinks); /* Get tty position from pane position for overlay check. */ px = ctx->xoff + ctx->ocx - ctx->wox; @@ -2114,11 +2144,12 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) 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; @@ -2133,7 +2164,7 @@ tty_set_selection(struct tty *tty, const char *buf, size_t len) b64_ntop(buf, len, encoded, size); tty->flags |= TTY_NOBLOCK; - tty_putcode_ptr2(tty, TTYC_MS, "", encoded); + tty_putcode_ptr2(tty, TTYC_MS, flags, encoded); free(encoded); } @@ -2208,7 +2239,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; @@ -2224,11 +2256,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); @@ -2245,6 +2277,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); @@ -2534,9 +2568,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; @@ -2554,7 +2608,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; /* @@ -2631,6 +2686,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); } @@ -2705,12 +2763,14 @@ tty_check_fg(struct tty *tty, struct colour_palette *palette, /* * Perform substitution if this pane has a palette. If the bright - * attribute is set, use the bright entry in the palette by changing to - * the aixterm colour. + * attribute is set and Nobr is not present, use the bright entry in + * the palette by changing to the aixterm colour */ if (~gc->flags & GRID_FLAG_NOPALETTE) { c = gc->fg; - if (c < 8 && gc->attr & GRID_ATTR_BRIGHT) + if (c < 8 && + gc->attr & GRID_ATTR_BRIGHT && + !tty_term_has(tty->term, TTYC_NOBR)) c += 90; if ((c = colour_palette_get(palette, c)) != -1) gc->fg = c; @@ -2996,13 +3056,13 @@ 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 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 7c55b0f2..743364d6 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; @@ -1213,6 +1250,32 @@ window_copy_cmd_cursor_right(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int mid_value, oy, delta; + int scroll_up; /* >0 up, <0 down */ + + mid_value = (screen_size_y(&data->screen) - 1) / 2; + scroll_up = data->cy - mid_value; + delta = abs(scroll_up); + oy = screen_hsize(data->backing) + data->cy - data->oy; + + log_debug ("XXX %u %u %u %d %u", mid_value, oy, delta, scroll_up, data->oy); + if (scroll_up > 0 && data->oy >= delta) { + window_copy_scroll_up(wme, delta); + data->cy -= delta; + } else if (scroll_up < 0 && oy >= delta) { + window_copy_scroll_down(wme, delta); + data->cy += delta; + } + + window_copy_update_selection(wme, 0, 0); + return (WINDOW_COPY_CMD_REDRAW); +} + static enum window_copy_cmd_action window_copy_cmd_cursor_up(struct window_copy_cmd_state *cs) { @@ -2743,6 +2806,12 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_down_and_cancel }, + { .command = "scroll-middle", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_middle + }, { .command = "scroll-up", .minargs = 0, .maxargs = 0, @@ -3575,6 +3644,8 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) data->searchall = 0; } else visible_only = (strcmp(wp->searchstr, str) == 0); + if (visible_only == 0 && data->searchmark != NULL) + window_copy_clear_marks(wme); free(wp->searchstr); wp->searchstr = xstrdup(str); wp->searchregex = regex; @@ -3634,6 +3705,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) if (direction && window_copy_search_mark_at(data, fx, fy, &at) == 0 && at > 0 && + data->searchmark != NULL && data->searchmark[at] == data->searchmark[at - 1]) { window_copy_move_after_search_mark(data, &fx, &fy, wrapflag); @@ -3666,6 +3738,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) &start) == 0) { while (window_copy_search_mark_at(data, fx, fy, &at) == 0 && + data->searchmark != NULL && data->searchmark[at] == data->searchmark[start]) { data->cx = fx; @@ -4055,8 +4128,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); @@ -4070,23 +4144,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) { + if (data->searchcount == -1) + size = xsnprintf(hdr, sizeof hdr, "%s", tmp); + else { size = xsnprintf(hdr, sizeof hdr, - "[%u/%u]", data->oy, hsize); - } 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)) @@ -4533,7 +4613,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); } @@ -4607,7 +4687,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-tree.c b/window-tree.c index 77011ea2..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; @@ -895,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 * @@ -1243,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--; diff --git a/window.c b/window.c index bce8913a..fe184bb4 100644 --- a/window.c +++ b/window.c @@ -940,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); @@ -1042,6 +1043,8 @@ window_pane_set_event(struct window_pane *wp) wp->event = bufferevent_new(wp->fd, window_pane_read_callback, NULL, window_pane_error_callback, wp); + if (wp->event == NULL) + fatalx("out of memory"); wp->ictx = input_init(wp, wp->event, &wp->palette); bufferevent_enable(wp->event, EV_READ|EV_WRITE); @@ -1056,7 +1059,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; @@ -1618,3 +1621,17 @@ window_set_fill_character(struct window *w) 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); +}