diff options
Diffstat (limited to '')
-rw-r--r-- | CHANGES | 115 | ||||
-rw-r--r-- | Makefile.am | 10 | ||||
-rw-r--r-- | Makefile.in | 266 | ||||
-rw-r--r-- | arguments.c | 343 | ||||
-rw-r--r-- | cfg.c | 47 | ||||
-rw-r--r-- | client.c | 29 | ||||
-rw-r--r-- | cmd-attach-session.c | 3 | ||||
-rw-r--r-- | cmd-break-pane.c | 1 | ||||
-rw-r--r-- | cmd-capture-pane.c | 32 | ||||
-rw-r--r-- | cmd-choose-tree.c | 2 | ||||
-rw-r--r-- | cmd-command-prompt.c | 7 | ||||
-rw-r--r-- | cmd-confirm-before.c | 42 | ||||
-rw-r--r-- | cmd-display-menu.c | 55 | ||||
-rw-r--r-- | cmd-display-message.c | 26 | ||||
-rw-r--r-- | cmd-display-panes.c | 6 | ||||
-rw-r--r-- | cmd-find-window.c | 35 | ||||
-rw-r--r-- | cmd-find.c | 12 | ||||
-rw-r--r-- | cmd-join-pane.c | 37 | ||||
-rw-r--r-- | cmd-list-clients.c | 24 | ||||
-rw-r--r-- | cmd-list-keys.c | 11 | ||||
-rw-r--r-- | cmd-load-buffer.c | 2 | ||||
-rw-r--r-- | cmd-new-session.c | 10 | ||||
-rw-r--r-- | cmd-new-window.c | 7 | ||||
-rw-r--r-- | cmd-parse.c | 24 | ||||
-rw-r--r-- | cmd-parse.y | 20 | ||||
-rw-r--r-- | cmd-paste-buffer.c | 5 | ||||
-rw-r--r-- | cmd-pipe-pane.c | 2 | ||||
-rw-r--r-- | cmd-queue.c | 51 | ||||
-rw-r--r-- | cmd-resize-window.c | 3 | ||||
-rw-r--r-- | cmd-run-shell.c | 14 | ||||
-rw-r--r-- | cmd-save-buffer.c | 13 | ||||
-rw-r--r-- | cmd-select-pane.c | 6 | ||||
-rw-r--r-- | cmd-send-keys.c | 40 | ||||
-rw-r--r-- | cmd-set-buffer.c | 16 | ||||
-rw-r--r-- | cmd-source-file.c | 9 | ||||
-rw-r--r-- | cmd-split-window.c | 79 | ||||
-rw-r--r-- | cmd-swap-pane.c | 8 | ||||
-rw-r--r-- | cmd.c | 12 | ||||
-rw-r--r-- | colour.c | 42 | ||||
-rw-r--r-- | compat.h | 14 | ||||
-rw-r--r-- | compat/getpeereid.c | 5 | ||||
-rw-r--r-- | compat/htonll.c | 30 | ||||
-rw-r--r-- | compat/imsg-buffer.c | 507 | ||||
-rw-r--r-- | compat/imsg.c | 284 | ||||
-rw-r--r-- | compat/imsg.h | 75 | ||||
-rw-r--r-- | compat/ntohll.c | 30 | ||||
-rw-r--r-- | compat/systemd.c | 159 | ||||
-rwxr-xr-x | configure | 317 | ||||
-rw-r--r-- | configure.ac | 69 | ||||
-rw-r--r-- | control-notify.c | 26 | ||||
-rw-r--r-- | control.c | 12 | ||||
-rw-r--r-- | environ.c | 15 | ||||
-rw-r--r-- | file.c | 54 | ||||
-rw-r--r-- | format-draw.c | 54 | ||||
-rw-r--r-- | format.c | 261 | ||||
-rw-r--r-- | grid-view.c | 2 | ||||
-rw-r--r-- | grid.c | 160 | ||||
-rw-r--r-- | hyperlinks.c | 227 | ||||
-rw-r--r-- | image-sixel.c | 600 | ||||
-rw-r--r-- | image.c | 186 | ||||
-rw-r--r-- | input-keys.c | 26 | ||||
-rw-r--r-- | input.c | 329 | ||||
-rw-r--r-- | key-bindings.c | 12 | ||||
-rw-r--r-- | key-string.c | 4 | ||||
-rw-r--r-- | layout-custom.c | 4 | ||||
-rw-r--r-- | menu.c | 177 | ||||
-rw-r--r-- | mode-tree.c | 6 | ||||
-rw-r--r-- | notify.c | 41 | ||||
-rw-r--r-- | options-table.c | 76 | ||||
-rw-r--r-- | options.c | 15 | ||||
-rw-r--r-- | paste.c | 26 | ||||
-rw-r--r-- | popup.c | 9 | ||||
-rw-r--r-- | proc.c | 16 | ||||
-rw-r--r-- | regsub.c | 4 | ||||
-rw-r--r-- | screen-redraw.c | 6 | ||||
-rw-r--r-- | screen-write.c | 496 | ||||
-rw-r--r-- | screen.c | 40 | ||||
-rw-r--r-- | server-client.c | 156 | ||||
-rw-r--r-- | server-fn.c | 77 | ||||
-rw-r--r-- | server.c | 5 | ||||
-rw-r--r-- | session.c | 24 | ||||
-rw-r--r-- | spawn.c | 19 | ||||
-rw-r--r-- | status.c | 72 | ||||
-rw-r--r-- | style.c | 83 | ||||
-rw-r--r-- | tmux-protocol.h | 7 | ||||
-rw-r--r-- | tmux.1 | 858 | ||||
-rw-r--r-- | tmux.c | 4 | ||||
-rw-r--r-- | tmux.h | 312 | ||||
-rw-r--r-- | tty-acs.c | 4 | ||||
-rw-r--r-- | tty-features.c | 168 | ||||
-rw-r--r-- | tty-keys.c | 341 | ||||
-rw-r--r-- | tty-term.c | 100 | ||||
-rw-r--r-- | tty.c | 482 | ||||
-rw-r--r-- | utf8-combined.c | 100 | ||||
-rw-r--r-- | utf8.c | 239 | ||||
-rw-r--r-- | window-buffer.c | 7 | ||||
-rw-r--r-- | window-client.c | 2 | ||||
-rw-r--r-- | window-copy.c | 229 | ||||
-rw-r--r-- | window-tree.c | 22 | ||||
-rw-r--r-- | window.c | 101 |
100 files changed, 7471 insertions, 1781 deletions
@@ -1,3 +1,116 @@ +CHANGES FROM 3.3a to 3.4 + +* Add options keep-last and keep-group to destroy-unattached to keep the last + session whether in a group. + +* Don't allow paste-buffer into dead panes. + +* Add -t to source-file. + +* Rewrite combined character handling to be more consistent and to support + newer Unicode combined characters. + +* Add basic support for SIXEL if built with --enable-sixel. + +* Add a session, pane and user mouse range types for the status line and add + format variables for mouse_status_line and mouse_status_range so they can be + associated with different commands in the key bindings. + +* Add flag (-o) to next-prompt/previous-prompt to go to OSC 133 command output. + +* Add options and flags for menu styles (menu-style, menu-border-style) similar + to those existing for popups. + +* Add support for marking lines with a shell prompt based on the OSC 133 extension. + +* Check for libterminfo for NetBSD. + +* Add "us" to styles for underscore colour. + +* Add flags (-c and -y) to change the confirm key and default behaviour of + confirm-before. + +* Use ncurses' new tparm_s function (added in 6.4-20230424) instead of tparm so + it does not object to string arguments in c apabilities it doesn't already + know. Also ignore errors from tparm if using previous ncurses versions. + +* Set default lock command to vlock on Linux if present at build time. + +* Discard mouse sequences that have the right form but actually are invalid. + +* Add support for spawning panes in separate cgroups with systemd and a + configure flag (--disable-cgroups) to turn off. + +* Add a format (pane_unseen_changes) to show if there are unseen changes while + in a mode. + +* Remove old buffer when renaming rather than complaining. + +* Add an L modifier like P, W, S to loop over clients. + +* Add -f to list-clients like the other list commands. + +* Extend display-message to work for control clients. + +* Add a flag to display-menu to select the manu item selected when the menu is + open. + +* Have tmux recognise pasted text wrapped in bracket paste sequences, rather + than only forwarding them to the program inside. + +* Have client return 1 if process is interrupted to an input pane. + +* Query the client terminal for foreground and background colours and if OSC 10 + or 11 is received but no colour has been set inside tmux, return the colour + from the first attached client. + +* Add send-keys -K to handle keys directly as if typed (so look up in key + table). + +* Process escape sequences in show-buffer. + +* Add a -l flag to display-message to disable format expansion. + +* Add paste-buffer-deleted notification and fix name of paste-buffer-changed. + +* Do not attempt to connect to the socket as a client if systemd is active. + +* Add scroll-top and scroll-bottom commands to scroll so cursor is at top or + bottom. + +* Add a -T flag to capture-pane to stop at the last used cell instead of the + full width. Restore the previous behaviour by making it default to off unless + -J is used. + +* Add message-line option to control where message and prompt go. + +* Notification when a when a paste buffer is deleted. + +* Add a Nobr terminfo(5) capability to tell tmux the terminal does not use bright + colours for bold. + +* Change g and G to go to top and bottom in menus. + +* Add a third state "all" to allow-passthrough to work even in invisible panes. + +* Add support for OSC 8 hyperlinks. + +* Store the time lines are scrolled into history and display in copy mode. + +* Add a %config-error reply to control mode for configuration file errors since + reporting them in view mode is useless. + +* A new feature flag (ignorefkeys) to ignore terminfo(5) function key + definitions for rxvt. + +* Pass through first argument to OSC 52 (which clipboards to set) if the + application provides it. + +* Expand arguments to send-keys, capture-pane, split-window, join-pane where it + makes sense to do so. + +* Ignore named buffers when choosing a buffer if one is not specified by the user. + CHANGES FROM 3.3 TO 3.3a * Do not crash when run-shell produces output from a config file. @@ -1302,7 +1415,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 diff --git a/Makefile.am b/Makefile.am index fb94e82..8e5f72b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,3 @@ -# Makefile.am - # Obvious program stuff. bin_PROGRAMS = tmux CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c @@ -14,6 +12,7 @@ dist_EXTRA_tmux_SOURCES = compat/*.[ch] AM_CPPFLAGS += @XOPEN_DEFINES@ \ -DTMUX_VERSION='"@VERSION@"' \ -DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \ + -DTMUX_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \ -DTMUX_TERM='"@DEFAULT_TERM@"' # Additional object files. @@ -150,6 +149,7 @@ dist_tmux_SOURCES = \ grid-reader.c \ grid-view.c \ grid.c \ + hyperlinks.c \ input-keys.c \ input.c \ job.c \ @@ -189,6 +189,7 @@ dist_tmux_SOURCES = \ tty-keys.c \ tty-term.c \ tty.c \ + utf8-combined.c \ utf8.c \ window-buffer.c \ window-client.c \ @@ -216,6 +217,11 @@ if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c endif +# Enable sixel support. +if ENABLE_SIXEL +dist_tmux_SOURCES += image.c image-sixel.c +endif + if NEED_FUZZING check_PROGRAMS = fuzz/input-fuzzer fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) diff --git a/Makefile.in b/Makefile.in index 979172b..b2854d9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -14,8 +14,6 @@ @SET_MAKE@ -# Makefile.am - VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ @@ -133,6 +131,9 @@ bin_PROGRAMS = tmux$(EXEEXT) # Add compat file for utf8proc. @HAVE_UTF8PROC_TRUE@am__append_14 = compat/utf8proc.c + +# Enable sixel support. +@ENABLE_SIXEL_TRUE@am__append_15 = image.c image-sixel.c @NEED_FUZZING_TRUE@check_PROGRAMS = fuzz/input-fuzzer$(EXEEXT) subdir = . ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -155,6 +156,42 @@ fuzz_input_fuzzer_OBJECTS = fuzz/input-fuzzer.$(OBJEXT) @NEED_FUZZING_TRUE@fuzz_input_fuzzer_DEPENDENCIES = $(LDADD) fuzz_input_fuzzer_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(fuzz_input_fuzzer_LDFLAGS) $(LDFLAGS) -o $@ +am__dist_tmux_SOURCES_DIST = alerts.c arguments.c attributes.c cfg.c \ + client.c cmd-attach-session.c cmd-bind-key.c cmd-break-pane.c \ + cmd-capture-pane.c cmd-choose-tree.c cmd-command-prompt.c \ + cmd-confirm-before.c cmd-copy-mode.c cmd-detach-client.c \ + cmd-display-menu.c cmd-display-message.c cmd-display-panes.c \ + cmd-find-window.c cmd-find.c cmd-if-shell.c cmd-join-pane.c \ + cmd-kill-pane.c cmd-kill-server.c cmd-kill-session.c \ + cmd-kill-window.c cmd-list-buffers.c cmd-list-clients.c \ + cmd-list-keys.c cmd-list-panes.c cmd-list-sessions.c \ + cmd-list-windows.c cmd-load-buffer.c cmd-lock-server.c \ + cmd-move-window.c cmd-new-session.c cmd-new-window.c \ + cmd-parse.y cmd-paste-buffer.c cmd-pipe-pane.c cmd-queue.c \ + cmd-refresh-client.c cmd-rename-session.c cmd-rename-window.c \ + cmd-resize-pane.c cmd-resize-window.c cmd-respawn-pane.c \ + cmd-respawn-window.c cmd-rotate-window.c cmd-run-shell.c \ + cmd-save-buffer.c cmd-select-layout.c 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 \ + cmd-show-environment.c cmd-show-messages.c cmd-show-options.c \ + cmd-show-prompt-history.c cmd-source-file.c cmd-split-window.c \ + cmd-swap-pane.c cmd-swap-window.c cmd-switch-client.c \ + cmd-unbind-key.c cmd-wait-for.c cmd.c colour.c compat.h \ + control-notify.c control.c environ.c file.c format.c \ + format-draw.c grid-reader.c grid-view.c grid.c hyperlinks.c \ + input-keys.c input.c job.c key-bindings.c key-string.c \ + layout-custom.c layout-set.c layout.c log.c menu.c mode-tree.c \ + names.c notify.c options-table.c options.c paste.c popup.c \ + proc.c regsub.c resize.c screen-redraw.c screen-write.c \ + screen.c server-acl.c server-client.c server-fn.c server.c \ + session.c spawn.c status.c style.c tmux.c tmux.h \ + tmux-protocol.h tty-acs.c tty-features.c tty-keys.c tty-term.c \ + tty.c utf8-combined.c utf8.c window-buffer.c window-client.c \ + window-clock.c window-copy.c window-customize.c window-tree.c \ + window.c xmalloc.c xmalloc.h image.c image-sixel.c +@ENABLE_SIXEL_TRUE@am__objects_1 = image.$(OBJEXT) \ +@ENABLE_SIXEL_TRUE@ image-sixel.$(OBJEXT) dist_tmux_OBJECTS = alerts.$(OBJEXT) arguments.$(OBJEXT) \ attributes.$(OBJEXT) cfg.$(OBJEXT) client.$(OBJEXT) \ cmd-attach-session.$(OBJEXT) cmd-bind-key.$(OBJEXT) \ @@ -192,29 +229,31 @@ dist_tmux_OBJECTS = alerts.$(OBJEXT) arguments.$(OBJEXT) \ colour.$(OBJEXT) control-notify.$(OBJEXT) control.$(OBJEXT) \ environ.$(OBJEXT) file.$(OBJEXT) format.$(OBJEXT) \ format-draw.$(OBJEXT) grid-reader.$(OBJEXT) \ - grid-view.$(OBJEXT) grid.$(OBJEXT) input-keys.$(OBJEXT) \ - input.$(OBJEXT) job.$(OBJEXT) key-bindings.$(OBJEXT) \ - key-string.$(OBJEXT) layout-custom.$(OBJEXT) \ - layout-set.$(OBJEXT) layout.$(OBJEXT) log.$(OBJEXT) \ - menu.$(OBJEXT) mode-tree.$(OBJEXT) names.$(OBJEXT) \ - notify.$(OBJEXT) options-table.$(OBJEXT) options.$(OBJEXT) \ - paste.$(OBJEXT) popup.$(OBJEXT) proc.$(OBJEXT) \ - regsub.$(OBJEXT) resize.$(OBJEXT) screen-redraw.$(OBJEXT) \ - screen-write.$(OBJEXT) screen.$(OBJEXT) server-acl.$(OBJEXT) \ - server-client.$(OBJEXT) server-fn.$(OBJEXT) server.$(OBJEXT) \ - session.$(OBJEXT) spawn.$(OBJEXT) status.$(OBJEXT) \ - style.$(OBJEXT) tmux.$(OBJEXT) tty-acs.$(OBJEXT) \ - tty-features.$(OBJEXT) tty-keys.$(OBJEXT) tty-term.$(OBJEXT) \ - tty.$(OBJEXT) utf8.$(OBJEXT) window-buffer.$(OBJEXT) \ + grid-view.$(OBJEXT) grid.$(OBJEXT) hyperlinks.$(OBJEXT) \ + input-keys.$(OBJEXT) input.$(OBJEXT) job.$(OBJEXT) \ + key-bindings.$(OBJEXT) key-string.$(OBJEXT) \ + layout-custom.$(OBJEXT) layout-set.$(OBJEXT) layout.$(OBJEXT) \ + log.$(OBJEXT) menu.$(OBJEXT) mode-tree.$(OBJEXT) \ + names.$(OBJEXT) notify.$(OBJEXT) options-table.$(OBJEXT) \ + options.$(OBJEXT) paste.$(OBJEXT) popup.$(OBJEXT) \ + proc.$(OBJEXT) regsub.$(OBJEXT) resize.$(OBJEXT) \ + screen-redraw.$(OBJEXT) screen-write.$(OBJEXT) \ + screen.$(OBJEXT) server-acl.$(OBJEXT) server-client.$(OBJEXT) \ + server-fn.$(OBJEXT) server.$(OBJEXT) session.$(OBJEXT) \ + spawn.$(OBJEXT) status.$(OBJEXT) style.$(OBJEXT) \ + tmux.$(OBJEXT) tty-acs.$(OBJEXT) tty-features.$(OBJEXT) \ + tty-keys.$(OBJEXT) tty-term.$(OBJEXT) tty.$(OBJEXT) \ + utf8-combined.$(OBJEXT) utf8.$(OBJEXT) window-buffer.$(OBJEXT) \ window-client.$(OBJEXT) window-clock.$(OBJEXT) \ window-copy.$(OBJEXT) window-customize.$(OBJEXT) \ - window-tree.$(OBJEXT) window.$(OBJEXT) xmalloc.$(OBJEXT) -@NEED_FORKPTY_TRUE@am__objects_1 = \ + window-tree.$(OBJEXT) window.$(OBJEXT) xmalloc.$(OBJEXT) \ + $(am__objects_1) +@NEED_FORKPTY_TRUE@am__objects_2 = \ @NEED_FORKPTY_TRUE@ compat/forkpty-@PLATFORM@.$(OBJEXT) -@HAVE_SYSTEMD_TRUE@am__objects_2 = compat/systemd.$(OBJEXT) -@HAVE_UTF8PROC_TRUE@am__objects_3 = compat/utf8proc.$(OBJEXT) -nodist_tmux_OBJECTS = osdep-@PLATFORM@.$(OBJEXT) $(am__objects_1) \ - $(am__objects_2) $(am__objects_3) +@HAVE_SYSTEMD_TRUE@am__objects_3 = compat/systemd.$(OBJEXT) +@HAVE_UTF8PROC_TRUE@am__objects_4 = compat/utf8proc.$(OBJEXT) +nodist_tmux_OBJECTS = osdep-@PLATFORM@.$(OBJEXT) $(am__objects_2) \ + $(am__objects_3) $(am__objects_4) tmux_OBJECTS = $(dist_tmux_OBJECTS) $(nodist_tmux_OBJECTS) tmux_LDADD = $(LDADD) tmux_DEPENDENCIES = $(LIBOBJS) @@ -256,7 +295,7 @@ am__v_YACC_1 = YLWRAP = $(top_srcdir)/etc/ylwrap SOURCES = fuzz/input-fuzzer.c $(dist_tmux_SOURCES) \ $(nodist_tmux_SOURCES) $(dist_EXTRA_tmux_SOURCES) -DIST_SOURCES = fuzz/input-fuzzer.c $(dist_tmux_SOURCES) \ +DIST_SOURCES = fuzz/input-fuzzer.c $(am__dist_tmux_SOURCES_DIST) \ $(dist_EXTRA_tmux_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ @@ -299,8 +338,9 @@ am__DIST_COMMON = $(srcdir)/Makefile.in \ $(top_srcdir)/compat/getline.c $(top_srcdir)/compat/getopt.c \ $(top_srcdir)/compat/getpeereid.c \ $(top_srcdir)/compat/getprogname.c \ + $(top_srcdir)/compat/htonll.c \ $(top_srcdir)/compat/imsg-buffer.c $(top_srcdir)/compat/imsg.c \ - $(top_srcdir)/compat/memmem.c \ + $(top_srcdir)/compat/memmem.c $(top_srcdir)/compat/ntohll.c \ $(top_srcdir)/compat/reallocarray.c \ $(top_srcdir)/compat/recallocarray.c \ $(top_srcdir)/compat/setenv.c \ @@ -342,6 +382,7 @@ AM_CFLAGS = @AM_CFLAGS@ $(am__append_1) $(am__append_2) \ AM_CPPFLAGS = @AM_CPPFLAGS@ @XOPEN_DEFINES@ \ -DTMUX_VERSION='"@VERSION@"' \ -DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \ + -DTMUX_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \ -DTMUX_TERM='"@DEFAULT_TERM@"' $(am__append_4) $(am__append_5) \ $(am__append_6) $(am__append_7) $(am__append_9) \ $(am__append_10) $(am__append_11) @@ -357,6 +398,7 @@ CFLAGS = @CFLAGS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CYGPATH_W = @CYGPATH_W@ +DEFAULT_LOCK_CMD = @DEFAULT_LOCK_CMD@ DEFAULT_TERM = @DEFAULT_TERM@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ @@ -385,6 +427,8 @@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBTINFO_CFLAGS = @LIBTINFO_CFLAGS@ LIBTINFO_LIBS = @LIBTINFO_LIBS@ +LIBUTF8PROC_CFLAGS = @LIBUTF8PROC_CFLAGS@ +LIBUTF8PROC_LIBS = @LIBUTF8PROC_LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ MANFORMAT = @MANFORMAT@ @@ -433,6 +477,8 @@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ +found_vlock = @found_vlock@ +found_yacc = @found_yacc@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ @@ -475,138 +521,40 @@ dist_EXTRA_tmux_SOURCES = compat/*.[ch] LDADD = $(LIBOBJS) # List of sources. -dist_tmux_SOURCES = \ - alerts.c \ - arguments.c \ - attributes.c \ - cfg.c \ - client.c \ - cmd-attach-session.c \ - cmd-bind-key.c \ - cmd-break-pane.c \ - cmd-capture-pane.c \ - cmd-choose-tree.c \ - cmd-command-prompt.c \ - cmd-confirm-before.c \ - cmd-copy-mode.c \ - cmd-detach-client.c \ - cmd-display-menu.c \ - cmd-display-message.c \ - cmd-display-panes.c \ - cmd-find-window.c \ - cmd-find.c \ - cmd-if-shell.c \ - cmd-join-pane.c \ - cmd-kill-pane.c \ - cmd-kill-server.c \ - cmd-kill-session.c \ - cmd-kill-window.c \ - cmd-list-buffers.c \ - cmd-list-clients.c \ - cmd-list-keys.c \ - cmd-list-panes.c \ - cmd-list-sessions.c \ - cmd-list-windows.c \ - cmd-load-buffer.c \ - cmd-lock-server.c \ - cmd-move-window.c \ - cmd-new-session.c \ - cmd-new-window.c \ - cmd-parse.y \ - cmd-paste-buffer.c \ - cmd-pipe-pane.c \ - cmd-queue.c \ - cmd-refresh-client.c \ - cmd-rename-session.c \ - cmd-rename-window.c \ - cmd-resize-pane.c \ - cmd-resize-window.c \ - cmd-respawn-pane.c \ - cmd-respawn-window.c \ - cmd-rotate-window.c \ - cmd-run-shell.c \ - cmd-save-buffer.c \ - cmd-select-layout.c \ - 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 \ - cmd-show-environment.c \ - cmd-show-messages.c \ - cmd-show-options.c \ - cmd-show-prompt-history.c \ - cmd-source-file.c \ - cmd-split-window.c \ - cmd-swap-pane.c \ - cmd-swap-window.c \ - cmd-switch-client.c \ - cmd-unbind-key.c \ - cmd-wait-for.c \ - cmd.c \ - colour.c \ - compat.h \ - control-notify.c \ - control.c \ - environ.c \ - file.c \ - format.c \ - format-draw.c \ - grid-reader.c \ - grid-view.c \ - grid.c \ - input-keys.c \ - input.c \ - job.c \ - key-bindings.c \ - key-string.c \ - layout-custom.c \ - layout-set.c \ - layout.c \ - log.c \ - menu.c \ - mode-tree.c \ - names.c \ - notify.c \ - options-table.c \ - options.c \ - paste.c \ - popup.c \ - proc.c \ - regsub.c \ - resize.c \ - screen-redraw.c \ - screen-write.c \ - screen.c \ - server-acl.c \ - server-client.c \ - server-fn.c \ - server.c \ - session.c \ - spawn.c \ - status.c \ - style.c \ - tmux.c \ - tmux.h \ - tmux-protocol.h \ - tty-acs.c \ - tty-features.c \ - tty-keys.c \ - tty-term.c \ - tty.c \ - utf8.c \ - window-buffer.c \ - window-client.c \ - window-clock.c \ - window-copy.c \ - window-customize.c \ - window-tree.c \ - window.c \ - xmalloc.c \ - xmalloc.h - +dist_tmux_SOURCES = alerts.c arguments.c attributes.c cfg.c client.c \ + cmd-attach-session.c cmd-bind-key.c cmd-break-pane.c \ + cmd-capture-pane.c cmd-choose-tree.c cmd-command-prompt.c \ + cmd-confirm-before.c cmd-copy-mode.c cmd-detach-client.c \ + cmd-display-menu.c cmd-display-message.c cmd-display-panes.c \ + cmd-find-window.c cmd-find.c cmd-if-shell.c cmd-join-pane.c \ + cmd-kill-pane.c cmd-kill-server.c cmd-kill-session.c \ + cmd-kill-window.c cmd-list-buffers.c cmd-list-clients.c \ + cmd-list-keys.c cmd-list-panes.c cmd-list-sessions.c \ + cmd-list-windows.c cmd-load-buffer.c cmd-lock-server.c \ + cmd-move-window.c cmd-new-session.c cmd-new-window.c \ + cmd-parse.y cmd-paste-buffer.c cmd-pipe-pane.c cmd-queue.c \ + cmd-refresh-client.c cmd-rename-session.c cmd-rename-window.c \ + cmd-resize-pane.c cmd-resize-window.c cmd-respawn-pane.c \ + cmd-respawn-window.c cmd-rotate-window.c cmd-run-shell.c \ + cmd-save-buffer.c cmd-select-layout.c 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 \ + cmd-show-environment.c cmd-show-messages.c cmd-show-options.c \ + cmd-show-prompt-history.c cmd-source-file.c cmd-split-window.c \ + cmd-swap-pane.c cmd-swap-window.c cmd-switch-client.c \ + cmd-unbind-key.c cmd-wait-for.c cmd.c colour.c compat.h \ + control-notify.c control.c environ.c file.c format.c \ + format-draw.c grid-reader.c grid-view.c grid.c hyperlinks.c \ + input-keys.c input.c job.c key-bindings.c key-string.c \ + layout-custom.c layout-set.c layout.c log.c menu.c mode-tree.c \ + names.c notify.c options-table.c options.c paste.c popup.c \ + proc.c regsub.c resize.c screen-redraw.c screen-write.c \ + screen.c server-acl.c server-client.c server-fn.c server.c \ + session.c spawn.c status.c style.c tmux.c tmux.h \ + tmux-protocol.h tty-acs.c tty-features.c tty-keys.c tty-term.c \ + tty.c utf8-combined.c utf8.c window-buffer.c window-client.c \ + window-clock.c window-copy.c window-customize.c window-tree.c \ + window.c xmalloc.c xmalloc.h $(am__append_15) nodist_tmux_SOURCES = osdep-@PLATFORM@.c $(am__append_12) \ $(am__append_13) $(am__append_14) @NEED_FUZZING_TRUE@fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) @@ -810,6 +758,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grid-reader.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grid-view.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hyperlinks.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/image-sixel.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/image.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input-keys.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/job.Po@am__quote@ @@ -848,6 +799,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-keys.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-term.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8-combined.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-buffer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-client.Po@am__quote@ @@ -876,9 +828,11 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/getopt.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/getpeereid.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/getprogname.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/htonll.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/imsg-buffer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/imsg.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/memmem.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/ntohll.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/reallocarray.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/recallocarray.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/setenv.Po@am__quote@ diff --git a/arguments.c b/arguments.c index d0dc2d4..669375e 100644 --- a/arguments.c +++ b/arguments.c @@ -37,6 +37,10 @@ struct args_entry { u_char flag; struct args_values values; u_int count; + + int flags; +#define ARGS_ENTRY_OPTIONAL_VALUE 0x1 + RB_ENTRY(args_entry) entry; }; @@ -94,6 +98,22 @@ args_copy_value(struct args_value *to, struct args_value *from) } } +/* Type to string. */ +static const char * +args_type_to_string (enum args_type type) +{ + switch (type) + { + case ARGS_NONE: + return "NONE"; + case ARGS_STRING: + return "STRING"; + case ARGS_COMMANDS: + return "COMMANDS"; + } + return "INVALID"; +} + /* Get value as string. */ static const char * args_value_as_string(struct args_value *value) @@ -122,6 +142,99 @@ args_create(void) return (args); } +/* Parse a single flag. */ +static int +args_parse_flag_argument(struct args_value *values, u_int count, char **cause, + struct args *args, u_int *i, const char *string, int flag, + int optional_argument) +{ + struct args_value *argument, *new; + const char *s; + + new = xcalloc(1, sizeof *new); + if (*string != '\0') { + new->type = ARGS_STRING; + new->string = xstrdup(string); + goto out; + } + + if (*i == count) + argument = NULL; + else { + argument = &values[*i]; + if (argument->type != ARGS_STRING) { + xasprintf(cause, "-%c argument must be a string", flag); + return (-1); + } + } + if (argument == NULL) { + if (optional_argument) { + log_debug("%s: -%c (optional)", __func__, flag); + args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE); + return (0); /* either - or end */ + } + xasprintf(cause, "-%c expects an argument", flag); + return (-1); + } + args_copy_value(new, argument); + (*i)++; + +out: + s = args_value_as_string(new); + log_debug("%s: -%c = %s", __func__, flag, s); + args_set(args, flag, new, 0); + return (0); +} + +/* Parse flags argument. */ +static int +args_parse_flags(const struct args_parse *parse, struct args_value *values, + u_int count, char **cause, struct args *args, u_int *i) +{ + struct args_value *value; + u_char flag; + const char *found, *string; + int optional_argument; + + value = &values[*i]; + if (value->type != ARGS_STRING) + return (1); + + string = value->string; + log_debug("%s: next %s", __func__, string); + if (*string++ != '-' || *string == '\0') + return (1); + (*i)++; + if (string[0] == '-' && string[1] == '\0') + return (1); + + for (;;) { + flag = *string++; + if (flag == '\0') + return (0); + if (flag == '?') + return (-1); + if (!isalnum(flag)) { + xasprintf(cause, "invalid flag -%c", flag); + return (-1); + } + + found = strchr(parse->template, flag); + if (found == NULL) { + xasprintf(cause, "unknown flag -%c", flag); + return (-1); + } + if (found[1] != ':') { + log_debug("%s: -%c", __func__, flag); + args_set(args, flag, NULL, 0); + continue; + } + optional_argument = (found[2] == ':'); + return (args_parse_flag_argument(values, count, cause, args, i, + string, flag, optional_argument)); + } +} + /* Parse arguments into a new argument set. */ struct args * args_parse(const struct args_parse *parse, struct args_value *values, @@ -131,86 +244,21 @@ args_parse(const struct args_parse *parse, struct args_value *values, u_int i; enum args_parse_type type; struct args_value *value, *new; - u_char flag; - const char *found, *string, *s; - int optional_argument; + const char *s; + int stop; if (count == 0) return (args_create()); args = args_create(); for (i = 1; i < count; /* nothing */) { - value = &values[i]; - if (value->type != ARGS_STRING) - break; - - string = value->string; - if (*string++ != '-' || *string == '\0') - break; - i++; - if (string[0] == '-' && string[1] == '\0') - break; - - for (;;) { - flag = *string++; - if (flag == '\0') - break; - if (flag == '?') { - args_free(args); - return (NULL); - } - if (!isalnum(flag)) { - xasprintf(cause, "invalid flag -%c", flag); - args_free(args); - return (NULL); - } - found = strchr(parse->template, flag); - if (found == NULL) { - xasprintf(cause, "unknown flag -%c", flag); - args_free(args); - return (NULL); - } - if (*++found != ':') { - log_debug("%s: -%c", __func__, flag); - args_set(args, flag, NULL); - continue; - } - if (*found == ':') { - optional_argument = 1; - found++; - } - new = xcalloc(1, sizeof *new); - if (*string != '\0') { - new->type = ARGS_STRING; - new->string = xstrdup(string); - } else { - if (i == count) { - if (optional_argument) { - log_debug("%s: -%c", __func__, - flag); - args_set(args, flag, NULL); - continue; - } - xasprintf(cause, - "-%c expects an argument", - flag); - args_free(args); - return (NULL); - } - if (values[i].type != ARGS_STRING) { - xasprintf(cause, - "-%c argument must be a string", - flag); - args_free(args); - return (NULL); - } - args_copy_value(new, &values[i++]); - } - s = args_value_as_string(new); - log_debug("%s: -%c = %s", __func__, flag, s); - args_set(args, flag, new); - break; + stop = args_parse_flags(parse, values, count, cause, args, &i); + if (stop == -1) { + args_free(args); + return (NULL); } + if (stop == 1) + break; } log_debug("%s: flags end at %u of %u", __func__, i, count); if (i != count) { @@ -218,8 +266,8 @@ args_parse(const struct args_parse *parse, struct args_value *values, value = &values[i]; s = args_value_as_string(value); - log_debug("%s: %u = %s (type %d)", __func__, i, s, - value->type); + log_debug("%s: %u = %s (type %s)", __func__, i, s, + args_type_to_string (value->type)); if (parse->cb != NULL) { type = parse->cb(args, args->count, cause); @@ -323,13 +371,13 @@ args_copy(struct args *args, int argc, char **argv) RB_FOREACH(entry, args_tree, &args->tree) { if (TAILQ_EMPTY(&entry->values)) { for (i = 0; i < entry->count; i++) - args_set(new_args, entry->flag, NULL); + args_set(new_args, entry->flag, NULL, 0); continue; } TAILQ_FOREACH(value, &entry->values, entry) { new_value = xcalloc(1, sizeof *new_value); args_copy_copy_value(new_value, value, argc, argv); - args_set(new_args, entry->flag, new_value); + args_set(new_args, entry->flag, new_value, 0); } } if (args->count == 0) @@ -487,6 +535,7 @@ args_print(struct args *args) char *buf; u_int i, j; struct args_entry *entry; + struct args_entry *last = NULL; struct args_value *value; len = 1; @@ -494,6 +543,8 @@ args_print(struct args *args) /* Process the flags first. */ RB_FOREACH(entry, args_tree, &args->tree) { + if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) + continue; if (!TAILQ_EMPTY(&entry->values)) continue; @@ -505,6 +556,16 @@ args_print(struct args *args) /* Then the flags with arguments. */ RB_FOREACH(entry, args_tree, &args->tree) { + if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) { + if (*buf != '\0') + args_print_add(&buf, &len, " -%c", entry->flag); + else + args_print_add(&buf, &len, "-%c", entry->flag); + last = entry; + continue; + } + if (TAILQ_EMPTY(&entry->values)) + continue; TAILQ_FOREACH(value, &entry->values, entry) { if (*buf != '\0') args_print_add(&buf, &len, " -%c", entry->flag); @@ -512,7 +573,10 @@ args_print(struct args *args) args_print_add(&buf, &len, "-%c", entry->flag); args_print_add_value(&buf, &len, value); } + last = entry; } + if (last && (last->flags & ARGS_ENTRY_OPTIONAL_VALUE)) + args_print_add(&buf, &len, " --"); /* And finally the argument vector. */ for (i = 0; i < args->count; i++) @@ -582,7 +646,7 @@ args_has(struct args *args, u_char flag) /* Set argument value in the arguments tree. */ void -args_set(struct args *args, u_char flag, struct args_value *value) +args_set(struct args *args, u_char flag, struct args_value *value, int flags) { struct args_entry *entry; @@ -591,6 +655,7 @@ args_set(struct args *args, u_char flag, struct args_value *value) entry = xcalloc(1, sizeof *entry); entry->flag = flag; entry->count = 1; + entry->flags = flags; TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); } else @@ -696,6 +761,7 @@ args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx, struct args_value *value; struct args_command_state *state; const char *cmd; + const char *file; state = xcalloc(1, sizeof *state); @@ -722,7 +788,9 @@ args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx, if (wait) state->pi.item = item; - cmd_get_source(self, &state->pi.file, &state->pi.line); + cmd_get_source(self, &file, &state->pi.line); + if (file != NULL) + state->pi.file = xstrdup(file); state->pi.c = tc; if (state->pi.c != NULL) state->pi.c->references++; @@ -747,6 +815,8 @@ args_make_commands(struct args_command_state *state, int argc, char **argv, } cmd = xstrdup(state->cmd); + log_debug("%s: %s", __func__, cmd); + cmd_log_argv(argc, argv, __func__); for (i = 0; i < argc; i++) { new_cmd = cmd_template_replace(cmd, argv[i], i + 1); log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd); @@ -775,6 +845,7 @@ args_make_commands_free(struct args_command_state *state) cmd_list_free(state->cmdlist); if (state->pi.c != NULL) server_client_unref(state->pi.c); + free((void *)state->pi.file); free(state->cmd); free(state); } @@ -848,6 +919,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, @@ -860,6 +966,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)); } @@ -874,6 +984,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'; @@ -904,3 +1018,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); +} @@ -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); @@ -67,6 +66,7 @@ start_cfg(void) { struct client *c; u_int i; + int flags = 0; /* * Configuration files are loaded without a client, so commands are run @@ -84,19 +84,17 @@ start_cfg(void) cmdq_append(c, cfg_item); } - for (i = 0; i < cfg_nfiles; i++) { - if (cfg_quiet) - load_cfg(cfg_files[i], c, NULL, CMD_PARSE_QUIET, NULL); - else - load_cfg(cfg_files[i], c, NULL, 0, NULL); - } + if (cfg_quiet) + flags = CMD_PARSE_QUIET; + for (i = 0; i < cfg_nfiles; i++) + load_cfg(cfg_files[i], c, NULL, NULL, flags, NULL); cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); } int -load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, - struct cmdq_item **new_item) +load_cfg(const char *path, struct client *c, struct cmdq_item *item, + struct cmd_find_state *current, int flags, struct cmdq_item **new_item) { FILE *f; struct cmd_parse_input pi; @@ -135,7 +133,7 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, } if (item != NULL) - state = cmdq_copy_state(cmdq_get_state(item)); + state = cmdq_copy_state(cmdq_get_state(item), current); else state = cmdq_new_state(NULL, NULL, 0); cmdq_add_format(state, "current_file", "%s", pi.file); @@ -155,8 +153,8 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, int load_cfg_from_buffer(const void *buf, size_t len, const char *path, - struct client *c, struct cmdq_item *item, int flags, - struct cmdq_item **new_item) + struct client *c, struct cmdq_item *item, struct cmd_find_state *current, + int flags, struct cmdq_item **new_item) { struct cmd_parse_input pi; struct cmd_parse_result *pr; @@ -187,7 +185,7 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path, } if (item != NULL) - state = cmdq_copy_state(cmdq_get_state(item)); + state = cmdq_copy_state(cmdq_get_state(item), current); else state = cmdq_new_state(NULL, NULL, 0); cmdq_add_format(state, "current_file", "%s", pi.file); @@ -238,11 +236,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; @@ -254,6 +270,7 @@ cfg_show_causes(struct session *s) free(cfg_causes[i]); } +out: free(cfg_causes); cfg_causes = NULL; cfg_ncauses = 0; @@ -245,9 +245,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, u_int ncaps = 0; struct args_value *values; - /* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ - signal(SIGCHLD, SIG_IGN); - /* Set up the initial command. */ if (shell_command != NULL) { msg = MSG_SHELL; @@ -284,6 +281,12 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, log_debug("flags are %#llx", (unsigned long long)client_flags); /* Initialize the client socket and start the server. */ +#ifdef HAVE_SYSTEMD + if (systemd_activated()) { + /* socket-based activation, do not even try to be a client. */ + fd = server_start(client_proc, flags, base, 0, NULL); + } else +#endif fd = client_connect(base, socket_path, client_flags); if (fd == -1) { if (errno == ECONNREFUSED) { @@ -527,11 +530,22 @@ client_signal(int sig) { struct sigaction sigact; int status; + pid_t pid; log_debug("%s: %s", __func__, strsignal(sig)); - if (sig == SIGCHLD) - waitpid(WAIT_ANY, &status, WNOHANG); - else if (!client_attached) { + if (sig == SIGCHLD) { + for (;;) { + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid == 0) + break; + if (pid == -1) { + if (errno == ECHILD) + break; + log_debug("waitpid failed: %s", + strerror(errno)); + } + } + } else if (!client_attached) { if (sig == SIGTERM || sig == SIGHUP) proc_exit(client_proc); } else { @@ -694,6 +708,9 @@ client_dispatch_wait(struct imsg *imsg) !(client_flags & CLIENT_CONTROL), client_file_check_cb, NULL); break; + case MSG_READ_CANCEL: + file_read_cancel(&client_files, imsg); + break; case MSG_WRITE_OPEN: file_write_open(&client_files, client_peer, imsg, 1, !(client_flags & CLIENT_CONTROL), client_file_check_cb, diff --git a/cmd-attach-session.c b/cmd-attach-session.c index b92a7f2..4e2d15d 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -158,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-break-pane.c b/cmd-break-pane.c index 4f38d4b..9c4b150 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -115,6 +115,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) layout_init(w, wp); wp->flags |= PANE_CHANGED; + colour_palette_from_option(&wp->palette, wp->options); if (idx == -1) idx = -1 - options_get_number(dst_s->options, "base-index"); diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 964f831..8f7250e 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:JNpPqS:t:", 0, 0, NULL }, - .usage = "[-aCeJNpPq] " CMD_BUFFER_USAGE " [-E end-line] " + .args = { "ab:CeE:JNpPqS:Tt:", 0, 0, NULL }, + .usage = "[-aCeJNpPqT] " CMD_BUFFER_USAGE " [-E end-line] " "[-S start-line] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -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 }, @@ -110,7 +110,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct grid *gd; const struct grid_line *gl; struct grid_cell *gc = NULL; - int n, with_codes, escape_c0, join_lines, no_trim; + int n, join_lines, flags = 0; u_int i, sx, top, bottom, tmp; char *cause, *buf, *line; const char *Sflag, *Eflag; @@ -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); @@ -167,15 +169,19 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, top = tmp; } - with_codes = args_has(args, 'e'); - escape_c0 = args_has(args, 'C'); join_lines = args_has(args, 'J'); - no_trim = args_has(args, 'N'); + if (args_has(args, 'e')) + flags |= GRID_STRING_WITH_SEQUENCES; + if (args_has(args, 'C')) + flags |= GRID_STRING_ESCAPE_SEQUENCES; + if (!join_lines && !args_has(args, 'T')) + flags |= GRID_STRING_EMPTY_CELLS; + if (!join_lines && !args_has(args, 'N')) + flags |= GRID_STRING_TRIM_SPACES; 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); + line = grid_string_cells(gd, 0, i, sx, &gc, flags, wp->screen); linelen = strlen(line); buf = cmd_capture_pane_append(buf, len, line, linelen); @@ -202,6 +208,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 7aa1d21..f2f4b2e 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 4455856..6010d0f 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -179,10 +179,10 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, if (s == NULL) goto out; + if (done) { if (cdata->flags & PROMPT_INCREMENTAL) goto out; - cmd_append_argv(&cdata->argc, &cdata->argv, s); if (++cdata->current != cdata->count) { prompt = &cdata->prompts[cdata->current]; @@ -193,8 +193,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, argc = cdata->argc; argv = cmd_copy_argv(cdata->argc, cdata->argv); - cmd_append_argv(&argc, &argv, s); + if (!done) + cmd_append_argv(&argc, &argv, s); + if (done) { + cmd_free_argv(cdata->argc, cdata->argv); cdata->argc = argc; cdata->argv = cmd_copy_argv(argc, argv); } diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index ce8c95e..485e6e6 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -41,8 +41,9 @@ const struct cmd_entry cmd_confirm_before_entry = { .name = "confirm-before", .alias = "confirm", - .args = { "bp:t:", 1, 1, cmd_confirm_before_args_parse }, - .usage = "[-b] [-p prompt] " CMD_TARGET_CLIENT_USAGE " command", + .args = { "bc:p:t:y", 1, 1, cmd_confirm_before_args_parse }, + .usage = "[-by] [-c confirm_key] [-p prompt] " CMD_TARGET_CLIENT_USAGE + " command", .flags = CMD_CLIENT_TFLAG, .exec = cmd_confirm_before_exec @@ -51,6 +52,8 @@ const struct cmd_entry cmd_confirm_before_entry = { struct cmd_confirm_before_data { struct cmdq_item *item; struct cmd_list *cmdlist; + u_char confirm_key; + int default_yes; }; static enum args_parse_type @@ -68,7 +71,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) struct client *tc = cmdq_get_target_client(item); struct cmd_find_state *target = cmdq_get_target(item); char *new_prompt; - const char *prompt, *cmd; + const char *confirm_key, *prompt, *cmd; int wait = !args_has(args, 'b'); cdata = xcalloc(1, sizeof *cdata); @@ -79,11 +82,26 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) if (wait) cdata->item = item; + cdata->default_yes = args_has(args, 'y'); + if ((confirm_key = args_get(args, 'c')) != NULL) { + if (confirm_key[1] == '\0' && + confirm_key[0] > 31 && + confirm_key[0] < 127) + cdata->confirm_key = confirm_key[0]; + else { + cmdq_error(item, "invalid confirm key"); + return (CMD_RETURN_ERROR); + } + } + else + cdata->confirm_key = 'y'; + if ((prompt = args_get(args, 'p')) != NULL) xasprintf(&new_prompt, "%s ", prompt); else { cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name; - xasprintf(&new_prompt, "Confirm '%s'? (y/n) ", cmd); + xasprintf(&new_prompt, "Confirm '%s'? (%c/n) ", + cmd, cdata->confirm_key); } status_prompt_set(tc, target, new_prompt, NULL, @@ -107,9 +125,9 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s, if (c->flags & CLIENT_DEAD) goto out; - if (s == NULL || *s == '\0') + if (s == NULL) goto out; - if (tolower((u_char)s[0]) != 'y' || s[1] != '\0') + if (s[0] != cdata->confirm_key && (s[0] != '\0' || !cdata->default_yes)) goto out; retcode = 0; @@ -123,12 +141,12 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s, } out: - if (item != NULL) { - if (cmdq_get_client(item) != NULL && - cmdq_get_client(item)->session == NULL) - cmdq_get_client(item)->retval = retcode; - cmdq_continue(item); - } + if (item != NULL) { + if (cmdq_get_client(item) != NULL && + cmdq_get_client(item)->session == NULL) + cmdq_get_client(item)->retval = retcode; + cmdq_continue(item); + } return (0); } diff --git a/cmd-display-menu.c b/cmd-display-menu.c index e6a503b..d04a17c 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -38,8 +38,10 @@ const struct cmd_entry cmd_display_menu_entry = { .name = "display-menu", .alias = "menu", - .args = { "c:t:OT:x:y:", 1, -1, cmd_display_menu_args_parse }, - .usage = "[-O] [-c target-client] " CMD_TARGET_PANE_USAGE " [-T title] " + .args = { "b:c:C:H:s:S:Ot:T:x:y:", 1, -1, cmd_display_menu_args_parse }, + .usage = "[-O] [-b border-lines] [-c target-client] " + "[-C starting-choice] [-H selected-style] [-s style] " + "[-S border-style] " CMD_TARGET_PANE_USAGE "[-T title] " "[-x position] [-y position] name key command ...", .target = { 't', CMD_FIND_PANE, 0 }, @@ -274,6 +276,7 @@ cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item, log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h); free(p); + format_free(ft); return (1); } @@ -286,19 +289,41 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) struct client *tc = cmdq_get_target_client(item); struct menu *menu = NULL; struct menu_item menu_item; - const char *key, *name; - char *title; - int flags = 0; + const char *key, *name, *value; + const char *style = args_get(args, 's'); + const char *border_style = args_get(args, 'S'); + const char *selected_style = args_get(args, 'H'); + enum box_lines lines = BOX_LINES_DEFAULT; + char *title, *cause; + int flags = 0, starting_choice = 0; u_int px, py, i, count = args_count(args); + struct options *o = target->s->curw->window->options; + struct options_entry *oe; + if (tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); + if (args_has(args, 'C')) { + if (strcmp(args_get(args, 'C'), "-") == 0) + starting_choice = -1; + else { + starting_choice = args_strtonum(args, 'C', 0, UINT_MAX, + &cause); + if (cause != NULL) { + cmdq_error(item, "starting choice %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + } + if (args_has(args, 'T')) title = format_single_from_target(item, args_get(args, 'T')); else title = xstrdup(""); menu = menu_create(title); + free(title); for (i = 0; i != count; /* nothing */) { name = args_string(args, i++); @@ -309,7 +334,6 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) if (count - i < 2) { cmdq_error(item, "not enough arguments"); - free(title); menu_free(menu); return (CMD_RETURN_ERROR); } @@ -321,7 +345,6 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) menu_add_item(menu, &menu_item, item, tc, target); } - free(title); if (menu == NULL) { cmdq_error(item, "invalid menu arguments"); return (CMD_RETURN_ERROR); @@ -336,12 +359,24 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } + value = args_get(args, 'b'); + if (value != NULL) { + oe = options_get(o, "menu-border-lines"); + lines = options_find_choice(options_table_entry(oe), value, + &cause); + if (lines == -1) { + cmdq_error(item, "menu-border-lines %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'O')) flags |= MENU_STAYOPEN; if (!event->m.valid) flags |= MENU_NOMOUSE; - if (menu_display(menu, flags, item, px, py, tc, target, NULL, - NULL) != 0) + if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines, + style, selected_style, border_style, target, NULL, NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } @@ -454,11 +489,13 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) cmd_free_argv(argc, argv); if (env != NULL) environ_free(env); + free(cwd); free(title); return (CMD_RETURN_NORMAL); } if (env != NULL) environ_free(env); + free(cwd); free(title); cmd_free_argv(argc, argv); return (CMD_RETURN_WAIT); diff --git a/cmd-display-message.c b/cmd-display-message.c index 7828f69..512509f 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = { .name = "display-message", .alias = "display", - .args = { "ac:d:INpt:F:v", 0, 1, NULL }, - .usage = "[-aINpv] [-c target-client] [-d delay] [-F format] " + .args = { "ac:d:lINpt:F:v", 0, 1, NULL }, + .usage = "[-aIlNpv] [-c target-client] [-d delay] [-F format] " CMD_TARGET_PANE_USAGE " [message]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -68,9 +68,10 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) struct window_pane *wp = target->wp; const char *template; char *msg, *cause; - int delay = -1, flags; + int delay = -1, flags, Nflag = args_has(args, 'N'); struct format_tree *ft; u_int count = args_count(args); + struct evbuffer *evb; if (args_has(args, 'I')) { if (wp == NULL) @@ -132,15 +133,24 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - msg = format_expand_time(ft, template); + if (args_has(args, 'l')) + msg = xstrdup(template); + else + msg = format_expand_time(ft, template); + if (cmdq_get_client(item) == NULL) cmdq_error(item, "%s", msg); else if (args_has(args, 'p')) cmdq_print(item, "%s", msg); - else if (tc != NULL) { - status_message_set(tc, delay, 0, args_has(args, 'N'), "%s", - msg); - } + else if (tc != NULL && (tc->flags & CLIENT_CONTROL)) { + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); + evbuffer_add_printf(evb, "%%message %s", msg); + server_client_print(tc, 0, evb); + evbuffer_free(evb); + } else if (tc != NULL) + status_message_set(tc, delay, 0, Nflag, "%s", msg); free(msg); format_free(ft); diff --git a/cmd-display-panes.c b/cmd-display-panes.c index 5773a2d..06f6dc2 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-find-window.c b/cmd-find-window.c index 6e07537..5609983 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -48,6 +48,7 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) struct cmd_find_state *target = cmdq_get_target(item); struct window_pane *wp = target->wp; const char *s = args_string(args, 0), *suffix = ""; + const char *star = "*"; struct args_value *filter; int C, N, T; @@ -55,6 +56,8 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) N = args_has(args, 'N'); T = args_has(args, 'T'); + if (args_has(args, 'r')) + star = ""; if (args_has(args, 'r') && args_has(args, 'i')) suffix = "/ri"; else if (args_has(args, 'r')) @@ -71,40 +74,40 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) if (C && N && T) { xasprintf(&filter->string, "#{||:" - "#{C%s:%s},#{||:#{m%s:*%s*,#{window_name}}," - "#{m%s:*%s*,#{pane_title}}}}", - suffix, s, suffix, s, suffix, s); + "#{C%s:%s},#{||:#{m%s:%s%s%s,#{window_name}}," + "#{m%s:%s%s%s,#{pane_title}}}}", + suffix, s, suffix, star, s, star, suffix, star, s, star); } else if (C && N) { xasprintf(&filter->string, - "#{||:#{C%s:%s},#{m%s:*%s*,#{window_name}}}", - suffix, s, suffix, s); + "#{||:#{C%s:%s},#{m%s:%s%s%s,#{window_name}}}", + suffix, s, suffix, star, s, star); } else if (C && T) { xasprintf(&filter->string, - "#{||:#{C%s:%s},#{m%s:*%s*,#{pane_title}}}", - suffix, s, suffix, s); + "#{||:#{C%s:%s},#{m%s:%s%s%s,#{pane_title}}}", + suffix, s, suffix, star, s, star); } else if (N && T) { xasprintf(&filter->string, - "#{||:#{m%s:*%s*,#{window_name}}," - "#{m%s:*%s*,#{pane_title}}}", - suffix, s, suffix, s); + "#{||:#{m%s:%s%s%s,#{window_name}}," + "#{m%s:%s%s%s,#{pane_title}}}", + suffix, star, s, star, suffix, star, s, star); } else if (C) { xasprintf(&filter->string, "#{C%s:%s}", suffix, s); } else if (N) { xasprintf(&filter->string, - "#{m%s:*%s*,#{window_name}}", - suffix, s); + "#{m%s:%s%s%s,#{window_name}}", + suffix, star, s, star); } else { xasprintf(&filter->string, - "#{m%s:*%s*,#{pane_title}}", - suffix, s); + "#{m%s:%s%s%s,#{pane_title}}", + suffix, star, s, star); } new_args = args_create(); if (args_has(args, 'Z')) - args_set(new_args, 'Z', NULL); - args_set(new_args, 'f', filter); + args_set(new_args, 'Z', NULL, 0); + args_set(new_args, 'f', filter, 0); window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args); args_free(new_args); @@ -582,27 +582,27 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) /* Try special characters. */ if (strcmp(pane, "!") == 0) { - fs->wp = fs->w->last; + fs->wp = TAILQ_FIRST(&fs->w->last_panes); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{up-of}") == 0) { - fs->wp = window_pane_find_up(fs->current->wp); + fs->wp = window_pane_find_up(fs->w->active); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{down-of}") == 0) { - fs->wp = window_pane_find_down(fs->current->wp); + fs->wp = window_pane_find_down(fs->w->active); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{left-of}") == 0) { - fs->wp = window_pane_find_left(fs->current->wp); + fs->wp = window_pane_find_left(fs->w->active); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{right-of}") == 0) { - fs->wp = window_pane_find_right(fs->current->wp); + fs->wp = window_pane_find_right(fs->w->active); if (fs->wp == NULL) return (-1); return (0); @@ -614,7 +614,7 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) n = strtonum(pane + 1, 1, INT_MAX, NULL); else n = 1; - wp = fs->current->wp; + wp = fs->w->active; if (pane[0] == '+') fs->wp = window_pane_next_by_number(fs->w, wp, n); else diff --git a/cmd-join-pane.c b/cmd-join-pane.c index cb3fb34..da1ba9a 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -71,10 +71,11 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) struct window *src_w, *dst_w; struct window_pane *src_wp, *dst_wp; char *cause = NULL; - int size, percentage, dst_idx; + int size, dst_idx; int flags; enum layout_type type; struct layout_cell *lc; + u_int curval = 0; dst_s = target->s; dst_wl = target->wl; @@ -97,24 +98,31 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; - size = -1; - if (args_has(args, 'l')) { - if (type == LAYOUT_TOPBOTTOM) { - size = args_percentage(args, 'l', 0, INT_MAX, - dst_wp->sy, &cause); + /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ + if (args_has(args, 'l') || args_has(args, 'p')) { + if (args_has(args, 'f')) { + if (type == LAYOUT_TOPBOTTOM) + curval = dst_w->sy; + else + curval = dst_w->sx; } else { - size = args_percentage(args, 'l', 0, INT_MAX, - dst_wp->sx, &cause); - } - } else if (args_has(args, 'p')) { - percentage = args_strtonum(args, 'p', 0, 100, &cause); - if (cause == NULL) { if (type == LAYOUT_TOPBOTTOM) - size = (dst_wp->sy * percentage) / 100; + curval = dst_wp->sy; else - size = (dst_wp->sx * percentage) / 100; + curval = dst_wp->sx; } } + + size = -1; + if (args_has(args, 'l')) { + size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, + item, &cause); + } else if (args_has(args, 'p')) { + size = args_strtonum_and_expand(args, 'l', 0, 100, item, + &cause); + if (cause == NULL) + size = curval * size / 100; + } if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); @@ -147,6 +155,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) else TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); layout_assign_pane(lc, src_wp, 0); + colour_palette_from_option(&src_wp->palette, src_wp->options); recalculate_sizes(); diff --git a/cmd-list-clients.c b/cmd-list-clients.c index 53a9917..da7541b 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -41,8 +41,8 @@ const struct cmd_entry cmd_list_clients_entry = { .name = "list-clients", .alias = "lsc", - .args = { "F:t:", 0, 0, NULL }, - .usage = "[-F format] " CMD_TARGET_SESSION_USAGE, + .args = { "F:f:t:", 0, 0, NULL }, + .usage = "[-F format] [-f filter] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -58,9 +58,10 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) struct client *c; struct session *s; struct format_tree *ft; - const char *template; + const char *template, *filter; u_int idx; - char *line; + char *line, *expanded; + int flag; if (args_has(args, 't')) s = target->s; @@ -69,6 +70,7 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) if ((template = args_get(args, 'F')) == NULL) template = LIST_CLIENTS_TEMPLATE; + filter = args_get(args, 'f'); idx = 0; TAILQ_FOREACH(c, &clients, entry) { @@ -79,9 +81,17 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) format_add(ft, "line", "%u", idx); format_defaults(ft, c, NULL, NULL, NULL); - line = format_expand(ft, template); - cmdq_print(item, "%s", line); - free(line); + if (filter != NULL) { + expanded = format_expand(ft, filter); + flag = format_true(expanded); + free(expanded); + } else + flag = 1; + if (flag) { + line = format_expand(ft, template); + cmdq_print(item, "%s", line); + free(line); + } format_free(ft); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index ae9f995..395b147 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -148,6 +148,7 @@ static enum cmd_retval cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); struct key_table *table; struct key_binding *bd; const char *tablename, *r, *keystr; @@ -296,9 +297,15 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) strlcat(tmp, cp, tmpsize); free(cp); - cmdq_print(item, "bind-key %s", tmp); - + if (args_has(args, '1') && tc != NULL) { + status_message_set(tc, -1, 1, 0, "bind-key %s", + tmp); + } else + cmdq_print(item, "bind-key %s", tmp); free(key); + + if (args_has(args, '1')) + break; bd = key_bindings_next(table, bd); } table = key_bindings_next_table(table); diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index 59810de..70fd7ed 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 cb9abfb..c90369b 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-new-window.c b/cmd-new-window.c index e7f0868..f2d932d 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -60,7 +60,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) struct session *s = target->s; struct winlink *wl = target->wl, *new_wl = NULL; int idx = target->idx, before; - char *cause = NULL, *cp; + char *cause = NULL, *cp, *expanded; const char *template, *name; struct cmd_find_state fs; struct args_value *av; @@ -71,16 +71,19 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) */ name = args_get(args, 'n'); if (args_has(args, 'S') && name != NULL && target->idx == -1) { + expanded = format_single(item, name, c, s, NULL, NULL); RB_FOREACH(wl, winlinks, &s->windows) { - if (strcmp(wl->window->name, name) != 0) + if (strcmp(wl->window->name, expanded) != 0) continue; if (new_wl == NULL) { new_wl = wl; continue; } cmdq_error(item, "multiple windows named %s", name); + free(expanded); return (CMD_RETURN_ERROR); } + free(expanded); if (new_wl != NULL) { if (args_has(args, 'd')) return (CMD_RETURN_NORMAL); diff --git a/cmd-parse.c b/cmd-parse.c index 309c6fe..4be4890 100644 --- a/cmd-parse.c +++ b/cmd-parse.c @@ -845,7 +845,8 @@ cmd_parse_from_arguments(struct args_value *values, u_int count, arg->type = CMD_PARSE_STRING; arg->string = copy; TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry); - } + } else + free(copy); } else if (values[i].type == ARGS_COMMANDS) { arg = xcalloc(1, sizeof *arg); arg->type = CMD_PARSE_PARSED_COMMANDS; @@ -1373,13 +1374,24 @@ yylex_token(int ch) for (;;) { /* EOF or \n are always the end of the token. */ - if (ch == EOF || (state == NONE && ch == '\n')) + if (ch == EOF) { + log_debug("%s: end at EOF", __func__); + break; + } + if (state == NONE && ch == '\n') { + log_debug("%s: end at EOL", __func__); break; + } /* Whitespace or ; or } ends a token unless inside quotes. */ - if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') && - state == NONE) + if (state == NONE && (ch == ' ' || ch == '\t')) { + log_debug("%s: end at WS", __func__); + break; + } + if (state == NONE && (ch == ';' || ch == '}')) { + log_debug("%s: end at %c", __func__, ch); break; + } /* * Spaces and comments inside quotes after \n are removed but @@ -1462,7 +1474,7 @@ error: free(buf); return (NULL); } -#line 1458 "cmd-parse.c" +#line 1470 "cmd-parse.c" /* allocate initial stack or double stack size, up to YYMAXDEPTH */ static int yygrowstack(void) { @@ -2156,7 +2168,7 @@ case 46: free(yyvsp[-1].commands); } break; -#line 2152 "cmd-parse.c" +#line 2164 "cmd-parse.c" } yyssp -= yym; yystate = *yyssp; diff --git a/cmd-parse.y b/cmd-parse.y index 1d69277..65ffad8 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -1086,7 +1086,8 @@ cmd_parse_from_arguments(struct args_value *values, u_int count, arg->type = CMD_PARSE_STRING; arg->string = copy; TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry); - } + } else + free(copy); } else if (values[i].type == ARGS_COMMANDS) { arg = xcalloc(1, sizeof *arg); arg->type = CMD_PARSE_PARSED_COMMANDS; @@ -1614,13 +1615,24 @@ yylex_token(int ch) for (;;) { /* EOF or \n are always the end of the token. */ - if (ch == EOF || (state == NONE && ch == '\n')) + if (ch == EOF) { + log_debug("%s: end at EOF", __func__); + break; + } + if (state == NONE && ch == '\n') { + log_debug("%s: end at EOL", __func__); break; + } /* Whitespace or ; or } ends a token unless inside quotes. */ - if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') && - state == NONE) + if (state == NONE && (ch == ' ' || ch == '\t')) { + log_debug("%s: end at WS", __func__); + break; + } + if (state == NONE && (ch == ';' || ch == '}')) { + log_debug("%s: end at %c", __func__, ch); break; + } /* * Spaces and comments inside quotes after \n are removed but diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c index 36326e1..269e92f 100644 --- a/cmd-paste-buffer.c +++ b/cmd-paste-buffer.c @@ -54,6 +54,11 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item) size_t seplen, bufsize; int bracket = args_has(args, 'p'); + if (window_pane_exited(wp)) { + cmdq_error(item, "target pane has exited"); + return (CMD_RETURN_ERROR); + } + bufname = NULL; if (args_has(args, 'b')) bufname = args_get(args, 'b'); diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c index 0fa656c..268b51b 100644 --- a/cmd-pipe-pane.c +++ b/cmd-pipe-pane.c @@ -68,7 +68,7 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) sigset_t set, oldset; /* Do nothing if pane is dead. */ - if (wp->fd == -1 || (wp->flags & PANE_EXITED)) { + if (window_pane_exited(wp)) { cmdq_error(item, "target pane has exited"); return (CMD_RETURN_ERROR); } diff --git a/cmd-queue.c b/cmd-queue.c index 8325e2e..e188afc 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -236,8 +236,10 @@ cmdq_link_state(struct cmdq_state *state) /* Make a copy of a state. */ struct cmdq_state * -cmdq_copy_state(struct cmdq_state *state) +cmdq_copy_state(struct cmdq_state *state, struct cmd_find_state *current) { + if (current != NULL) + return (cmdq_new_state(current, &state->event, state->flags)); return (cmdq_new_state(&state->current, &state->event, state->flags)); } @@ -823,43 +825,28 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags) /* Show message from command. */ void +cmdq_print_data(struct cmdq_item *item, int parse, struct evbuffer *evb) +{ + server_client_print(item->client, parse, evb); +} + +/* Show message from command. */ +void cmdq_print(struct cmdq_item *item, const char *fmt, ...) { - struct client *c = item->client; - struct window_pane *wp; - struct window_mode_entry *wme; - va_list ap; - char *tmp, *msg; + va_list ap; + struct evbuffer *evb; + + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); va_start(ap, fmt); - xvasprintf(&msg, fmt, ap); + evbuffer_add_vprintf(evb, fmt, ap); va_end(ap); - log_debug("%s: %s", __func__, msg); - - if (c == NULL) - /* nothing */; - else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { - if (~c->flags & CLIENT_UTF8) { - tmp = msg; - msg = utf8_sanitize(tmp); - free(tmp); - } - if (c->flags & CLIENT_CONTROL) - control_write(c, "%s", msg); - else - file_print(c, "%s\n", msg); - } else { - wp = server_client_get_pane(c); - 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, 0, "%s", msg); - } - - free(msg); + cmdq_print_data(item, 0, evb); + evbuffer_free(evb); } /* Show error from command. */ diff --git a/cmd-resize-window.c b/cmd-resize-window.c index ad73916..c420451 100644 --- a/cmd-resize-window.c +++ b/cmd-resize-window.c @@ -53,8 +53,7 @@ cmd_resize_window_exec(struct cmd *self, struct cmdq_item *item) struct session *s = target->s; const char *errstr; char *cause; - u_int adjust, sx, sy; - int xpixel = -1, ypixel = -1; + u_int adjust, sx, sy, xpixel = 0, ypixel = 0; if (args_count(args) == 0) adjust = 1; diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 560efac..ddb5b1b 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -44,8 +44,9 @@ const struct cmd_entry cmd_run_shell_entry = { .name = "run-shell", .alias = "run", - .args = { "bd:Ct:", 0, 1, cmd_run_shell_args_parse }, - .usage = "[-bC] [-d delay] " CMD_TARGET_PANE_USAGE " [shell-command]", + .args = { "bd:Ct:c:", 0, 2, cmd_run_shell_args_parse }, + .usage = "[-bC] [-c start-directory] [-d delay] " CMD_TARGET_PANE_USAGE + " [shell-command]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -103,6 +104,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); struct cmd_run_shell_data *cdata; + struct client *c = cmdq_get_client(item); struct client *tc = cmdq_get_target_client(item); struct session *s = target->s; struct window_pane *wp = target->wp; @@ -137,7 +139,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) cdata->wp_id = -1; if (wait) { - cdata->client = cmdq_get_client(item); + cdata->client = c; cdata->item = item; } else { cdata->client = tc; @@ -145,8 +147,10 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) } if (cdata->client != NULL) cdata->client->references++; - - cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s)); + if (args_has(args, 'c')) + cdata->cwd = xstrdup(args_get(args, 'c')); + else + cdata->cwd = xstrdup(server_client_get_cwd(c, s)); cdata->s = s; if (s != NULL) diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 7d67837..3e81500 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -78,7 +78,8 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) int flags; const char *bufname = args_get(args, 'b'), *bufdata; size_t bufsize; - char *path, *tmp; + char *path; + struct evbuffer *evb; if (bufname == NULL) { if ((pb = paste_get_top(NULL)) == NULL) { @@ -96,10 +97,12 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) if (cmd_get_entry(self) == &cmd_show_buffer_entry) { if (c->session != NULL || (c->flags & CLIENT_CONTROL)) { - utf8_stravisx(&tmp, bufdata, bufsize, - VIS_OCTAL|VIS_CSTYLE|VIS_TAB); - cmdq_print(item, "%s", tmp); - free(tmp); + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); + evbuffer_add(evb, bufdata, bufsize); + cmdq_print_data(item, 1, evb); + evbuffer_free(evb); return (CMD_RETURN_NORMAL); } path = xstrdup("-"); diff --git a/cmd-select-pane.c b/cmd-select-pane.c index ae21d4c..135729f 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -98,7 +98,11 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) struct options_entry *o; if (entry == &cmd_last_pane_entry || args_has(args, 'l')) { - lastwp = w->last; + /* + * Check for no last pane found in case the other pane was + * spawned without being visited (for example split-window -d). + */ + lastwp = TAILQ_FIRST(&w->last_panes); if (lastwp == NULL && window_count_panes(w) == 2) { lastwp = TAILQ_PREV(w->active, window_panes, entry); if (lastwp == NULL) diff --git a/cmd-send-keys.c b/cmd-send-keys.c index d6a9543..ac99a6f 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -33,13 +33,13 @@ const struct cmd_entry cmd_send_keys_entry = { .name = "send-keys", .alias = "send", - .args = { "FHlMN:Rt:X", 0, -1, NULL }, - .usage = "[-FHlMRX] [-N repeat-count] " CMD_TARGET_PANE_USAGE - " key ...", + .args = { "c:FHKlMN:Rt:X", 0, -1, NULL }, + .usage = "[-FHKlMRX] [-c target-client] [-N repeat-count] " + CMD_TARGET_PANE_USAGE " key ...", .target = { 't', CMD_FIND_PANE, 0 }, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL, .exec = cmd_send_keys_exec }; @@ -58,7 +58,7 @@ const struct cmd_entry cmd_send_prefix_entry = { static struct cmdq_item * cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after, - key_code key) + struct args *args, key_code key) { struct cmd_find_state *target = cmdq_get_target(item); struct client *tc = cmdq_get_target_client(item); @@ -66,8 +66,20 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after, struct winlink *wl = target->wl; struct window_pane *wp = target->wp; struct window_mode_entry *wme; - struct key_table *table; + struct key_table *table = NULL; struct key_binding *bd; + struct key_event *event; + + if (args_has(args, 'K')) { + if (tc == NULL) + return (item); + event = xmalloc(sizeof *event); + event->key = key|KEYC_SENT; + memset(&event->m, 0, sizeof event->m); + if (server_client_handle_key(tc, event) == 0) + free(event); + return (item); + } wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode->key_table == NULL) { @@ -102,14 +114,16 @@ cmd_send_keys_inject_string(struct cmdq_item *item, struct cmdq_item *after, n = strtol(s, &endptr, 16); if (*s =='\0' || n < 0 || n > 0xff || *endptr != '\0') return (item); - return (cmd_send_keys_inject_key(item, after, KEYC_LITERAL|n)); + return (cmd_send_keys_inject_key(item, after, args, + KEYC_LITERAL|n)); } literal = args_has(args, 'l'); if (!literal) { key = key_string_lookup_string(s); if (key != KEYC_NONE && key != KEYC_UNKNOWN) { - after = cmd_send_keys_inject_key(item, after, key); + after = cmd_send_keys_inject_key(item, after, args, + key); if (after != NULL) return (after); } @@ -125,7 +139,8 @@ cmd_send_keys_inject_string(struct cmdq_item *item, struct cmdq_item *after, continue; key = uc; } - after = cmd_send_keys_inject_key(item, after, key); + after = cmd_send_keys_inject_key(item, after, args, + key); } free(ud); } @@ -151,7 +166,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); @@ -192,7 +208,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) key = options_get_number(s->options, "prefix2"); else key = options_get_number(s->options, "prefix"); - cmd_send_keys_inject_key(item, item, key); + cmd_send_keys_inject_key(item, item, args, key); return (CMD_RETURN_NORMAL); } @@ -206,7 +222,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'N') || args_has(args, 'R')) return (CMD_RETURN_NORMAL); for (; np != 0; np--) - cmd_send_keys_inject_key(item, NULL, event->key); + cmd_send_keys_inject_key(item, NULL, args, event->key); return (CMD_RETURN_NORMAL); } diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c index 9112683..35e7295 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-source-file.c b/cmd-source-file.c index 255d443..b390ed6 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -35,8 +35,10 @@ const struct cmd_entry cmd_source_file_entry = { .name = "source-file", .alias = "source", - .args = { "Fnqv", 1, -1, NULL }, - .usage = "[-Fnqv] path ...", + .args = { "t:Fnqv", 1, -1, NULL }, + .usage = "[-Fnqv] " CMD_TARGET_PANE_USAGE " path ...", + + .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .flags = 0, .exec = cmd_source_file_exec @@ -92,6 +94,7 @@ cmd_source_file_done(struct client *c, const char *path, int error, size_t bsize = EVBUFFER_LENGTH(buffer); u_int n; struct cmdq_item *new_item; + struct cmd_find_state *target = cmdq_get_target(item); if (!closed) return; @@ -100,7 +103,7 @@ cmd_source_file_done(struct client *c, const char *path, int error, cmdq_error(item, "%s: %s", path, strerror(error)); else if (bsize != 0) { if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after, - cdata->flags, &new_item) < 0) + target, cdata->flags, &new_item) < 0) cdata->retval = CMD_RETURN_ERROR; else if (new_item != NULL) cdata->after = new_item; diff --git a/cmd-split-window.c b/cmd-split-window.c index 9947dfd..637bad3 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 4191b89..6931bd1 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -128,10 +128,10 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) window_set_active_pane(dst_w, src_wp, 1); } if (src_w != dst_w) { - if (src_w->last == src_wp) - src_w->last = NULL; - if (dst_w->last == dst_wp) - dst_w->last = NULL; + window_pane_stack_remove(&src_w->last_panes, src_wp); + window_pane_stack_remove(&dst_w->last_panes, dst_wp); + colour_palette_from_option(&src_wp->palette, src_wp->options); + colour_palette_from_option(&dst_wp->palette, dst_wp->options); } server_redraw_window(src_w); server_redraw_window(dst_w); @@ -812,10 +812,14 @@ cmd_mouse_pane(struct mouse_event *m, struct session **sp, if ((wl = cmd_mouse_window(m, sp)) == NULL) return (NULL); - if ((wp = window_pane_find_by_id(m->wp)) == NULL) - return (NULL); - if (!window_has_pane(wl->window, wp)) - return (NULL); + if (m->wp == -1) + wp = wl->window->active; + else { + if ((wp = window_pane_find_by_id(m->wp)) == NULL) + return (NULL); + if (!window_has_pane(wl->window, wp)) + return (NULL); + } if (wlp != NULL) *wlp = wl; @@ -960,6 +960,47 @@ colour_byname(const char *name) return (-1); } +/* Parse colour from an X11 string. */ +int +colour_parseX11(const char *p) +{ + double c, m, y, k = 0; + u_int r, g, b; + size_t len = strlen(p); + int colour = -1; + char *copy; + + if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || + (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || + sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) + colour = colour_join_rgb(r, g, b); + else if ((len == 18 && + sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || + (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) + colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); + else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || + sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && + c >= 0 && c <= 1 && m >= 0 && m <= 1 && + y >= 0 && y <= 1 && k >= 0 && k <= 1) { + colour = colour_join_rgb( + (1 - c) * (1 - k) * 255, + (1 - m) * (1 - k) * 255, + (1 - y) * (1 - k) * 255); + } else { + while (len != 0 && *p == ' ') { + p++; + len--; + } + while (len != 0 && p[len - 1] == ' ') + len--; + copy = xstrndup(p, len); + colour = colour_byname(copy); + free(copy); + } + log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); + return (colour); +} + /* Initialize palette. */ void colour_palette_init(struct colour_palette *p) @@ -1069,5 +1110,4 @@ colour_palette_from_option(struct colour_palette *p, struct options *oo) } a = options_array_next(a); } - } @@ -334,6 +334,18 @@ char *strndup(const char *, size_t); void *memmem(const void *, size_t, const void *, size_t); #endif +#ifndef HAVE_HTONLL +/* htonll.c */ +#undef htonll +uint64_t htonll(uint64_t); +#endif + +#ifndef HAVE_NTOHLL +/* ntohll.c */ +#undef ntohll +uint64_t ntohll(uint64_t); +#endif + #ifndef HAVE_GETPEEREID /* getpeereid.c */ int getpeereid(int, uid_t *, gid_t *); @@ -423,7 +435,9 @@ void *recallocarray(void *, size_t, size_t, size_t); #ifdef HAVE_SYSTEMD /* systemd.c */ +int systemd_activated(void); int systemd_create_socket(int, char **); +int systemd_move_pid_to_new_cgroup(pid_t, char **); #endif #ifdef HAVE_UTF8PROC diff --git a/compat/getpeereid.c b/compat/getpeereid.c index c194e88..b79f420 100644 --- a/compat/getpeereid.c +++ b/compat/getpeereid.c @@ -18,6 +18,7 @@ #include <sys/socket.h> #include <stdio.h> +#include <unistd.h> #ifdef HAVE_UCRED_H #include <ucred.h> @@ -49,6 +50,8 @@ getpeereid(int s, uid_t *uid, gid_t *gid) ucred_free(ucred); return (0); #else - return (getuid()); + *uid = geteuid(); + *gid = getegid(); + return (0); #endif } diff --git a/compat/htonll.c b/compat/htonll.c new file mode 100644 index 0000000..aef6598 --- /dev/null +++ b/compat/htonll.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include "compat.h" + +uint64_t +htonll(uint64_t v) +{ + uint32_t b; + uint32_t t; + + b = htonl (v & 0xffffffff); + t = htonl (v >> 32); + return ((uint64_t)b << 32 | t); +} diff --git a/compat/imsg-buffer.c b/compat/imsg-buffer.c index 67d4c70..9aed0ed 100644 --- a/compat/imsg-buffer.c +++ b/compat/imsg-buffer.c @@ -1,6 +1,7 @@ -/* $OpenBSD: imsg-buffer.c,v 1.12 2019/01/20 02:50:03 bcook Exp $ */ +/* $OpenBSD: imsg-buffer.c,v 1.18 2023/12/12 15:47:41 claudio Exp $ */ /* + * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any @@ -19,9 +20,11 @@ #include <sys/types.h> #include <sys/socket.h> #include <sys/uio.h> +#include <arpa/inet.h> #include <limits.h> #include <errno.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -29,18 +32,36 @@ #include "compat.h" #include "imsg.h" +#undef htobe16 +#define htobe16 htons +#undef htobe32 +#define htobe32 htonl +#undef htobe64 +#define htobe64 htonll +#undef be16toh +#define be16toh ntohs +#undef be32toh +#define be32toh ntohl +#undef be64toh +#define be64toh ntohll + static int ibuf_realloc(struct ibuf *, size_t); static void ibuf_enqueue(struct msgbuf *, struct ibuf *); static void ibuf_dequeue(struct msgbuf *, struct ibuf *); +static void msgbuf_drain(struct msgbuf *, size_t); struct ibuf * ibuf_open(size_t len) { struct ibuf *buf; + if (len == 0) { + errno = EINVAL; + return (NULL); + } if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) return (NULL); - if ((buf->buf = malloc(len)) == NULL) { + if ((buf->buf = calloc(len, 1)) == NULL) { free(buf); return (NULL); } @@ -55,14 +76,22 @@ ibuf_dynamic(size_t len, size_t max) { struct ibuf *buf; - if (max < len) + if (max == 0 || max < len) { + errno = EINVAL; return (NULL); + } - if ((buf = ibuf_open(len)) == NULL) + if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) return (NULL); - - if (max > 0) - buf->max = max; + if (len > 0) { + if ((buf->buf = calloc(len, 1)) == NULL) { + free(buf); + return (NULL); + } + } + buf->size = len; + buf->max = max; + buf->fd = -1; return (buf); } @@ -73,7 +102,7 @@ ibuf_realloc(struct ibuf *buf, size_t len) unsigned char *b; /* on static buffers max is eq size and so the following fails */ - if (buf->wpos + len > buf->max) { + if (len > SIZE_MAX - buf->wpos || buf->wpos + len > buf->max) { errno = ERANGE; return (-1); } @@ -87,23 +116,16 @@ ibuf_realloc(struct ibuf *buf, size_t len) return (0); } -int -ibuf_add(struct ibuf *buf, const void *data, size_t len) -{ - if (buf->wpos + len > buf->size) - if (ibuf_realloc(buf, len) == -1) - return (-1); - - memcpy(buf->buf + buf->wpos, data, len); - buf->wpos += len; - return (0); -} - void * ibuf_reserve(struct ibuf *buf, size_t len) { void *b; + if (len > SIZE_MAX - buf->wpos || buf->max == 0) { + errno = ERANGE; + return (NULL); + } + if (buf->wpos + len > buf->size) if (ibuf_realloc(buf, len) == -1) return (NULL); @@ -113,34 +135,416 @@ ibuf_reserve(struct ibuf *buf, size_t len) return (b); } +int +ibuf_add(struct ibuf *buf, const void *data, size_t len) +{ + void *b; + + if ((b = ibuf_reserve(buf, len)) == NULL) + return (-1); + + memcpy(b, data, len); + return (0); +} + +int +ibuf_add_ibuf(struct ibuf *buf, const struct ibuf *from) +{ + return ibuf_add(buf, ibuf_data(from), ibuf_size(from)); +} + +/* remove after tree is converted */ +int +ibuf_add_buf(struct ibuf *buf, const struct ibuf *from) +{ + return ibuf_add_ibuf(buf, from); +} + +int +ibuf_add_n8(struct ibuf *buf, uint64_t value) +{ + uint8_t v; + + if (value > UINT8_MAX) { + errno = EINVAL; + return (-1); + } + v = value; + return ibuf_add(buf, &v, sizeof(v)); +} + +int +ibuf_add_n16(struct ibuf *buf, uint64_t value) +{ + uint16_t v; + + if (value > UINT16_MAX) { + errno = EINVAL; + return (-1); + } + v = htobe16(value); + return ibuf_add(buf, &v, sizeof(v)); +} + +int +ibuf_add_n32(struct ibuf *buf, uint64_t value) +{ + uint32_t v; + + if (value > UINT32_MAX) { + errno = EINVAL; + return (-1); + } + v = htobe32(value); + return ibuf_add(buf, &v, sizeof(v)); +} + +int +ibuf_add_n64(struct ibuf *buf, uint64_t value) +{ + value = htobe64(value); + return ibuf_add(buf, &value, sizeof(value)); +} + +int +ibuf_add_h16(struct ibuf *buf, uint64_t value) +{ + uint16_t v; + + if (value > UINT16_MAX) { + errno = EINVAL; + return (-1); + } + v = value; + return ibuf_add(buf, &v, sizeof(v)); +} + +int +ibuf_add_h32(struct ibuf *buf, uint64_t value) +{ + uint32_t v; + + if (value > UINT32_MAX) { + errno = EINVAL; + return (-1); + } + v = value; + return ibuf_add(buf, &v, sizeof(v)); +} + +int +ibuf_add_h64(struct ibuf *buf, uint64_t value) +{ + return ibuf_add(buf, &value, sizeof(value)); +} + +int +ibuf_add_zero(struct ibuf *buf, size_t len) +{ + void *b; + + if ((b = ibuf_reserve(buf, len)) == NULL) + return (-1); + memset(b, 0, len); + return (0); +} + void * ibuf_seek(struct ibuf *buf, size_t pos, size_t len) { - /* only allowed to seek in already written parts */ - if (pos + len > buf->wpos) + /* only allow seeking between rpos and wpos */ + if (ibuf_size(buf) < pos || SIZE_MAX - pos < len || + ibuf_size(buf) < pos + len) { + errno = ERANGE; return (NULL); + } - return (buf->buf + pos); + return (buf->buf + buf->rpos + pos); +} + +int +ibuf_set(struct ibuf *buf, size_t pos, const void *data, size_t len) +{ + void *b; + + if ((b = ibuf_seek(buf, pos, len)) == NULL) + return (-1); + + memcpy(b, data, len); + return (0); +} + +int +ibuf_set_n8(struct ibuf *buf, size_t pos, uint64_t value) +{ + uint8_t v; + + if (value > UINT8_MAX) { + errno = EINVAL; + return (-1); + } + v = value; + return (ibuf_set(buf, pos, &v, sizeof(v))); +} + +int +ibuf_set_n16(struct ibuf *buf, size_t pos, uint64_t value) +{ + uint16_t v; + + if (value > UINT16_MAX) { + errno = EINVAL; + return (-1); + } + v = htobe16(value); + return (ibuf_set(buf, pos, &v, sizeof(v))); +} + +int +ibuf_set_n32(struct ibuf *buf, size_t pos, uint64_t value) +{ + uint32_t v; + + if (value > UINT32_MAX) { + errno = EINVAL; + return (-1); + } + v = htobe32(value); + return (ibuf_set(buf, pos, &v, sizeof(v))); +} + +int +ibuf_set_n64(struct ibuf *buf, size_t pos, uint64_t value) +{ + value = htobe64(value); + return (ibuf_set(buf, pos, &value, sizeof(value))); +} + +int +ibuf_set_h16(struct ibuf *buf, size_t pos, uint64_t value) +{ + uint16_t v; + + if (value > UINT16_MAX) { + errno = EINVAL; + return (-1); + } + v = value; + return (ibuf_set(buf, pos, &v, sizeof(v))); +} + +int +ibuf_set_h32(struct ibuf *buf, size_t pos, uint64_t value) +{ + uint32_t v; + + if (value > UINT32_MAX) { + errno = EINVAL; + return (-1); + } + v = value; + return (ibuf_set(buf, pos, &v, sizeof(v))); +} + +int +ibuf_set_h64(struct ibuf *buf, size_t pos, uint64_t value) +{ + return (ibuf_set(buf, pos, &value, sizeof(value))); +} + +void * +ibuf_data(const struct ibuf *buf) +{ + return (buf->buf + buf->rpos); } size_t -ibuf_size(struct ibuf *buf) +ibuf_size(const struct ibuf *buf) { - return (buf->wpos); + return (buf->wpos - buf->rpos); } size_t -ibuf_left(struct ibuf *buf) +ibuf_left(const struct ibuf *buf) { + if (buf->max == 0) + return (0); return (buf->max - buf->wpos); } +int +ibuf_truncate(struct ibuf *buf, size_t len) +{ + if (ibuf_size(buf) >= len) { + buf->wpos = buf->rpos + len; + return (0); + } + if (buf->max == 0) { + /* only allow to truncate down */ + errno = ERANGE; + return (-1); + } + return ibuf_add_zero(buf, len - ibuf_size(buf)); +} + +void +ibuf_rewind(struct ibuf *buf) +{ + buf->rpos = 0; +} + void ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) { ibuf_enqueue(msgbuf, buf); } +void +ibuf_from_buffer(struct ibuf *buf, void *data, size_t len) +{ + memset(buf, 0, sizeof(*buf)); + buf->buf = data; + buf->size = buf->wpos = len; + buf->fd = -1; +} + +void +ibuf_from_ibuf(struct ibuf *buf, const struct ibuf *from) +{ + ibuf_from_buffer(buf, ibuf_data(from), ibuf_size(from)); +} + +int +ibuf_get(struct ibuf *buf, void *data, size_t len) +{ + if (ibuf_size(buf) < len) { + errno = EBADMSG; + return (-1); + } + + memcpy(data, ibuf_data(buf), len); + buf->rpos += len; + return (0); +} + +int +ibuf_get_ibuf(struct ibuf *buf, size_t len, struct ibuf *new) +{ + if (ibuf_size(buf) < len) { + errno = EBADMSG; + return (-1); + } + + ibuf_from_buffer(new, ibuf_data(buf), len); + buf->rpos += len; + return (0); +} + +int +ibuf_get_n8(struct ibuf *buf, uint8_t *value) +{ + return ibuf_get(buf, value, sizeof(*value)); +} + +int +ibuf_get_n16(struct ibuf *buf, uint16_t *value) +{ + int rv; + + rv = ibuf_get(buf, value, sizeof(*value)); + *value = be16toh(*value); + return (rv); +} + +int +ibuf_get_n32(struct ibuf *buf, uint32_t *value) +{ + int rv; + + rv = ibuf_get(buf, value, sizeof(*value)); + *value = be32toh(*value); + return (rv); +} + +int +ibuf_get_n64(struct ibuf *buf, uint64_t *value) +{ + int rv; + + rv = ibuf_get(buf, value, sizeof(*value)); + *value = be64toh(*value); + return (rv); +} + +int +ibuf_get_h16(struct ibuf *buf, uint16_t *value) +{ + return ibuf_get(buf, value, sizeof(*value)); +} + +int +ibuf_get_h32(struct ibuf *buf, uint32_t *value) +{ + return ibuf_get(buf, value, sizeof(*value)); +} + +int +ibuf_get_h64(struct ibuf *buf, uint64_t *value) +{ + return ibuf_get(buf, value, sizeof(*value)); +} + +int +ibuf_skip(struct ibuf *buf, size_t len) +{ + if (ibuf_size(buf) < len) { + errno = EBADMSG; + return (-1); + } + + buf->rpos += len; + return (0); +} + +void +ibuf_free(struct ibuf *buf) +{ + if (buf == NULL) + return; + if (buf->max == 0) /* if buf lives on the stack */ + abort(); /* abort before causing more harm */ + if (buf->fd != -1) + close(buf->fd); + freezero(buf->buf, buf->size); + free(buf); +} + +int +ibuf_fd_avail(struct ibuf *buf) +{ + return (buf->fd != -1); +} + +int +ibuf_fd_get(struct ibuf *buf) +{ + int fd; + + fd = buf->fd; + buf->fd = -1; + return (fd); +} + +void +ibuf_fd_set(struct ibuf *buf, int fd) +{ + if (buf->max == 0) /* if buf lives on the stack */ + abort(); /* abort before causing more harm */ + if (buf->fd != -1) + close(buf->fd); + buf->fd = fd; +} + int ibuf_write(struct msgbuf *msgbuf) { @@ -153,8 +557,8 @@ ibuf_write(struct msgbuf *msgbuf) TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { if (i >= IOV_MAX) break; - iov[i].iov_base = buf->buf + buf->rpos; - iov[i].iov_len = buf->wpos - buf->rpos; + iov[i].iov_base = ibuf_data(buf); + iov[i].iov_len = ibuf_size(buf); i++; } @@ -178,15 +582,6 @@ again: } void -ibuf_free(struct ibuf *buf) -{ - if (buf == NULL) - return; - freezero(buf->buf, buf->size); - free(buf); -} - -void msgbuf_init(struct msgbuf *msgbuf) { msgbuf->queued = 0; @@ -194,7 +589,7 @@ msgbuf_init(struct msgbuf *msgbuf) TAILQ_INIT(&msgbuf->bufs); } -void +static void msgbuf_drain(struct msgbuf *msgbuf, size_t n) { struct ibuf *buf, *next; @@ -202,8 +597,8 @@ msgbuf_drain(struct msgbuf *msgbuf, size_t n) for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; buf = next) { next = TAILQ_NEXT(buf, entry); - if (buf->rpos + n >= buf->wpos) { - n -= buf->wpos - buf->rpos; + if (n >= ibuf_size(buf)) { + n -= ibuf_size(buf); ibuf_dequeue(msgbuf, buf); } else { buf->rpos += n; @@ -225,7 +620,7 @@ int msgbuf_write(struct msgbuf *msgbuf) { struct iovec iov[IOV_MAX]; - struct ibuf *buf; + struct ibuf *buf, *buf0 = NULL; unsigned int i = 0; ssize_t n; struct msghdr msg; @@ -241,24 +636,26 @@ msgbuf_write(struct msgbuf *msgbuf) TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { if (i >= IOV_MAX) break; - iov[i].iov_base = buf->buf + buf->rpos; - iov[i].iov_len = buf->wpos - buf->rpos; + if (i > 0 && buf->fd != -1) + break; + iov[i].iov_base = ibuf_data(buf); + iov[i].iov_len = ibuf_size(buf); i++; if (buf->fd != -1) - break; + buf0 = buf; } msg.msg_iov = iov; msg.msg_iovlen = i; - if (buf != NULL && buf->fd != -1) { + if (buf0 != NULL) { msg.msg_control = (caddr_t)&cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; - *(int *)CMSG_DATA(cmsg) = buf->fd; + *(int *)CMSG_DATA(cmsg) = buf0->fd; } again: @@ -279,9 +676,9 @@ again: * assumption: fd got sent if sendmsg sent anything * this works because fds are passed one at a time */ - if (buf != NULL && buf->fd != -1) { - close(buf->fd); - buf->fd = -1; + if (buf0 != NULL) { + close(buf0->fd); + buf0->fd = -1; } msgbuf_drain(msgbuf, n); @@ -289,9 +686,17 @@ again: return (1); } +uint32_t +msgbuf_queuelen(struct msgbuf *msgbuf) +{ + return (msgbuf->queued); +} + static void ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) { + if (buf->max == 0) /* if buf lives on the stack */ + abort(); /* abort before causing more harm */ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); msgbuf->queued++; } @@ -300,10 +705,6 @@ static void ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) { TAILQ_REMOVE(&msgbuf->bufs, buf, entry); - - if (buf->fd != -1) - close(buf->fd); - msgbuf->queued--; ibuf_free(buf); } diff --git a/compat/imsg.c b/compat/imsg.c index 54ac7e5..c65cd30 100644 --- a/compat/imsg.c +++ b/compat/imsg.c @@ -1,6 +1,7 @@ -/* $OpenBSD: imsg.c,v 1.16 2017/12/14 09:27:44 kettenis Exp $ */ +/* $OpenBSD: imsg.c,v 1.23 2023/12/12 15:47:41 claudio Exp $ */ /* + * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any @@ -28,23 +29,28 @@ #include "compat.h" #include "imsg.h" +struct imsg_fd { + TAILQ_ENTRY(imsg_fd) entry; + int fd; +}; + int imsg_fd_overhead = 0; -static int imsg_get_fd(struct imsgbuf *); +static int imsg_dequeue_fd(struct imsgbuf *); void -imsg_init(struct imsgbuf *ibuf, int fd) +imsg_init(struct imsgbuf *imsgbuf, int fd) { - msgbuf_init(&ibuf->w); - memset(&ibuf->r, 0, sizeof(ibuf->r)); - ibuf->fd = fd; - ibuf->w.fd = fd; - ibuf->pid = getpid(); - TAILQ_INIT(&ibuf->fds); + msgbuf_init(&imsgbuf->w); + memset(&imsgbuf->r, 0, sizeof(imsgbuf->r)); + imsgbuf->fd = fd; + imsgbuf->w.fd = fd; + imsgbuf->pid = getpid(); + TAILQ_INIT(&imsgbuf->fds); } ssize_t -imsg_read(struct imsgbuf *ibuf) +imsg_read(struct imsgbuf *imsgbuf) { struct msghdr msg; struct cmsghdr *cmsg; @@ -60,8 +66,8 @@ imsg_read(struct imsgbuf *ibuf) memset(&msg, 0, sizeof(msg)); memset(&cmsgbuf, 0, sizeof(cmsgbuf)); - iov.iov_base = ibuf->r.buf + ibuf->r.wpos; - iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + iov.iov_base = imsgbuf->r.buf + imsgbuf->r.wpos; + iov.iov_len = sizeof(imsgbuf->r.buf) - imsgbuf->r.wpos; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &cmsgbuf.buf; @@ -79,13 +85,13 @@ again: return (-1); } - if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) { + if ((n = recvmsg(imsgbuf->fd, &msg, 0)) == -1) { if (errno == EINTR) goto again; goto fail; } - ibuf->r.wpos += n; + imsgbuf->r.wpos += n; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { @@ -105,7 +111,7 @@ again: fd = ((int *)CMSG_DATA(cmsg))[i]; if (ifd != NULL) { ifd->fd = fd; - TAILQ_INSERT_TAIL(&ibuf->fds, ifd, + TAILQ_INSERT_TAIL(&imsgbuf->fds, ifd, entry); ifd = NULL; } else @@ -121,94 +127,235 @@ fail: } ssize_t -imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) +imsg_get(struct imsgbuf *imsgbuf, struct imsg *imsg) { + struct imsg m; size_t av, left, datalen; - av = ibuf->r.wpos; + av = imsgbuf->r.wpos; if (IMSG_HEADER_SIZE > av) return (0); - memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); - if (imsg->hdr.len < IMSG_HEADER_SIZE || - imsg->hdr.len > MAX_IMSGSIZE) { + memcpy(&m.hdr, imsgbuf->r.buf, sizeof(m.hdr)); + if (m.hdr.len < IMSG_HEADER_SIZE || + m.hdr.len > MAX_IMSGSIZE) { errno = ERANGE; return (-1); } - if (imsg->hdr.len > av) + if (m.hdr.len > av) return (0); - datalen = imsg->hdr.len - IMSG_HEADER_SIZE; - ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; - if (datalen == 0) - imsg->data = NULL; - else if ((imsg->data = malloc(datalen)) == NULL) - return (-1); - if (imsg->hdr.flags & IMSGF_HASFD) - imsg->fd = imsg_get_fd(ibuf); - else - imsg->fd = -1; + m.fd = -1; + m.buf = NULL; + m.data = NULL; + + datalen = m.hdr.len - IMSG_HEADER_SIZE; + imsgbuf->r.rptr = imsgbuf->r.buf + IMSG_HEADER_SIZE; + if (datalen != 0) { + if ((m.buf = ibuf_open(datalen)) == NULL) + return (-1); + if (ibuf_add(m.buf, imsgbuf->r.rptr, datalen) == -1) { + /* this should never fail */ + ibuf_free(m.buf); + return (-1); + } + m.data = ibuf_data(m.buf); + } - memcpy(imsg->data, ibuf->r.rptr, datalen); + if (m.hdr.flags & IMSGF_HASFD) + m.fd = imsg_dequeue_fd(imsgbuf); - if (imsg->hdr.len < av) { - left = av - imsg->hdr.len; - memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); - ibuf->r.wpos = left; + if (m.hdr.len < av) { + left = av - m.hdr.len; + memmove(&imsgbuf->r.buf, imsgbuf->r.buf + m.hdr.len, left); + imsgbuf->r.wpos = left; } else - ibuf->r.wpos = 0; + imsgbuf->r.wpos = 0; + *imsg = m; return (datalen + IMSG_HEADER_SIZE); } int -imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, - int fd, const void *data, uint16_t datalen) +imsg_get_ibuf(struct imsg *imsg, struct ibuf *ibuf) +{ + if (imsg->buf == NULL) { + errno = EBADMSG; + return (-1); + } + return ibuf_get_ibuf(imsg->buf, ibuf_size(imsg->buf), ibuf); +} + +int +imsg_get_data(struct imsg *imsg, void *data, size_t len) +{ + if (len == 0) { + errno = EINVAL; + return (-1); + } + if (imsg->buf == NULL || ibuf_size(imsg->buf) != len) { + errno = EBADMSG; + return (-1); + } + return ibuf_get(imsg->buf, data, len); +} + +int +imsg_get_fd(struct imsg *imsg) +{ + int fd = imsg->fd; + + imsg->fd = -1; + return fd; +} + +uint32_t +imsg_get_id(struct imsg *imsg) +{ + return (imsg->hdr.peerid); +} + +size_t +imsg_get_len(struct imsg *imsg) +{ + if (imsg->buf == NULL) + return 0; + return ibuf_size(imsg->buf); +} + +pid_t +imsg_get_pid(struct imsg *imsg) +{ + return (imsg->hdr.pid); +} + +uint32_t +imsg_get_type(struct imsg *imsg) +{ + return (imsg->hdr.type); +} + +int +imsg_compose(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, + int fd, const void *data, size_t datalen) { struct ibuf *wbuf; - if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + if ((wbuf = imsg_create(imsgbuf, type, id, pid, datalen)) == NULL) return (-1); if (imsg_add(wbuf, data, datalen) == -1) return (-1); - wbuf->fd = fd; - - imsg_close(ibuf, wbuf); + ibuf_fd_set(wbuf, fd); + imsg_close(imsgbuf, wbuf); return (1); } int -imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, +imsg_composev(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, int fd, const struct iovec *iov, int iovcnt) { struct ibuf *wbuf; - int i, datalen = 0; + int i; + size_t datalen = 0; for (i = 0; i < iovcnt; i++) datalen += iov[i].iov_len; - if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + if ((wbuf = imsg_create(imsgbuf, type, id, pid, datalen)) == NULL) return (-1); for (i = 0; i < iovcnt; i++) if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) return (-1); - wbuf->fd = fd; + ibuf_fd_set(wbuf, fd); + imsg_close(imsgbuf, wbuf); - imsg_close(ibuf, wbuf); + return (1); +} + +/* + * Enqueue imsg with payload from ibuf buf. fd passing is not possible + * with this function. + */ +int +imsg_compose_ibuf(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, + pid_t pid, struct ibuf *buf) +{ + struct ibuf *hdrbuf = NULL; + struct imsg_hdr hdr; + int save_errno; + + if (ibuf_size(buf) + IMSG_HEADER_SIZE > MAX_IMSGSIZE) { + errno = ERANGE; + goto fail; + } + + hdr.type = type; + hdr.len = ibuf_size(buf) + IMSG_HEADER_SIZE; + hdr.flags = 0; + hdr.peerid = id; + if ((hdr.pid = pid) == 0) + hdr.pid = imsgbuf->pid; + if ((hdrbuf = ibuf_open(IMSG_HEADER_SIZE)) == NULL) + goto fail; + if (imsg_add(hdrbuf, &hdr, sizeof(hdr)) == -1) + goto fail; + + ibuf_close(&imsgbuf->w, hdrbuf); + ibuf_close(&imsgbuf->w, buf); + return (1); + + fail: + save_errno = errno; + ibuf_free(buf); + ibuf_free(hdrbuf); + errno = save_errno; + return (-1); +} + +/* + * Forward imsg to another channel. Any attached fd is closed. + */ +int +imsg_forward(struct imsgbuf *imsgbuf, struct imsg *msg) +{ + struct ibuf *wbuf; + size_t len = 0; + + if (msg->fd != -1) { + close(msg->fd); + msg->fd = -1; + } + + if (msg->buf != NULL) { + ibuf_rewind(msg->buf); + len = ibuf_size(msg->buf); + } + + if ((wbuf = imsg_create(imsgbuf, msg->hdr.type, msg->hdr.peerid, + msg->hdr.pid, len)) == NULL) + return (-1); + + if (msg->buf != NULL) { + if (ibuf_add_buf(wbuf, msg->buf) == -1) { + ibuf_free(wbuf); + return (-1); + } + } + + imsg_close(imsgbuf, wbuf); return (1); } -/* ARGSUSED */ struct ibuf * -imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, - uint16_t datalen) +imsg_create(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, + size_t datalen) { struct ibuf *wbuf; struct imsg_hdr hdr; @@ -221,9 +368,9 @@ imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, hdr.type = type; hdr.flags = 0; - hdr.peerid = peerid; + hdr.peerid = id; if ((hdr.pid = pid) == 0) - hdr.pid = ibuf->pid; + hdr.pid = imsgbuf->pid; if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { return (NULL); } @@ -234,7 +381,7 @@ imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, } int -imsg_add(struct ibuf *msg, const void *data, uint16_t datalen) +imsg_add(struct ibuf *msg, const void *data, size_t datalen) { if (datalen) if (ibuf_add(msg, data, datalen) == -1) { @@ -245,58 +392,57 @@ imsg_add(struct ibuf *msg, const void *data, uint16_t datalen) } void -imsg_close(struct imsgbuf *ibuf, struct ibuf *msg) +imsg_close(struct imsgbuf *imsgbuf, struct ibuf *msg) { struct imsg_hdr *hdr; hdr = (struct imsg_hdr *)msg->buf; hdr->flags &= ~IMSGF_HASFD; - if (msg->fd != -1) + if (ibuf_fd_avail(msg)) hdr->flags |= IMSGF_HASFD; + hdr->len = ibuf_size(msg); - hdr->len = (uint16_t)msg->wpos; - - ibuf_close(&ibuf->w, msg); + ibuf_close(&imsgbuf->w, msg); } void imsg_free(struct imsg *imsg) { - freezero(imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE); + ibuf_free(imsg->buf); } static int -imsg_get_fd(struct imsgbuf *ibuf) +imsg_dequeue_fd(struct imsgbuf *imsgbuf) { int fd; struct imsg_fd *ifd; - if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL) + if ((ifd = TAILQ_FIRST(&imsgbuf->fds)) == NULL) return (-1); fd = ifd->fd; - TAILQ_REMOVE(&ibuf->fds, ifd, entry); + TAILQ_REMOVE(&imsgbuf->fds, ifd, entry); free(ifd); return (fd); } int -imsg_flush(struct imsgbuf *ibuf) +imsg_flush(struct imsgbuf *imsgbuf) { - while (ibuf->w.queued) - if (msgbuf_write(&ibuf->w) <= 0) + while (imsgbuf->w.queued) + if (msgbuf_write(&imsgbuf->w) <= 0) return (-1); return (0); } void -imsg_clear(struct imsgbuf *ibuf) +imsg_clear(struct imsgbuf *imsgbuf) { int fd; - msgbuf_clear(&ibuf->w); - while ((fd = imsg_get_fd(ibuf)) != -1) + msgbuf_clear(&imsgbuf->w); + while ((fd = imsg_dequeue_fd(imsgbuf)) != -1) close(fd); } diff --git a/compat/imsg.h b/compat/imsg.h index 5b092cf..dd47b18 100644 --- a/compat/imsg.h +++ b/compat/imsg.h @@ -1,6 +1,7 @@ -/* $OpenBSD: imsg.h,v 1.5 2019/01/20 02:50:03 bcook Exp $ */ +/* $OpenBSD: imsg.h,v 1.8 2023/12/12 15:47:41 claudio Exp $ */ /* + * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org> * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org> * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> @@ -21,7 +22,7 @@ #ifndef _IMSG_H_ #define _IMSG_H_ -#include <stdint.h> +#include <sys/types.h> #define IBUF_READ_SIZE 65535 #define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) @@ -49,11 +50,7 @@ struct ibuf_read { size_t wpos; }; -struct imsg_fd { - TAILQ_ENTRY(imsg_fd) entry; - int fd; -}; - +struct imsg_fd; struct imsgbuf { TAILQ_HEAD(, imsg_fd) fds; struct ibuf_read r; @@ -76,35 +73,83 @@ struct imsg { struct imsg_hdr hdr; int fd; void *data; + struct ibuf *buf; }; +struct iovec; -/* buffer.c */ +/* imsg-buffer.c */ struct ibuf *ibuf_open(size_t); struct ibuf *ibuf_dynamic(size_t, size_t); int ibuf_add(struct ibuf *, const void *, size_t); +int ibuf_add_buf(struct ibuf *, const struct ibuf *); +int ibuf_add_ibuf(struct ibuf *, const struct ibuf *); +int ibuf_add_zero(struct ibuf *, size_t); +int ibuf_add_n8(struct ibuf *, uint64_t); +int ibuf_add_n16(struct ibuf *, uint64_t); +int ibuf_add_n32(struct ibuf *, uint64_t); +int ibuf_add_n64(struct ibuf *, uint64_t); +int ibuf_add_h16(struct ibuf *, uint64_t); +int ibuf_add_h32(struct ibuf *, uint64_t); +int ibuf_add_h64(struct ibuf *, uint64_t); void *ibuf_reserve(struct ibuf *, size_t); void *ibuf_seek(struct ibuf *, size_t, size_t); -size_t ibuf_size(struct ibuf *); -size_t ibuf_left(struct ibuf *); +int ibuf_set(struct ibuf *, size_t, const void *, size_t); +int ibuf_set_n8(struct ibuf *, size_t, uint64_t); +int ibuf_set_n16(struct ibuf *, size_t, uint64_t); +int ibuf_set_n32(struct ibuf *, size_t, uint64_t); +int ibuf_set_n64(struct ibuf *, size_t, uint64_t); +int ibuf_set_h16(struct ibuf *, size_t, uint64_t); +int ibuf_set_h32(struct ibuf *, size_t, uint64_t); +int ibuf_set_h64(struct ibuf *, size_t, uint64_t); +void *ibuf_data(const struct ibuf *); +size_t ibuf_size(const struct ibuf *); +size_t ibuf_left(const struct ibuf *); +int ibuf_truncate(struct ibuf *, size_t); +void ibuf_rewind(struct ibuf *); void ibuf_close(struct msgbuf *, struct ibuf *); -int ibuf_write(struct msgbuf *); +void ibuf_from_buffer(struct ibuf *, void *, size_t); +void ibuf_from_ibuf(struct ibuf *, const struct ibuf *); +int ibuf_get(struct ibuf *, void *, size_t); +int ibuf_get_ibuf(struct ibuf *, size_t, struct ibuf *); +int ibuf_get_n8(struct ibuf *, uint8_t *); +int ibuf_get_n16(struct ibuf *, uint16_t *); +int ibuf_get_n32(struct ibuf *, uint32_t *); +int ibuf_get_n64(struct ibuf *, uint64_t *); +int ibuf_get_h16(struct ibuf *, uint16_t *); +int ibuf_get_h32(struct ibuf *, uint32_t *); +int ibuf_get_h64(struct ibuf *, uint64_t *); +int ibuf_skip(struct ibuf *, size_t); void ibuf_free(struct ibuf *); +int ibuf_fd_avail(struct ibuf *); +int ibuf_fd_get(struct ibuf *); +void ibuf_fd_set(struct ibuf *, int); +int ibuf_write(struct msgbuf *); void msgbuf_init(struct msgbuf *); void msgbuf_clear(struct msgbuf *); +uint32_t msgbuf_queuelen(struct msgbuf *); int msgbuf_write(struct msgbuf *); -void msgbuf_drain(struct msgbuf *, size_t); /* imsg.c */ void imsg_init(struct imsgbuf *, int); ssize_t imsg_read(struct imsgbuf *); ssize_t imsg_get(struct imsgbuf *, struct imsg *); +int imsg_get_ibuf(struct imsg *, struct ibuf *); +int imsg_get_data(struct imsg *, void *, size_t); +int imsg_get_fd(struct imsg *); +uint32_t imsg_get_id(struct imsg *); +size_t imsg_get_len(struct imsg *); +pid_t imsg_get_pid(struct imsg *); +uint32_t imsg_get_type(struct imsg *); +int imsg_forward(struct imsgbuf *, struct imsg *); int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, - const void *, uint16_t); + const void *, size_t); int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const struct iovec *, int); -struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, uint16_t); -int imsg_add(struct ibuf *, const void *, uint16_t); +int imsg_compose_ibuf(struct imsgbuf *, uint32_t, uint32_t, pid_t, + struct ibuf *); +struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, size_t); +int imsg_add(struct ibuf *, const void *, size_t); void imsg_close(struct imsgbuf *, struct ibuf *); void imsg_free(struct imsg *); int imsg_flush(struct imsgbuf *); diff --git a/compat/ntohll.c b/compat/ntohll.c new file mode 100644 index 0000000..c2fe1bb --- /dev/null +++ b/compat/ntohll.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include "compat.h" + +uint64_t +ntohll(uint64_t v) +{ + uint32_t b; + uint32_t t; + + b = ntohl (v & 0xffffffff); + t = ntohl (v >> 32); + return ((uint64_t)b << 32 | t); +} diff --git a/compat/systemd.c b/compat/systemd.c index 7317e43..6f790a3 100644 --- a/compat/systemd.c +++ b/compat/systemd.c @@ -19,17 +19,28 @@ #include <sys/types.h> #include <sys/un.h> +#include <systemd/sd-bus.h> #include <systemd/sd-daemon.h> +#include <systemd/sd-login.h> +#include <systemd/sd-id128.h> + +#include <string.h> #include "tmux.h" int +systemd_activated(void) +{ + return (sd_listen_fds(0) >= 1); +} + +int systemd_create_socket(int flags, char **cause) { int fds; int fd; struct sockaddr_un sa; - int addrlen = sizeof sa; + socklen_t addrlen = sizeof sa; fds = sd_listen_fds(0); if (fds > 1) { /* too many file descriptors */ @@ -56,3 +67,149 @@ fail: xasprintf(cause, "systemd socket error (%s)", strerror(errno)); return (-1); } + +int +systemd_move_pid_to_new_cgroup(pid_t pid, char **cause) +{ + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL, *reply = NULL; + sd_bus *bus = NULL; + char *name, *desc, *slice; + sd_id128_t uuid; + int r; + pid_t parent_pid; + + /* Connect to the session bus. */ + r = sd_bus_default_user(&bus); + if (r < 0) { + xasprintf(cause, "failed to connect to session bus: %s", + strerror(-r)); + goto finish; + } + + /* Start building the method call. */ + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) { + xasprintf(cause, "failed to create bus message: %s", + strerror(-r)); + goto finish; + } + + /* Generate a unique name for the new scope, to avoid collisions. */ + r = sd_id128_randomize(&uuid); + if (r < 0) { + xasprintf(cause, "failed to generate uuid: %s", strerror(-r)); + goto finish; + } + xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope", + SD_ID128_FORMAT_VAL(uuid)); + r = sd_bus_message_append(m, "s", name); + free(name); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + goto finish; + } + + /* Mode: fail if there's a queued unit with the same name. */ + r = sd_bus_message_append(m, "s", "fail"); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + goto finish; + } + + /* Start properties array. */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) { + xasprintf(cause, "failed to start properties array: %s", + strerror(-r)); + goto finish; + } + + parent_pid = getpid(); + xasprintf(&desc, "tmux child pane %ld launched by process %ld", + (long)pid, (long)parent_pid); + r = sd_bus_message_append(m, "(sv)", "Description", "s", desc); + free(desc); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* + * Inherit the slice from the parent process, or default to + * "app-tmux.slice" if that fails. + */ + r = sd_pid_get_user_slice(parent_pid, &slice); + if (r < 0) { + slice = xstrdup("app-tmux.slice"); + } + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + free(slice); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* PIDs to add to the scope: length - 1 array of uint32_t. */ + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* Clean up the scope even if it fails. */ + r = sd_bus_message_append(m, "(sv)", "CollectMode", "s", + "inactive-or-failed"); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* End properties array. */ + r = sd_bus_message_close_container(m); + if (r < 0) { + xasprintf(cause, "failed to end properties array: %s", + strerror(-r)); + goto finish; + } + + /* aux is currently unused and should be passed an empty array. */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + goto finish; + } + + /* Call the method with a timeout of 1 second = 1e6 us. */ + r = sd_bus_call(bus, m, 1000000, &error, &reply); + if (r < 0) { + if (error.message != NULL) { + /* We have a specific error message from sd-bus. */ + xasprintf(cause, "StartTransientUnit call failed: %s", + error.message); + } else { + xasprintf(cause, "StartTransientUnit call failed: %s", + strerror(-r)); + } + goto finish; + } + +finish: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + sd_bus_message_unref(reply); + sd_bus_unref(bus); + + return (r); +} @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for tmux 3.3a. +# Generated by GNU Autoconf 2.69 for tmux 3.4. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -577,8 +577,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='tmux' PACKAGE_TARNAME='tmux' -PACKAGE_VERSION='3.3a' -PACKAGE_STRING='tmux 3.3a' +PACKAGE_VERSION='3.4' +PACKAGE_STRING='tmux 3.4' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -625,6 +625,8 @@ LTLIBOBJS AM_LDFLAGS AM_CFLAGS AM_CPPFLAGS +DEFAULT_LOCK_CMD +found_vlock IS_UNKNOWN_FALSE IS_UNKNOWN_TRUE IS_HAIKU_FALSE @@ -653,18 +655,23 @@ DEFAULT_TERM NEED_FORKPTY_FALSE NEED_FORKPTY_TRUE XOPEN_DEFINES +ENABLE_SIXEL_FALSE +ENABLE_SIXEL_TRUE HAVE_SYSTEMD_FALSE HAVE_SYSTEMD_TRUE SYSTEMD_LIBS SYSTEMD_CFLAGS HAVE_UTF8PROC_FALSE HAVE_UTF8PROC_TRUE +LIBUTF8PROC_LIBS +LIBUTF8PROC_CFLAGS LIBNCURSESW_LIBS LIBNCURSESW_CFLAGS LIBNCURSES_LIBS LIBNCURSES_CFLAGS LIBTINFO_LIBS LIBTINFO_CFLAGS +found_yacc LIBEVENT_LIBS LIBEVENT_CFLAGS LIBEVENT_CORE_LIBS @@ -790,6 +797,8 @@ with_TERM enable_utempter enable_utf8proc enable_systemd +enable_cgroups +enable_sixel ' ac_precious_vars='build_alias host_alias @@ -816,6 +825,8 @@ LIBNCURSES_CFLAGS LIBNCURSES_LIBS LIBNCURSESW_CFLAGS LIBNCURSESW_LIBS +LIBUTF8PROC_CFLAGS +LIBUTF8PROC_LIBS SYSTEMD_CFLAGS SYSTEMD_LIBS' @@ -1368,7 +1379,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures tmux 3.3a to adapt to many kinds of systems. +\`configure' configures tmux 3.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1439,7 +1450,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of tmux 3.3a:";; + short | recursive ) echo "Configuration of tmux 3.4:";; esac cat <<\_ACEOF @@ -1464,6 +1475,10 @@ Optional Features: --enable-systemd enable systemd integration + --disable-cgroups disable adding panes to new cgroups with systemd + + --enable-sixel enable sixel images + Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1512,6 +1527,10 @@ Some influential environment variables: C compiler flags for LIBNCURSESW, overriding pkg-config LIBNCURSESW_LIBS linker flags for LIBNCURSESW, overriding pkg-config + LIBUTF8PROC_CFLAGS + C compiler flags for LIBUTF8PROC, overriding pkg-config + LIBUTF8PROC_LIBS + linker flags for LIBUTF8PROC, overriding pkg-config SYSTEMD_CFLAGS C compiler flags for SYSTEMD, overriding pkg-config SYSTEMD_LIBS @@ -1583,7 +1602,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -tmux configure 3.3a +tmux configure 3.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1994,7 +2013,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by tmux $as_me 3.3a, which was +It was created by tmux $as_me 3.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2860,7 +2879,7 @@ fi # Define the identity of the package. PACKAGE='tmux' - VERSION='3.3a' + VERSION='3.4' cat >>confdefs.h <<_ACEOF @@ -5202,7 +5221,7 @@ for ac_func in \ prctl \ proc_pidinfo \ getpeerucred \ - sysconf \ + sysconf do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` @@ -5373,6 +5392,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "htonll" "ac_cv_func_htonll" +if test "x$ac_cv_func_htonll" = xyes; then : + $as_echo "#define HAVE_HTONLL 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" htonll.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS htonll.$ac_objext" + ;; +esac + +fi + ac_fn_c_check_func "$LINENO" "memmem" "ac_cv_func_memmem" if test "x$ac_cv_func_memmem" = xyes; then : $as_echo "#define HAVE_MEMMEM 1" >>confdefs.h @@ -5386,6 +5418,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "ntohll" "ac_cv_func_ntohll" +if test "x$ac_cv_func_ntohll" = xyes; then : + $as_echo "#define HAVE_NTOHLL 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" ntohll.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS ntohll.$ac_objext" + ;; +esac + +fi + ac_fn_c_check_func "$LINENO" "setenv" "ac_cv_func_setenv" if test "x$ac_cv_func_setenv" = xyes; then : $as_echo "#define HAVE_SETENV 1" >>confdefs.h @@ -5978,6 +6023,49 @@ if test "x$found_libevent" = xno; then as_fn_error $? "\"libevent not found\"" "$LINENO" 5 fi +# Look for yacc. +# Extract the first word of "$YACC", so it can be a program name with args. +set dummy $YACC; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_found_yacc+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$found_yacc"; then + ac_cv_prog_found_yacc="$found_yacc" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_found_yacc="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_found_yacc" && ac_cv_prog_found_yacc="no" +fi +fi +found_yacc=$ac_cv_prog_found_yacc +if test -n "$found_yacc"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_yacc" >&5 +$as_echo "$found_yacc" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +if test "x$found_yacc" = xno; then + as_fn_error $? "\"yacc not found\"" "$LINENO" 5 +fi + # Look for ncurses or curses. Try pkg-config first then directly for the # library. @@ -6243,7 +6331,7 @@ return setupterm (); return 0; } _ACEOF -for ac_lib in '' tinfo ncurses ncursesw; do +for ac_lib in '' tinfo terminfo ncurses ncursesw; do if test -z "$ac_lib"; then ac_res="none required" else @@ -6356,6 +6444,21 @@ fi as_fn_error $? "\"curses not found\"" "$LINENO" 5 fi fi +for ac_func in \ + tiparm \ + tiparm_s \ + +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + # Look for utempter. # Check whether --enable-utempter was given. @@ -6448,6 +6551,102 @@ if test "${enable_utf8proc+set}" = set; then : fi if test "x$enable_utf8proc" = xyes; then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libutf8proc" >&5 +$as_echo_n "checking for libutf8proc... " >&6; } + +if test -n "$LIBUTF8PROC_CFLAGS"; then + pkg_cv_LIBUTF8PROC_CFLAGS="$LIBUTF8PROC_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libutf8proc\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libutf8proc") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBUTF8PROC_CFLAGS=`$PKG_CONFIG --cflags "libutf8proc" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$LIBUTF8PROC_LIBS"; then + pkg_cv_LIBUTF8PROC_LIBS="$LIBUTF8PROC_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libutf8proc\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libutf8proc") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_LIBUTF8PROC_LIBS=`$PKG_CONFIG --libs "libutf8proc" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + LIBUTF8PROC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libutf8proc" 2>&1` + else + LIBUTF8PROC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libutf8proc" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$LIBUTF8PROC_PKG_ERRORS" >&5 + + as_fn_error $? "Package requirements (libutf8proc) were not met: + +$LIBUTF8PROC_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables LIBUTF8PROC_CFLAGS +and LIBUTF8PROC_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details." "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +Alternatively, you may set the environment variables LIBUTF8PROC_CFLAGS +and LIBUTF8PROC_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +To get pkg-config, see <http://pkg-config.freedesktop.org/>. +See \`config.log' for more details" "$LINENO" 5; } +else + LIBUTF8PROC_CFLAGS=$pkg_cv_LIBUTF8PROC_CFLAGS + LIBUTF8PROC_LIBS=$pkg_cv_LIBUTF8PROC_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBUTF8PROC_LIBS $LIBS" + + +fi ac_fn_c_check_header_mongrel "$LINENO" "utf8proc.h" "ac_cv_header_utf8proc_h" "$ac_includes_default" if test "x$ac_cv_header_utf8proc_h" = xyes; then : enable_utf8proc=yes @@ -6633,11 +6832,47 @@ else HAVE_SYSTEMD_FALSE= fi +# Check whether --enable-cgroups was given. +if test "${enable_cgroups+set}" = set; then : + enableval=$enable_cgroups; +fi + +if test "x$enable_cgroups" = x; then + # Default to the same as $enable_systemd. + enable_cgroups=$enable_systemd +fi +if test "x$enable_cgroups" = xyes; then + if test "x$found_systemd" = xyes; then + $as_echo "#define ENABLE_CGROUPS 1" >>confdefs.h + + else + as_fn_error $? "\"cgroups requires systemd to be enabled\"" "$LINENO" 5 + fi +fi + +# Enable sixel support. +# Check whether --enable-sixel was given. +if test "${enable_sixel+set}" = set; then : + enableval=$enable_sixel; +fi + +if test "x$enable_sixel" = xyes; then + $as_echo "#define ENABLE_SIXEL 1" >>confdefs.h + +fi + if test "x$enable_sixel" = xyes; then + ENABLE_SIXEL_TRUE= + ENABLE_SIXEL_FALSE='#' +else + ENABLE_SIXEL_TRUE='#' + ENABLE_SIXEL_FALSE= +fi + # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for b64_ntop" >&5 $as_echo_n "checking for b64_ntop... " >&6; } - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include <sys/types.h> @@ -7891,6 +8126,58 @@ else fi +# Set the default lock command +DEFAULT_LOCK_CMD="lock -np" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking lock-command" >&5 +$as_echo_n "checking lock-command... " >&6; } +if test "x$PLATFORM" = xlinux; then + # Extract the first word of "vlock", so it can be a program name with args. +set dummy vlock; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_found_vlock+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$found_vlock"; then + ac_cv_prog_found_vlock="$found_vlock" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_found_vlock="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_found_vlock" && ac_cv_prog_found_vlock="no" +fi +fi +found_vlock=$ac_cv_prog_found_vlock +if test -n "$found_vlock"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_vlock" >&5 +$as_echo "$found_vlock" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test "x$found_vlock" = xyes; then + DEFAULT_LOCK_CMD="vlock" + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $DEFAULT_LOCK_CMD" >&5 +$as_echo "$DEFAULT_LOCK_CMD" >&6; } + + + # Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user # variables. @@ -8096,6 +8383,10 @@ if test -z "${HAVE_SYSTEMD_TRUE}" && test -z "${HAVE_SYSTEMD_FALSE}"; then as_fn_error $? "conditional \"HAVE_SYSTEMD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${ENABLE_SIXEL_TRUE}" && test -z "${ENABLE_SIXEL_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_SIXEL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${NEED_FORKPTY_TRUE}" && test -z "${NEED_FORKPTY_FALSE}"; then as_fn_error $? "conditional \"NEED_FORKPTY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -8541,7 +8832,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by tmux $as_me 3.3a, which was +This file was extended by tmux $as_me 3.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -8598,7 +8889,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -tmux config.status 3.3a +tmux config.status 3.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 2b8b3b1..d800e45 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], 3.3a) +AC_INIT([tmux], 3.4) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) @@ -44,7 +44,7 @@ fi # Set up the compiler in two different ways and say yes we may want to install. AC_PROG_CC AM_PROG_CC_C_O -AC_PROG_CC_C99 +m4_version_prereq(2.70, [AC_PROG_CC], [AC_PROG_CC_C99]) AC_PROG_CPP AC_PROG_EGREP AC_PROG_INSTALL @@ -148,7 +148,7 @@ AC_CHECK_FUNCS([ \ prctl \ proc_pidinfo \ getpeerucred \ - sysconf \ + sysconf ]) # Check for functions with a compatibility implementation. @@ -165,7 +165,9 @@ AC_REPLACE_FUNCS([ \ getpeereid \ getline \ getprogname \ + htonll \ memmem \ + ntohll \ setenv \ setproctitle \ strcasestr \ @@ -267,6 +269,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( @@ -309,7 +317,7 @@ fi if test "x$found_ncurses" = xno; then AC_SEARCH_LIBS( setupterm, - [tinfo ncurses ncursesw], + [tinfo terminfo ncurses ncursesw], found_ncurses=yes, found_ncurses=no ) @@ -344,6 +352,10 @@ else AC_MSG_ERROR("curses not found") fi fi +AC_CHECK_FUNCS([ \ + tiparm \ + tiparm_s \ +]) # Look for utempter. AC_ARG_ENABLE( @@ -373,6 +385,15 @@ AC_ARG_ENABLE( AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed) ) if test "x$enable_utf8proc" = xyes; then + PKG_CHECK_MODULES( + LIBUTF8PROC, + libutf8proc, + [ + AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBUTF8PROC_LIBS $LIBS" + ] + ) AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no) if test "x$enable_utf8proc" = xyes; then AC_SEARCH_LIBS( @@ -414,10 +435,35 @@ if test x"$enable_systemd" = xyes; then fi fi AM_CONDITIONAL(HAVE_SYSTEMD, [test "x$found_systemd" = xyes]) +AC_ARG_ENABLE( + cgroups, + AS_HELP_STRING(--disable-cgroups, disable adding panes to new cgroups with systemd) +) +if test "x$enable_cgroups" = x; then + # Default to the same as $enable_systemd. + enable_cgroups=$enable_systemd +fi +if test "x$enable_cgroups" = xyes; then + if test "x$found_systemd" = xyes; then + AC_DEFINE(ENABLE_CGROUPS) + else + AC_MSG_ERROR("cgroups requires systemd to be enabled") + fi +fi + +# Enable sixel support. +AC_ARG_ENABLE( + sixel, + AS_HELP_STRING(--enable-sixel, enable sixel images) +) +if test "x$enable_sixel" = xyes; then + AC_DEFINE(ENABLE_SIXEL) +fi +AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes]) # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) - AC_LINK_IFELSE([AC_LANG_PROGRAM( +AC_LINK_IFELSE([AC_LANG_PROGRAM( [ #include <sys/types.h> #include <netinet/in.h> @@ -915,6 +961,19 @@ AM_CONDITIONAL(IS_HPUX, test "x$PLATFORM" = xhpux) AM_CONDITIONAL(IS_HAIKU, test "x$PLATFORM" = xhaiku) AM_CONDITIONAL(IS_UNKNOWN, test "x$PLATFORM" = xunknown) +# Set the default lock command +DEFAULT_LOCK_CMD="lock -np" +AC_MSG_CHECKING(lock-command) +if test "x$PLATFORM" = xlinux; then + AC_CHECK_PROG(found_vlock, vlock, yes, no) + if test "x$found_vlock" = xyes; then + DEFAULT_LOCK_CMD="vlock" + fi +fi +AC_MSG_RESULT($DEFAULT_LOCK_CMD) +AC_SUBST(DEFAULT_LOCK_CMD) + + # Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user # variables. AC_SUBST(AM_CPPFLAGS) diff --git a/control-notify.c b/control-notify.c index 6ff0e43..30f9419 100644 --- a/control-notify.c +++ b/control-notify.c @@ -234,3 +234,29 @@ 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-buffer-changed %s", name); + } +} + +void +control_notify_paste_buffer_deleted(const char *name) +{ + struct client *c; + + TAILQ_FOREACH(c, &clients, entry) { + if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) + continue; + + control_write(c, "%%paste-buffer-deleted %s", name); + } +} @@ -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) @@ -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); } } @@ -149,7 +149,8 @@ file_fire_done_cb(__unused int fd, __unused short events, void *arg) struct client_file *cf = arg; struct client *c = cf->c; - if (cf->cb != NULL && (c == NULL || (~c->flags & CLIENT_DEAD))) + if (cf->cb != NULL && + (cf->closed || c == NULL || (~c->flags & CLIENT_DEAD))) cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data); file_free(cf); } @@ -173,9 +174,9 @@ file_fire_read(struct client_file *cf) int file_can_print(struct client *c) { - if (c == NULL) - return (0); - if (c->session != NULL && (~c->flags & CLIENT_CONTROL)) + if (c == NULL || + (c->flags & CLIENT_ATTACHED) || + (c->flags & CLIENT_CONTROL)) return (0); return (1); } @@ -352,7 +353,7 @@ done: } /* Read a file. */ -void +struct client_file * file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) { struct client_file *cf; @@ -420,10 +421,27 @@ skip: goto done; } free(msg); - return; + return cf; done: file_fire_done(cf); + return NULL; +} + +/* Cancel a file read. */ +void +file_cancel(struct client_file *cf) +{ + struct msg_read_cancel msg; + + log_debug("read cancel file %d", cf->stream); + + if (cf->closed) + return; + cf->closed = 1; + + msg.stream = cf->stream; + proc_send(cf->peer, MSG_READ_CANCEL, -1, &msg, sizeof msg); } /* Push event, fired if there is more writing to be done. */ @@ -585,6 +603,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 +764,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; @@ -753,6 +775,24 @@ reply: proc_send(peer, MSG_READ_DONE, -1, &reply, sizeof reply); } +/* Handle a read cancel message (client). */ +void +file_read_cancel(struct client_files *files, struct imsg *imsg) +{ + struct msg_read_cancel *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_READ_CANCEL size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("cancel file %d", cf->stream); + + file_read_error_callback(NULL, 0, cf); +} + /* Handle a write ready message (server). */ void file_write_ready(struct client_files *files, struct imsg *imsg) @@ -790,7 +830,7 @@ file_read_data(struct client_files *files, struct imsg *imsg) return; log_debug("file %d read %zu bytes", cf->stream, bsize); - if (cf->error == 0) { + if (cf->error == 0 && !cf->closed) { if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { cf->error = ENOMEM; file_fire_done(cf); diff --git a/format-draw.c b/format-draw.c index 1a7e60b..a42dfe1 100644 --- a/format-draw.c +++ b/format-draw.c @@ -33,6 +33,7 @@ struct format_range { enum style_range_type type; u_int argument; + char string[16]; TAILQ_ENTRY(format_range) entry; }; @@ -44,9 +45,18 @@ format_is_type(struct format_range *fr, struct style *sy) { if (fr->type != sy->range_type) return (0); - if (fr->type == STYLE_RANGE_WINDOW && - fr->argument != sy->range_argument) - return (0); + switch (fr->type) { + case STYLE_RANGE_NONE: + case STYLE_RANGE_LEFT: + case STYLE_RANGE_RIGHT: + return (1); + case STYLE_RANGE_PANE: + case STYLE_RANGE_WINDOW: + case STYLE_RANGE_SESSION: + return (fr->argument == sy->range_argument); + case STYLE_RANGE_USER: + return (strcmp(fr->string, sy->range_string) == 0); + } return (1); } @@ -942,6 +952,8 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, fr->type = sy.range_type; fr->argument = sy.range_argument; + strlcpy(fr->string, sy.range_string, + sizeof fr->string); } } @@ -1013,13 +1025,39 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, sr = xcalloc(1, sizeof *sr); sr->type = fr->type; sr->argument = fr->argument; + strlcpy(sr->string, fr->string, sizeof sr->string); sr->start = fr->start; sr->end = fr->end; TAILQ_INSERT_TAIL(srs, sr, entry); - log_debug("%s: range %d|%u at %u-%u", __func__, sr->type, - sr->argument, sr->start, sr->end); - + switch (sr->type) { + case STYLE_RANGE_NONE: + break; + case STYLE_RANGE_LEFT: + log_debug("%s: range left at %u-%u", __func__, + sr->start, sr->end); + break; + case STYLE_RANGE_RIGHT: + log_debug("%s: range right at %u-%u", __func__, + sr->start, sr->end); + break; + case STYLE_RANGE_PANE: + log_debug("%s: range pane|%%%u at %u-%u", __func__, + sr->argument, sr->start, sr->end); + break; + case STYLE_RANGE_WINDOW: + log_debug("%s: range window|%u at %u-%u", __func__, + sr->argument, sr->start, sr->end); + break; + case STYLE_RANGE_SESSION: + log_debug("%s: range session|$%u at %u-%u", __func__, + sr->argument, sr->start, sr->end); + break; + case STYLE_RANGE_USER: + log_debug("%s: range user|%u at %u-%u", __func__, + sr->argument, sr->start, sr->end); + break; + } format_free_range(&frs, fr); } @@ -1083,7 +1121,7 @@ format_trim_left(const char *expanded, u_int limit) struct utf8_data ud; enum utf8_state more; - out = copy = xcalloc(1, strlen(expanded) + 1); + out = copy = xcalloc(2, strlen(expanded) + 1); while (*cp != '\0') { if (width >= limit) break; @@ -1150,7 +1188,7 @@ format_trim_right(const char *expanded, u_int limit) return (xstrdup(expanded)); skip = total_width - limit; - out = copy = xcalloc(1, strlen(expanded) + 1); + out = copy = xcalloc(2, strlen(expanded) + 1); while (*cp != '\0') { if (*cp == '#') { end = format_leading_hashes(cp, &n, &leading_width); @@ -103,6 +103,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_SESSION_NAME 0x8000 #define FORMAT_CHARACTER 0x10000 #define FORMAT_COLOUR 0x20000 +#define FORMAT_CLIENTS 0x40000 /* Limit on recursion. */ #define FORMAT_LOOP_LIMIT 100 @@ -1125,7 +1126,6 @@ format_cb_mouse_word(struct format_tree *ft) struct window_pane *wp; struct grid *gd; u_int x, y; - char *s; if (!ft->m.valid) return (NULL); @@ -1138,13 +1138,32 @@ format_cb_mouse_word(struct format_tree *ft) if (!TAILQ_EMPTY(&wp->modes)) { if (TAILQ_FIRST(&wp->modes)->mode == &window_copy_mode || TAILQ_FIRST(&wp->modes)->mode == &window_view_mode) - return (s = window_copy_get_word(wp, x, y)); + return (window_copy_get_word(wp, x, y)); return (NULL); } gd = wp->base.grid; 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) @@ -1171,6 +1190,72 @@ format_cb_mouse_line(struct format_tree *ft) return (format_grid_line(gd, gd->hsize + y)); } +/* Callback for mouse_status_line. */ +static void * +format_cb_mouse_status_line(struct format_tree *ft) +{ + char *value; + u_int y; + + if (!ft->m.valid) + return (NULL); + if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED)) + return (NULL); + + if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) { + y = ft->m.y; + } else if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) { + y = ft->m.y - ft->m.statusat; + } else + return (NULL); + xasprintf(&value, "%u", y); + return (value); + +} + +/* Callback for mouse_status_range. */ +static void * +format_cb_mouse_status_range(struct format_tree *ft) +{ + struct style_range *sr; + u_int x, y; + + if (!ft->m.valid) + return (NULL); + if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED)) + return (NULL); + + if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) { + x = ft->m.x; + y = ft->m.y; + } else if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) { + x = ft->m.x; + y = ft->m.y - ft->m.statusat; + } else + return (NULL); + + sr = status_get_range(ft->c, x, y); + if (sr == NULL) + return (NULL); + switch (sr->type) { + case STYLE_RANGE_NONE: + return (NULL); + case STYLE_RANGE_LEFT: + return (xstrdup("left")); + case STYLE_RANGE_RIGHT: + return (xstrdup("right")); + case STYLE_RANGE_PANE: + return (xstrdup("pane")); + case STYLE_RANGE_WINDOW: + return (xstrdup("window")); + case STYLE_RANGE_SESSION: + return (xstrdup("session")); + case STYLE_RANGE_USER: + return (xstrdup(sr->string)); + } + return (NULL); +} + /* Callback for alternate_on. */ static void * format_cb_alternate_on(struct format_tree *ft) @@ -1865,12 +1950,24 @@ format_cb_pane_input_off(struct format_tree *ft) return (NULL); } +/* Callback for pane_unseen_changes. */ +static void * +format_cb_pane_unseen_changes(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->flags & PANE_UNSEENCHANGES) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + /* Callback for pane_last. */ static void * format_cb_pane_last(struct format_tree *ft) { if (ft->wp != NULL) { - if (ft->wp == ft->wp->window->last) + if (ft->wp == TAILQ_FIRST(&ft->wp->window->last_panes)) return (xstrdup("1")); return (xstrdup("0")); } @@ -2045,6 +2142,18 @@ format_cb_scroll_region_upper(struct format_tree *ft) return (NULL); } +/* Callback for server_sessions. */ +static void * +format_cb_server_sessions(__unused struct format_tree *ft) +{ + struct session *s; + u_int n = 0; + + RB_FOREACH(s, sessions, &sessions) + n++; + return (format_printf("%u", n)); +} + /* Callback for session_attached. */ static void * format_cb_session_attached(struct format_tree *ft) @@ -2789,6 +2898,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 }, @@ -2801,6 +2913,12 @@ static const struct format_table_entry format_table[] = { { "mouse_standard_flag", FORMAT_TABLE_STRING, format_cb_mouse_standard_flag }, + { "mouse_status_line", FORMAT_TABLE_STRING, + format_cb_mouse_status_line + }, + { "mouse_status_range", FORMAT_TABLE_STRING, + format_cb_mouse_status_range + }, { "mouse_utf8_flag", FORMAT_TABLE_STRING, format_cb_mouse_utf8_flag }, @@ -2930,6 +3048,9 @@ static const struct format_table_entry format_table[] = { { "pane_tty", FORMAT_TABLE_STRING, format_cb_pane_tty }, + { "pane_unseen_changes", FORMAT_TABLE_STRING, + format_cb_pane_unseen_changes + }, { "pane_width", FORMAT_TABLE_STRING, format_cb_pane_width }, @@ -2942,6 +3063,9 @@ static const struct format_table_entry format_table[] = { { "scroll_region_upper", FORMAT_TABLE_STRING, format_cb_scroll_region_upper }, + { "server_sessions", FORMAT_TABLE_STRING, + format_cb_server_sessions + }, { "session_activity", FORMAT_TABLE_TIME, format_cb_session_activity }, @@ -3387,12 +3511,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) @@ -3404,7 +3528,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)); } @@ -3509,7 +3636,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); @@ -3539,18 +3666,43 @@ 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); } -/* Remove escaped characters from string. */ +/* Unescape escaped characters. */ +static char * +format_unescape(const char *s) +{ + char *out, *cp; + int brackets = 0; + + cp = out = xmalloc(strlen(s) + 1); + for (; *s != '\0'; s++) { + if (*s == '#' && s[1] == '{') + brackets++; + if (brackets == 0 && + *s == '#' && + strchr(",#{}:", s[1]) != NULL) { + *cp++ = *++s; + continue; + } + if (*s == '}') + brackets--; + *cp++ = *s; + } + *cp = '\0'; + return (out); +} + +/* Remove escaped characters. */ static char * format_strip(const char *s) { @@ -3583,7 +3735,9 @@ format_skip(const char *s, const char *end) for (; *s != '\0'; s++) { if (*s == '#' && s[1] == '{') brackets++; - if (*s == '#' && strchr(",#{}:", s[1]) != NULL) { + if (*s == '#' && + s[1] != '\0' && + strchr(",#{}:", s[1]) != NULL) { s++; continue; } @@ -3697,7 +3851,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, cp++; /* Check single character modifiers with no arguments. */ - if (strchr("labcdnwETSWP<>", cp[0]) != NULL && + if (strchr("labcdnwETSWPL<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -3732,7 +3886,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s, argc = 0; /* Single argument with no wrapper character. */ - if (!ispunct(cp[1]) || cp[1] == '-') { + if (!ispunct((u_char)cp[1]) || cp[1] == '-') { end = format_skip(cp + 1, ":;"); if (end == NULL) break; @@ -4025,6 +4179,40 @@ format_loop_panes(struct format_expand_state *es, const char *fmt) return (value); } +/* Loop over clients. */ +static char * +format_loop_clients(struct format_expand_state *es, const char *fmt) +{ + struct format_tree *ft = es->ft; + struct client *c; + struct cmdq_item *item = ft->item; + struct format_tree *nft; + struct format_expand_state next; + char *expanded, *value; + size_t valuelen; + + value = xcalloc(1, 1); + valuelen = 1; + + TAILQ_FOREACH(c, &clients, entry) { + format_log(es, "client loop: %s", c->name); + nft = format_create(c, item, 0, ft->flags); + format_defaults(nft, c, ft->s, ft->wl, ft->wp); + format_copy_state(&next, es, 0); + next.ft = nft; + expanded = format_expand1(&next, fmt); + format_free(nft); + + valuelen += strlen(expanded); + value = xrealloc(value, valuelen); + + strlcat(value, expanded, valuelen); + free(expanded); + } + + return (value); +} + static char * format_replace_expression(struct format_modifier *mexp, struct format_expand_state *es, const char *copy) @@ -4299,6 +4487,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, case 'P': modifiers |= FORMAT_PANES; break; + case 'L': + modifiers |= FORMAT_CLIENTS; + break; } } else if (fm->size == 2) { if (strcmp(fm->modifier, "||") == 0 || @@ -4313,7 +4504,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, /* Is this a literal string? */ if (modifiers & FORMAT_LITERAL) { - value = xstrdup(copy); + format_log(es, "literal string is '%s'", copy); + value = format_unescape(copy); goto done; } @@ -4354,6 +4546,10 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen, value = format_loop_panes(es, copy); if (value == NULL) goto fail; + } else if (modifiers & FORMAT_CLIENTS) { + value = format_loop_clients(es, copy); + if (value == NULL) + goto fail; } else if (modifiers & FORMAT_WINDOW_NAME) { value = format_window_name(es, copy); if (value == NULL) @@ -4603,7 +4799,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]; @@ -4698,18 +4894,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); @@ -4732,10 +4930,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); @@ -5057,3 +5257,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 f230d3c..4d68733 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, NULL)); } @@ -37,7 +37,7 @@ /* Default grid cell data. */ const struct grid_cell grid_default_cell = { - { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 + { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 8, 0 }; /* @@ -45,15 +45,15 @@ 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, 8, 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, 8, 0 }; static const struct grid_cell_entry grid_cleared_entry = { - GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } } + { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED }; /* Store cell in entry. */ @@ -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; @@ -520,8 +528,9 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->bg = gce->data.bg; if (gce->flags & GRID_FLAG_BG256) gc->bg |= COLOUR_FLAG_256; - gc->us = 0; + gc->us = 8; utf8_set(&gc->data, gce->data.data); + gc->link = 0; } /* Get cell for reading. */ @@ -852,28 +861,60 @@ grid_string_cells_us(const struct grid_cell *gc, int *values) /* Add on SGR code. */ static void grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc, - int *oldc, size_t nnewc, size_t noldc, int escape_c0) + int *oldc, size_t nnewc, size_t noldc, int flags) { u_int i; char tmp[64]; - - if (nnewc != 0 && - (nnewc != noldc || - memcmp(newc, oldc, nnewc * sizeof newc[0]) != 0 || - (n != 0 && s[0] == 0))) { - if (escape_c0) - strlcat(buf, "\\033[", len); + int reset = (n != 0 && s[0] == 0); + + if (nnewc == 0) + return; /* no code to add */ + if (!reset && + nnewc == noldc && + memcmp(newc, oldc, nnewc * sizeof newc[0]) == 0) + return; /* no reset and colour unchanged */ + if (reset && (newc[0] == 49 || newc[0] == 39)) + return; /* reset and colour default */ + + if (flags & GRID_STRING_ESCAPE_SEQUENCES) + strlcat(buf, "\\033[", len); + else + strlcat(buf, "\033[", len); + for (i = 0; i < nnewc; i++) { + if (i + 1 < nnewc) + xsnprintf(tmp, sizeof tmp, "%d;", newc[i]); else - strlcat(buf, "\033[", len); - for (i = 0; i < nnewc; i++) { - if (i + 1 < nnewc) - xsnprintf(tmp, sizeof tmp, "%d;", newc[i]); - else - xsnprintf(tmp, sizeof tmp, "%d", newc[i]); - strlcat(buf, tmp, len); - } - strlcat(buf, "m", len); + xsnprintf(tmp, sizeof tmp, "%d", newc[i]); + strlcat(buf, tmp, len); } + strlcat(buf, "m", len); +} + +static int +grid_string_cells_add_hyperlink(char *buf, size_t len, const char *id, + const char *uri, int flags) +{ + char *tmp; + + if (strlen(uri) + strlen(id) + 17 >= len) + return (0); + + if (flags & GRID_STRING_ESCAPE_SEQUENCES) + 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 (flags & GRID_STRING_ESCAPE_SEQUENCES) + strlcat(buf, "\\033\\\\", len); + else + strlcat(buf, "\033\\", len); + return (1); } /* @@ -882,14 +923,16 @@ grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc, */ 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 flags, + 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 { + static const struct { u_int mask; u_int code; } attrs[] = { @@ -913,7 +956,7 @@ grid_string_cells_code(const struct grid_cell *lastgc, for (i = 0; i < nitems(attrs); i++) { if (((~attr & attrs[i].mask) && (lastattr & attrs[i].mask)) || - (lastgc->us != 0 && gc->us == 0)) { + (lastgc->us != 8 && gc->us == 8)) { s[n++] = 0; lastattr &= GRID_ATTR_CHARSET; break; @@ -928,7 +971,7 @@ grid_string_cells_code(const struct grid_cell *lastgc, /* Write the attributes. */ *buf = '\0'; if (n > 0) { - if (escape_c0) + if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\033[", len); else strlcat(buf, "\033[", len); @@ -950,46 +993,59 @@ grid_string_cells_code(const struct grid_cell *lastgc, nnewc = grid_string_cells_fg(gc, newc); noldc = grid_string_cells_fg(lastgc, oldc); grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, - escape_c0); + flags); /* If the background colour changed, append its parameters. */ nnewc = grid_string_cells_bg(gc, newc); noldc = grid_string_cells_bg(lastgc, oldc); grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, - escape_c0); + flags); /* If the underscore colour changed, append its parameters. */ nnewc = grid_string_cells_us(gc, newc); noldc = grid_string_cells_us(lastgc, oldc); grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, - escape_c0); + flags); /* Append shift in/shift out if needed. */ if ((attr & GRID_ATTR_CHARSET) && !(lastattr & GRID_ATTR_CHARSET)) { - if (escape_c0) + if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\016", len); /* SO */ else strlcat(buf, "\016", len); /* SO */ } if (!(attr & GRID_ATTR_CHARSET) && (lastattr & GRID_ATTR_CHARSET)) { - if (escape_c0) + if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\017", len); /* SI */ 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, flags); + } else if (*has_link) { + grid_string_cells_add_hyperlink(buf, len, "", "", + flags); + *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 flags, 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, end; + int has_link = 0; const struct grid_line *gl; if (lastgc != NULL && *lastgc == NULL) { @@ -1002,16 +1058,20 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, off = 0; gl = grid_peek_line(gd, py); + if (flags & GRID_STRING_EMPTY_CELLS) + end = gl->cellsize; + else + end = gl->cellused; for (xx = px; xx < px + nx; xx++) { - if (gl == NULL || xx >= gl->cellused) + if (gl == NULL || xx >= end) break; grid_get_cell(gd, xx, py, &gc); if (gc.flags & GRID_FLAG_PADDING) continue; - if (with_codes) { + if (flags & GRID_STRING_WITH_SEQUENCES) { grid_string_cells_code(*lastgc, &gc, code, sizeof code, - escape_c0); + flags, s, &has_link); codelen = strlen(code); memcpy(*lastgc, &gc, sizeof **lastgc); } else @@ -1019,7 +1079,9 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, data = gc.data.data; size = gc.data.size; - if (escape_c0 && size == 1 && *data == '\\') { + if ((flags & GRID_STRING_ESCAPE_SEQUENCES) && + size == 1 && + *data == '\\') { data = "\\\\"; size = 2; } @@ -1037,7 +1099,19 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, off += size; } - if (trim) { + if (has_link) { + grid_string_cells_add_hyperlink(code, sizeof code, "", "", + flags); + codelen = strlen(code); + while (len < off + size + codelen + 1) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, code, codelen); + off += codelen; + } + + if (flags & GRID_STRING_TRIM_SPACES) { while (off > 0 && buf[off - 1] == ' ') off--; } diff --git a/hyperlinks.c b/hyperlinks.c new file mode 100644 index 0000000..70c2f4e --- /dev/null +++ b/hyperlinks.c @@ -0,0 +1,227 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Will <author@will.party> + * Copyright (c) 2022 Jeff Chiang <pobomp@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#include "tmux.h" + +/* + * OSC 8 hyperlinks, described at: + * + * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + * + * Each hyperlink and ID combination is assigned a number ("inner" in this + * file) which is stored in an extended grid cell and maps into a tree here. + * + * Each URI has one inner number and one external ID (which tmux uses to send + * the hyperlink to the terminal) and one internal ID (which is received from + * the sending application inside tmux). + * + * Anonymous hyperlinks are each unique and are not reused even if they have + * the same URI (terminals will not want to tie them together). + */ + +#define MAX_HYPERLINKS 5000 + +static long long 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/image-sixel.c b/image-sixel.c new file mode 100644 index 0000000..3396a22 --- /dev/null +++ b/image-sixel.c @@ -0,0 +1,600 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#include "tmux.h" + +#define SIXEL_WIDTH_LIMIT 10000 +#define SIXEL_HEIGHT_LIMIT 10000 + +struct sixel_line { + u_int x; + uint16_t *data; +}; + +struct sixel_image { + u_int x; + u_int y; + u_int xpixel; + u_int ypixel; + + u_int *colours; + u_int ncolours; + + u_int dx; + u_int dy; + u_int dc; + + struct sixel_line *lines; +}; + +static int +sixel_parse_expand_lines(struct sixel_image *si, u_int y) +{ + if (y <= si->y) + return (0); + if (y > SIXEL_HEIGHT_LIMIT) + return (1); + si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines); + si->y = y; + return (0); +} + +static int +sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x) +{ + if (x <= sl->x) + return (0); + if (x > SIXEL_WIDTH_LIMIT) + return (1); + if (x > si->x) + si->x = x; + sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data); + sl->x = si->x; + return (0); +} + +static u_int +sixel_get_pixel(struct sixel_image *si, u_int x, u_int y) +{ + struct sixel_line *sl; + + if (y >= si->y) + return (0); + sl = &si->lines[y]; + if (x >= sl->x) + return (0); + return (sl->data[x]); +} + +static int +sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c) +{ + struct sixel_line *sl; + + if (sixel_parse_expand_lines(si, y + 1) != 0) + return (1); + sl = &si->lines[y]; + if (sixel_parse_expand_line(si, sl, x + 1) != 0) + return (1); + sl->data[x] = c; + return (0); +} + +static int +sixel_parse_write(struct sixel_image *si, u_int ch) +{ + struct sixel_line *sl; + u_int i; + + if (sixel_parse_expand_lines(si, si->dy + 6) != 0) + return (1); + sl = &si->lines[si->dy]; + + for (i = 0; i < 6; i++) { + if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0) + return (1); + if (ch & (1 << i)) + sl->data[si->dx] = si->dc; + sl++; + } + return (0); +} + +static const char * +sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) +{ + const char *last; + char *endptr; + u_int x, y; + + last = cp; + while (last != end) { + if (*last != ';' && (*last < '0' || *last > '9')) + break; + last++; + } + strtoul(cp, &endptr, 10); + if (endptr == last || *endptr != ';') + return (last); + strtoul(endptr + 1, &endptr, 10); + if (endptr == last) + return (last); + if (*endptr != ';') { + log_debug("%s: missing ;", __func__); + return (NULL); + } + + x = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') { + log_debug("%s: missing ;", __func__); + return (NULL); + } + if (x > SIXEL_WIDTH_LIMIT) { + log_debug("%s: image is too wide", __func__); + return (NULL); + } + y = strtoul(endptr + 1, &endptr, 10); + if (endptr != last) { + log_debug("%s: extra ;", __func__); + return (NULL); + } + if (y > SIXEL_HEIGHT_LIMIT) { + log_debug("%s: image is too tall", __func__); + return (NULL); + } + + si->x = x; + sixel_parse_expand_lines(si, y); + + return (last); +} + +static const char * +sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) +{ + const char *last; + char *endptr; + u_int c, type, r, g, b; + + last = cp; + while (last != end) { + if (*last != ';' && (*last < '0' || *last > '9')) + break; + last++; + } + + c = strtoul(cp, &endptr, 10); + if (c > SIXEL_COLOUR_REGISTERS) { + log_debug("%s: too many colours", __func__); + return (NULL); + } + si->dc = c + 1; + if (endptr == last || *endptr != ';') + return (last); + + type = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') { + log_debug("%s: missing ;", __func__); + return (NULL); + } + r = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') { + log_debug("%s: missing ;", __func__); + return (NULL); + } + g = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') { + log_debug("%s: missing ;", __func__); + return (NULL); + } + b = strtoul(endptr + 1, &endptr, 10); + if (endptr != last) { + log_debug("%s: missing ;", __func__); + return (NULL); + } + + if (type != 1 && type != 2) { + log_debug("%s: invalid type %d", __func__, type); + return (NULL); + } + if (c + 1 > si->ncolours) { + si->colours = xrecallocarray(si->colours, si->ncolours, c + 1, + sizeof *si->colours); + si->ncolours = c + 1; + } + si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b; + return (last); +} + +static const char * +sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end) +{ + const char *last; + char tmp[32], ch; + u_int n = 0, i; + const char *errstr = NULL; + + last = cp; + while (last != end) { + if (*last < '0' || *last > '9') + break; + tmp[n++] = *last++; + if (n == (sizeof tmp) - 1) { + log_debug("%s: repeat not terminated", __func__); + return (NULL); + } + } + if (n == 0 || last == end) { + log_debug("%s: repeat not terminated", __func__); + return (NULL); + } + tmp[n] = '\0'; + + n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr); + if (n == 0 || errstr != NULL) { + log_debug("%s: repeat too wide", __func__); + return (NULL); + } + + ch = (*last++) - 0x3f; + for (i = 0; i < n; i++) { + if (sixel_parse_write(si, ch) != 0) { + log_debug("%s: width limit reached", __func__); + return (NULL); + } + si->dx++; + } + return (last); +} + +struct sixel_image * +sixel_parse(const char *buf, size_t len, u_int xpixel, u_int ypixel) +{ + struct sixel_image *si; + const char *cp = buf, *end = buf + len; + char ch; + + if (len == 0 || len == 1 || *cp++ != 'q') { + log_debug("%s: empty image", __func__); + return (NULL); + } + + si = xcalloc (1, sizeof *si); + si->xpixel = xpixel; + si->ypixel = ypixel; + + while (cp != end) { + ch = *cp++; + switch (ch) { + case '"': + cp = sixel_parse_attributes(si, cp, end); + if (cp == NULL) + goto bad; + break; + case '#': + cp = sixel_parse_colour(si, cp, end); + if (cp == NULL) + goto bad; + break; + case '!': + cp = sixel_parse_repeat(si, cp, end); + if (cp == NULL) + goto bad; + break; + case '-': + si->dx = 0; + si->dy += 6; + break; + case '$': + si->dx = 0; + break; + default: + if (ch < 0x20) + break; + if (ch < 0x3f || ch > 0x7e) + goto bad; + if (sixel_parse_write(si, ch - 0x3f) != 0) { + log_debug("%s: width limit reached", __func__); + goto bad; + } + si->dx++; + break; + } + } + + if (si->x == 0 || si->y == 0) + goto bad; + return (si); + +bad: + free(si); + return (NULL); +} + +void +sixel_free(struct sixel_image *si) +{ + u_int y; + + for (y = 0; y < si->y; y++) + free(si->lines[y].data); + free(si->lines); + + free(si->colours); + free(si); +} + +void +sixel_log(struct sixel_image *si) +{ + struct sixel_line *sl; + char s[SIXEL_WIDTH_LIMIT + 1]; + u_int i, x, y, cx, cy; + + sixel_size_in_cells(si, &cx, &cy); + log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy); + for (i = 0; i < si->ncolours; i++) + log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]); + for (y = 0; y < si->y; y++) { + sl = &si->lines[y]; + for (x = 0; x < si->x; x++) { + if (x >= sl->x) + s[x] = '_'; + else if (sl->data[x] != 0) + s[x] = '0' + (sl->data[x] - 1) % 10; + else + s[x] = '.'; + } + s[x] = '\0'; + log_debug("%s: %4u: %s", __func__, y, s); + } +} + +void +sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) +{ + if ((si->x % si->xpixel) == 0) + *x = (si->x / si->xpixel); + else + *x = 1 + (si->x / si->xpixel); + if ((si->y % si->ypixel) == 0) + *y = (si->y / si->ypixel); + else + *y = 1 + (si->y / si->ypixel); +} + +struct sixel_image * +sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, + u_int oy, u_int sx, u_int sy, int colours) +{ + struct sixel_image *new; + u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py; + u_int x, y, i; + + /* + * We want to get the section of the image at ox,oy in image cells and + * map it onto the same size in terminal cells, remembering that we + * can only draw vertical sections of six pixels. + */ + + sixel_size_in_cells(si, &cx, &cy); + if (ox >= cx) + return (NULL); + if (oy >= cy) + return (NULL); + if (ox + sx >= cx) + sx = cx - ox; + if (oy + sy >= cy) + sy = cy - oy; + + if (xpixel == 0) + xpixel = si->xpixel; + if (ypixel == 0) + ypixel = si->ypixel; + + pox = ox * si->xpixel; + poy = oy * si->ypixel; + psx = sx * si->xpixel; + psy = sy * si->ypixel; + + tsx = sx * xpixel; + tsy = ((sy * ypixel) / 6) * 6; + + new = xcalloc (1, sizeof *si); + new->xpixel = xpixel; + new->ypixel = ypixel; + + for (y = 0; y < tsy; y++) { + py = poy + ((double)y * psy / tsy); + for (x = 0; x < tsx; x++) { + px = pox + ((double)x * psx / tsx); + sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py)); + } + } + + if (colours) { + new->colours = xmalloc(si->ncolours * sizeof *new->colours); + for (i = 0; i < si->ncolours; i++) + new->colours[i] = si->colours[i]; + new->ncolours = si->ncolours; + } + return (new); +} + +static void +sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, + size_t slen) +{ + if (*used + slen >= *len + 1) { + (*len) *= 2; + *buf = xrealloc(*buf, *len); + } + memcpy(*buf + *used, s, slen); + (*used) += slen; +} + +static void +sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) +{ + char tmp[16]; + size_t tmplen; + + if (count == 1) + sixel_print_add(buf, len, used, &ch, 1); + else if (count == 2) { + sixel_print_add(buf, len, used, &ch, 1); + sixel_print_add(buf, len, used, &ch, 1); + } else if (count == 3) { + sixel_print_add(buf, len, used, &ch, 1); + sixel_print_add(buf, len, used, &ch, 1); + sixel_print_add(buf, len, used, &ch, 1); + } else if (count != 0) { + tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch); + sixel_print_add(buf, len, used, tmp, tmplen); + } +} + +char * +sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) +{ + char *buf, tmp[64], *contains, data, last = 0; + size_t len, used = 0, tmplen; + u_int *colours, ncolours, i, c, x, y, count; + struct sixel_line *sl; + + if (map != NULL) { + colours = map->colours; + ncolours = map->ncolours; + } else { + colours = si->colours; + ncolours = si->ncolours; + } + contains = xcalloc(1, ncolours); + + len = 8192; + buf = xmalloc(len); + + sixel_print_add(&buf, &len, &used, "\033Pq", 3); + + tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->y); + sixel_print_add(&buf, &len, &used, tmp, tmplen); + + for (i = 0; i < ncolours; i++) { + c = colours[i]; + tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u", + i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff); + sixel_print_add(&buf, &len, &used, tmp, tmplen); + } + + for (y = 0; y < si->y; y += 6) { + memset(contains, 0, ncolours); + for (x = 0; x < si->x; x++) { + for (i = 0; i < 6; i++) { + if (y + i >= si->y) + break; + sl = &si->lines[y + i]; + if (x < sl->x && sl->data[x] != 0) + contains[sl->data[x] - 1] = 1; + } + } + + for (c = 0; c < ncolours; c++) { + if (!contains[c]) + continue; + tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c); + sixel_print_add(&buf, &len, &used, tmp, tmplen); + + count = 0; + for (x = 0; x < si->x; x++) { + data = 0; + for (i = 0; i < 6; i++) { + if (y + i >= si->y) + break; + sl = &si->lines[y + i]; + if (x < sl->x && sl->data[x] == c + 1) + data |= (1 << i); + } + data += 0x3f; + if (data != last) { + sixel_print_repeat(&buf, &len, &used, + count, last); + last = data; + count = 1; + } else + count++; + } + sixel_print_repeat(&buf, &len, &used, count, data); + sixel_print_add(&buf, &len, &used, "$", 1); + } + + if (buf[used - 1] == '$') + used--; + if (buf[used - 1] != '-') + sixel_print_add(&buf, &len, &used, "-", 1); + } + if (buf[used - 1] == '$' || buf[used - 1] == '-') + used--; + + sixel_print_add(&buf, &len, &used, "\033\\", 2); + + buf[used] = '\0'; + if (size != NULL) + *size = used; + + free(contains); + return (buf); +} + +struct screen * +sixel_to_screen(struct sixel_image *si) +{ + struct screen *s; + struct screen_write_ctx ctx; + struct grid_cell gc; + u_int x, y, sx, sy; + + sixel_size_in_cells(si, &sx, &sy); + + s = xmalloc(sizeof *s); + screen_init(s, sx, sy, 0); + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM); + utf8_set(&gc.data, '~'); + + screen_write_start(&ctx, s); + if (sx == 1 || sy == 1) { + for (y = 0; y < sy; y++) { + for (x = 0; x < sx; x++) + grid_view_set_cell(s->grid, x, y, &gc); + } + } else { + screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL); + for (y = 1; y < sy - 1; y++) { + for (x = 1; x < sx - 1; x++) + grid_view_set_cell(s->grid, x, y, &gc); + } + } + screen_write_stop(&ctx); + return (s); +} @@ -0,0 +1,186 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#include "tmux.h" + +static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images); +static u_int all_images_count; + +static void +image_free(struct image *im) +{ + struct screen *s = im->s; + + TAILQ_REMOVE(&all_images, im, all_entry); + all_images_count--; + + TAILQ_REMOVE(&s->images, im, entry); + sixel_free(im->data); + free(im->fallback); + free(im); +} + +int +image_free_all(struct screen *s) +{ + struct image *im, *im1; + int redraw = !TAILQ_EMPTY(&s->images); + + TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) + image_free(im); + return (redraw); +} + +/* Create text placeholder for an image. */ +static void +image_fallback(char **ret, u_int sx, u_int sy) +{ + char *buf, *label; + u_int py, size, lsize; + + /* Allocate first line. */ + lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1; + if (sx < lsize - 3) + size = lsize - 1; + else + size = sx + 2; + + /* Remaining lines. Every placeholder line has \r\n at the end. */ + size += (sx + 2) * (sy - 1) + 1; + *ret = buf = xmalloc(size); + + /* Render first line. */ + if (sx < lsize - 3) { + memcpy(buf, label, lsize); + buf += lsize - 1; + } else { + memcpy(buf, label, lsize - 3); + buf += lsize - 3; + memset(buf, '+', sx - lsize + 3); + buf += sx - lsize + 3; + snprintf(buf, 3, "\r\n"); + buf += 2; + } + + /* Remaining lines. */ + for (py = 1; py < sy; py++) { + memset(buf, '+', sx); + buf += sx; + snprintf(buf, 3, "\r\n"); + buf += 2; + } + + free(label); +} + +struct image* +image_store(struct screen *s, struct sixel_image *si) +{ + struct image *im; + + im = xcalloc(1, sizeof *im); + im->s = s; + im->data = si; + + im->px = s->cx; + im->py = s->cy; + sixel_size_in_cells(si, &im->sx, &im->sy); + + image_fallback(&im->fallback, im->sx, im->sy); + + TAILQ_INSERT_TAIL(&s->images, im, entry); + + TAILQ_INSERT_TAIL(&all_images, im, all_entry); + if (++all_images_count == 10/*XXX*/) + image_free(TAILQ_FIRST(&all_images)); + + return (im); +} + +int +image_check_line(struct screen *s, u_int py, u_int ny) +{ + struct image *im, *im1; + int redraw = 0; + + TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { + if (py + ny > im->py && py < im->py + im->sy) { + image_free(im); + redraw = 1; + } + } + return (redraw); +} + +int +image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) +{ + struct image *im, *im1; + int redraw = 0; + + TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { + if (py + ny <= im->py || py >= im->py + im->sy) + continue; + if (px + nx <= im->px || px >= im->px + im->sx) + continue; + image_free(im); + redraw = 1; + } + return (redraw); +} + +int +image_scroll_up(struct screen *s, u_int lines) +{ + struct image *im, *im1; + int redraw = 0; + u_int sx, sy; + struct sixel_image *new; + + TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { + if (im->py >= lines) { + im->py -= lines; + redraw = 1; + continue; + } + if (im->py + im->sy <= lines) { + image_free(im); + redraw = 1; + continue; + } + sx = im->sx; + sy = (im->py + im->sy) - lines; + + new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1); + sixel_free(im->data); + im->data = new; + + im->py = 0; + sixel_size_in_cells(im->data, &im->sx, &im->sy); + + free(im->fallback); + image_fallback(&im->fallback, im->sx, im->sy); + redraw = 1; + } + return (redraw); +} diff --git a/input-keys.c b/input-keys.c index ebf6133..0451b96 100644 --- a/input-keys.c +++ b/input-keys.c @@ -306,6 +306,20 @@ static struct input_key_entry input_key_defaults[] = { }, { .key = KEYC_DC|KEYC_BUILD_MODIFIERS, .data = "\033[3;_~" + }, + + /* Tab and modifiers. */ + { .key = '\011'|KEYC_CTRL, + .data = "\011" + }, + { .key = '\011'|KEYC_CTRL|KEYC_EXTENDED, + .data = "\033[9;5u" + }, + { .key = '\011'|KEYC_CTRL|KEYC_SHIFT, + .data = "\033[Z" + }, + { .key = '\011'|KEYC_CTRL|KEYC_SHIFT|KEYC_EXTENDED, + .data = "\033[1;5Z" } }; static const key_code input_key_modifiers[] = { @@ -416,7 +430,7 @@ input_key_write(const char *from, struct bufferevent *bev, const char *data, int input_key(struct screen *s, struct bufferevent *bev, key_code key) { - struct input_key_entry *ike; + struct input_key_entry *ike = NULL; key_code justkey, newkey, outkey, modifiers; struct utf8_data ud; char tmp[64], modifier; @@ -468,15 +482,23 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key) key &= ~KEYC_KEYPAD; if (~s->mode & MODE_KCURSOR) key &= ~KEYC_CURSOR; - ike = input_key_get(key); + if (s->mode & MODE_KEXTENDED) + ike = input_key_get(key|KEYC_EXTENDED); + if (ike == NULL) + ike = input_key_get(key); if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META)) ike = input_key_get(key & ~KEYC_META); if (ike == NULL && (key & KEYC_CURSOR)) ike = input_key_get(key & ~KEYC_CURSOR); if (ike == NULL && (key & KEYC_KEYPAD)) ike = input_key_get(key & ~KEYC_KEYPAD); + if (ike == NULL && (key & KEYC_EXTENDED)) + ike = input_key_get(key & ~KEYC_EXTENDED); if (ike != NULL) { log_debug("found key 0x%llx: \"%s\"", key, ike->data); + if ((key == KEYC_PASTE_START || key == KEYC_PASTE_END) && + (~s->mode & MODE_BRACKETPASTE)) + return (0); if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META)) input_key_write(__func__, bev, "\033", 1); input_key_write(__func__, bev, ike->data, strlen(ike->data)); @@ -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 *); @@ -143,6 +144,7 @@ static void input_osc_104(struct input_ctx *, const char *); static void input_osc_110(struct input_ctx *, const char *); static void input_osc_111(struct input_ctx *, const char *); static void input_osc_112(struct input_ctx *, const char *); +static void input_osc_133(struct input_ctx *, const char *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -167,6 +169,7 @@ static void input_csi_dispatch_rm(struct input_ctx *); static void input_csi_dispatch_rm_private(struct input_ctx *); static void input_csi_dispatch_sm(struct input_ctx *); static void input_csi_dispatch_sm_private(struct input_ctx *); +static void input_csi_dispatch_sm_graphics(struct input_ctx *); static void input_csi_dispatch_winops(struct input_ctx *); static void input_csi_dispatch_sgr_256(struct input_ctx *, int, u_int *); static void input_csi_dispatch_sgr_rgb(struct input_ctx *, int, u_int *); @@ -201,7 +204,7 @@ enum input_esc_type { INPUT_ESC_SCSG0_ON, INPUT_ESC_SCSG1_OFF, INPUT_ESC_SCSG1_ON, - INPUT_ESC_ST, + INPUT_ESC_ST }; /* Escape command table. */ @@ -257,11 +260,12 @@ enum input_csi_type { INPUT_CSI_SGR, INPUT_CSI_SM, INPUT_CSI_SM_PRIVATE, + INPUT_CSI_SM_GRAPHICS, INPUT_CSI_SU, INPUT_CSI_TBC, INPUT_CSI_VPA, INPUT_CSI_WINOPS, - INPUT_CSI_XDA, + INPUT_CSI_XDA }; /* Control (CSI) command table. */ @@ -281,6 +285,7 @@ static const struct input_table_entry input_csi_table[] = { { 'M', "", INPUT_CSI_DL }, { 'P', "", INPUT_CSI_DCH }, { 'S', "", INPUT_CSI_SU }, + { 'S', "?", INPUT_CSI_SM_GRAPHICS }, { 'T', "", INPUT_CSI_SD }, { 'X', "", INPUT_CSI_ECH }, { 'Z', "", INPUT_CSI_CBT }, @@ -304,7 +309,7 @@ static const struct input_table_entry input_csi_table[] = { { 'r', "", INPUT_CSI_DECSTBM }, { 's', "", INPUT_CSI_SCP }, { 't', "", INPUT_CSI_WINOPS }, - { 'u', "", INPUT_CSI_RCP }, + { 'u', "", INPUT_CSI_RCP } }; /* Input transition. */ @@ -970,6 +975,10 @@ input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) window_update_activity(wp->window); wp->flags |= PANE_CHANGED; + /* Flag new input while in a mode. */ + if (!TAILQ_EMPTY(&wp->modes)) + wp->flags |= PANE_UNSEENCHANGES; + /* NULL wp if there is a mode set as don't want to update the tty. */ if (TAILQ_EMPTY(&wp->modes)) screen_write_start_pane(sctx, wp, &wp->base); @@ -1085,6 +1094,7 @@ input_reply(struct input_ctx *ictx, const char *fmt, ...) xvasprintf(&reply, fmt, ap); va_end(ap); + log_debug("%s: %s", __func__, reply); bufferevent_write(bev, reply, strlen(reply)); free(reply); } @@ -1344,8 +1354,8 @@ input_csi_dispatch(struct input_ctx *ictx) if (ictx->flags & INPUT_DISCARD) return (0); - log_debug("%s: '%c' \"%s\" \"%s\"", - __func__, ictx->ch, ictx->interm_buf, ictx->param_buf); + log_debug("%s: '%c' \"%s\" \"%s\"", __func__, ictx->ch, + ictx->interm_buf, ictx->param_buf); if (input_split(ictx) != 0) return (0); @@ -1436,7 +1446,11 @@ input_csi_dispatch(struct input_ctx *ictx) case -1: break; case 0: +#ifdef ENABLE_SIXEL + input_reply(ictx, "\033[?1;2;4c"); +#else input_reply(ictx, "\033[?1;2c"); +#endif break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1588,6 +1602,9 @@ input_csi_dispatch(struct input_ctx *ictx) case INPUT_CSI_SM_PRIVATE: input_csi_dispatch_sm_private(ictx); break; + case INPUT_CSI_SM_GRAPHICS: + input_csi_dispatch_sm_graphics(ictx); + break; case INPUT_CSI_SU: n = input_get(ictx, 0, 1, 1); if (n != -1) @@ -1754,7 +1771,6 @@ static void input_csi_dispatch_sm_private(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; - struct window_pane *wp = ictx->wp; struct grid_cell *gc = &ictx->cell.cell; u_int i; @@ -1796,17 +1812,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) screen_write_mode_set(sctx, MODE_MOUSE_ALL); break; case 1004: - if (sctx->s->mode & MODE_FOCUSON) - break; 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 - bufferevent_write(wp->event, "\033[O", 3); break; case 1005: screen_write_mode_set(sctx, MODE_MOUSE_UTF8); @@ -1831,6 +1837,26 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) } } +/* Handle CSI graphics SM. */ +static void +input_csi_dispatch_sm_graphics(struct input_ctx *ictx) +{ +#ifdef ENABLE_SIXEL + int n, m, o; + + if (ictx->param_list_len > 3) + return; + n = input_get(ictx, 0, 0, 0); + m = input_get(ictx, 1, 0, 0); + o = input_get(ictx, 2, 0, 0); + + if (n == 1 && (m == 1 || m == 2 || m == 4)) + input_reply(ictx, "\033[?%d;0;%uS", n, SIXEL_COLOUR_REGISTERS); + else + input_reply(ictx, "\033[?%d;3;%dS", n, o); +#endif +} + /* Handle CSI window operations. */ static void input_csi_dispatch_winops(struct input_ctx *ictx) @@ -1838,9 +1864,13 @@ input_csi_dispatch_winops(struct input_ctx *ictx) struct screen_write_ctx *sctx = &ictx->ctx; struct screen *s = sctx->s; struct window_pane *wp = ictx->wp; + struct window *w = NULL; u_int x = screen_size_x(s), y = screen_size_y(s); int n, m; + if (wp != NULL) + w = wp->window; + m = 0; while ((n = input_get(ictx, m, 0, -1)) != -1) { switch (n) { @@ -1851,8 +1881,6 @@ input_csi_dispatch_winops(struct input_ctx *ictx) case 7: case 11: case 13: - case 14: - case 19: case 20: case 21: case 24: @@ -1870,6 +1898,30 @@ input_csi_dispatch_winops(struct input_ctx *ictx) if (input_get(ictx, m, 0, -1) == -1) return; break; + case 14: + if (w == NULL) + break; + input_reply(ictx, "\033[4;%u;%ut", y * w->ypixel, + x * w->xpixel); + break; + case 15: + if (w == NULL) + break; + input_reply(ictx, "\033[5;%u;%ut", y * w->ypixel, + x * w->xpixel); + break; + case 16: + if (w == NULL) + break; + input_reply(ictx, "\033[6;%u;%ut", w->ypixel, + w->xpixel); + break; + case 18: + input_reply(ictx, "\033[8;%u;%ut", y, x); + break; + case 19: + input_reply(ictx, "\033[9;%u;%ut", y, x); + break; case 22: m++; switch (input_get(ictx, m, 0, -1)) { @@ -1892,14 +1944,11 @@ input_csi_dispatch_winops(struct input_ctx *ictx) if (wp == NULL) break; notify_pane("pane-title-changed", wp); - server_redraw_window_borders(wp->window); - server_status_window(wp->window); + server_redraw_window_borders(w); + server_status_window(w); break; } break; - case 18: - input_reply(ictx, "\033[8;%u;%ut", x, y); - break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -2070,7 +2119,7 @@ static void input_csi_dispatch_sgr(struct input_ctx *ictx) { struct grid_cell *gc = &ictx->cell.cell; - u_int i; + u_int i, link; int n; if (ictx->param_list_len == 0) { @@ -2102,7 +2151,9 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) switch (n) { case 0: + link = gc->link; memcpy(gc, &grid_default_cell, sizeof *gc); + gc->link = link; break; case 1: gc->attr |= GRID_ATTR_BRIGHT; @@ -2188,7 +2239,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) gc->attr &= ~GRID_ATTR_OVERLINE; break; case 59: - gc->us = 0; + gc->us = 8; break; case 90: case 91: @@ -2246,17 +2297,38 @@ input_dcs_dispatch(struct input_ctx *ictx) size_t len = ictx->input_len; const char prefix[] = "tmux;"; const u_int prefixlen = (sizeof prefix) - 1; + long long allow_passthrough = 0; +#ifdef ENABLE_SIXEL + struct window *w; + struct sixel_image *si; +#endif if (wp == NULL) return (0); - if (ictx->flags & INPUT_DISCARD) + + if (ictx->flags & INPUT_DISCARD) { + log_debug("%s: %zu bytes (discard)", __func__, len); return (0); - if (!options_get_number(ictx->wp->options, "allow-passthrough")) + } + +#ifdef ENABLE_SIXEL + w = wp->window; + if (buf[0] == 'q') { + si = sixel_parse(buf, len, w->xpixel, w->ypixel); + if (si != NULL) + screen_write_sixelimage(sctx, si, ictx->cell.cell.bg); + } +#endif + + allow_passthrough = options_get_number(wp->options, "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); + } return (0); } @@ -2292,6 +2364,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++; @@ -2316,6 +2390,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; @@ -2340,6 +2417,9 @@ input_exit_osc(struct input_ctx *ictx) case 112: input_osc_112(ictx, p); break; + case 133: + input_osc_133(ictx, p); + break; default: log_debug("%s: unknown '%u'", __func__, option); break; @@ -2456,47 +2536,6 @@ input_top_bit_set(struct input_ctx *ictx) return (0); } -/* Parse colour from OSC. */ -static int -input_osc_parse_colour(const char *p) -{ - double c, m, y, k = 0; - u_int r, g, b; - size_t len = strlen(p); - int colour = -1; - char *copy; - - if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || - (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || - sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) - colour = colour_join_rgb(r, g, b); - else if ((len == 18 && - sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || - (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) - colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); - else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || - sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && - c >= 0 && c <= 1 && m >= 0 && m <= 1 && - y >= 0 && y <= 1 && k >= 0 && k <= 1) { - colour = colour_join_rgb( - (1 - c) * (1 - k) * 255, - (1 - m) * (1 - k) * 255, - (1 - y) * (1 - k) * 255); - } else { - while (len != 0 && *p == ' ') { - p++; - len--; - } - while (len != 0 && p[len - 1] == ' ') - len--; - copy = xstrndup(p, len); - colour = colour_byname(copy); - free(copy); - } - log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); - return (colour); -} - /* Reply to a colour request. */ static void input_osc_colour_reply(struct input_ctx *ictx, u_int n, int c) @@ -2545,7 +2584,7 @@ input_osc_4(struct input_ctx *ictx, const char *p) input_osc_colour_reply(ictx, 4, c); continue; } - if ((c = input_osc_parse_colour(s)) == -1) { + if ((c = colour_parseX11(s)) == -1) { s = next; continue; } @@ -2560,6 +2599,88 @@ 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); +} + +/* + * Get a client with a foreground for the pane. There isn't much to choose + * between them so just use the first. + */ +static int +input_get_fg_client(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + if (loop->tty.fg == -1) + continue; + return (loop->tty.fg); + } + return (-1); +} + +/* Get a client with a background for the pane. */ +static int +input_get_bg_client(struct window_pane *wp) +{ + struct window *w = wp->window; + struct client *loop; + + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->flags & CLIENT_UNATTACHEDFLAGS) + continue; + if (loop->session == NULL || !session_has(loop->session, w)) + continue; + if (loop->tty.bg == -1) + continue; + return (loop->tty.bg); + } + return (-1); +} + /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void input_osc_10(struct input_ctx *ictx, const char *p) @@ -2569,14 +2690,18 @@ input_osc_10(struct input_ctx *ictx, const char *p) int c; if (strcmp(p, "?") == 0) { - if (wp != NULL) { - tty_default_colours(&defaults, wp); - input_osc_colour_reply(ictx, 10, defaults.fg); - } + if (wp == NULL) + return; + tty_default_colours(&defaults, wp); + if (COLOUR_DEFAULT(defaults.fg)) + c = input_get_fg_client(wp); + else + c = defaults.fg; + input_osc_colour_reply(ictx, 10, c); return; } - if ((c = input_osc_parse_colour(p)) == -1) { + if ((c = colour_parseX11(p)) == -1) { log_debug("bad OSC 10: %s", p); return; } @@ -2613,14 +2738,18 @@ input_osc_11(struct input_ctx *ictx, const char *p) int c; if (strcmp(p, "?") == 0) { - if (wp != NULL) { - tty_default_colours(&defaults, wp); - input_osc_colour_reply(ictx, 11, defaults.bg); - } + if (wp == NULL) + return; + tty_default_colours(&defaults, wp); + if (COLOUR_DEFAULT(defaults.bg)) + c = input_get_bg_client(wp); + else + c = defaults.bg; + input_osc_colour_reply(ictx, 11, c); return; } - if ((c = input_osc_parse_colour(p)) == -1) { + if ((c = colour_parseX11(p)) == -1) { log_debug("bad OSC 11: %s", p); return; } @@ -2665,7 +2794,7 @@ input_osc_12(struct input_ctx *ictx, const char *p) return; } - if ((c = input_osc_parse_colour(p)) == -1) { + if ((c = colour_parseX11(p)) == -1) { log_debug("bad OSC 12: %s", p); return; } @@ -2680,6 +2809,27 @@ input_osc_112(struct input_ctx *ictx, const char *p) screen_set_cursor_colour(ictx->ctx.s, -1); } +/* Handle the OSC 133 sequence. */ +static void +input_osc_133(struct input_ctx *ictx, const char *p) +{ + struct grid *gd = ictx->ctx.s->grid; + u_int line = ictx->ctx.s->cy + gd->hsize; + struct grid_line *gl; + + if (line > gd->hsize + gd->sy - 1) + return; + gl = grid_get_line(gd, line); + + switch (*p) { + case 'A': + gl->flags |= GRID_LINE_START_PROMPT; + break; + case 'C': + gl->flags |= GRID_LINE_START_OUTPUT; + break; + } +} /* Handle the OSC 52 sequence for setting the clipboard. */ static void @@ -2693,6 +2843,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; @@ -2707,6 +2860,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); @@ -2728,7 +2887,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); @@ -2777,9 +2936,11 @@ input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len, const char *end) { char *out = NULL; - size_t outlen = 0; + int outlen = 0; if (buf != NULL && len != 0) { + if (len >= ((size_t)INT_MAX * 3 / 4) - 1) + return; outlen = 4 * ((len + 2) / 3) + 1; out = xmalloc(outlen); if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) { diff --git a/key-bindings.c b/key-bindings.c index 9517196..eaf05f0 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 }", @@ -374,7 +377,7 @@ key_bindings_init(void) "bind -N 'Move to the previously active pane' \\; { last-pane }", "bind -N 'Choose a paste buffer from a list' = { choose-buffer -Z }", "bind -N 'List key bindings' ? { list-keys -N }", - "bind -N 'Choose a client from a list' D { choose-client -Z }", + "bind -N 'Choose and detach a client from a list' D { choose-client -Z }", "bind -N 'Spread panes out evenly' E { select-layout -E }", "bind -N 'Switch to the last client' L { switch-client -l }", "bind -N 'Clear the marked pane' M { select-pane -M }", @@ -463,9 +466,11 @@ key_bindings_init(void) /* Mouse button 3 down on status left. */ "bind -n MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", + "bind -n M-MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", /* Mouse button 3 down on status line. */ "bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", + "bind -n M-MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", /* Mouse button 3 down on pane. */ "bind -n MouseDown3Pane { if -Ft= '#{||:#{mouse_any_flag},#{&&:#{pane_in_mode},#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}}}' { select-pane -t=; send -M } { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " } }", @@ -602,6 +607,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 +619,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/key-string.c b/key-string.c index 0ca9130..699d460 100644 --- a/key-string.c +++ b/key-string.c @@ -460,6 +460,10 @@ out: strlcat(out, "I", sizeof out); if (saved & KEYC_BUILD_MODIFIERS) strlcat(out, "B", sizeof out); + if (saved & KEYC_EXTENDED) + strlcat(out, "E", sizeof out); + if (saved & KEYC_SENT) + strlcat(out, "S", sizeof out); strlcat(out, "]", sizeof out); } return (out); diff --git a/layout-custom.c b/layout-custom.c index 932b30e..d7be5b1 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -162,8 +162,10 @@ layout_parse(struct window *w, const char *layout, char **cause) u_short csum; /* Check validity. */ - if (sscanf(layout, "%hx,", &csum) != 1) + if (sscanf(layout, "%hx,", &csum) != 1) { + *cause = xstrdup("invalid layout"); return (-1); + } layout += 5; if (csum != layout_checksum(layout)) { *cause = xstrdup("invalid layout"); @@ -27,6 +27,11 @@ struct menu_data { struct cmdq_item *item; int flags; + struct grid_cell style; + struct grid_cell border_style; + struct grid_cell selected_style; + enum box_lines border_lines; + struct cmd_find_state fs; struct screen s; @@ -64,6 +69,8 @@ menu_add_item(struct menu *menu, const struct menu_item *item, line = (item == NULL || item->name == NULL || *item->name == '\0'); if (line && menu->count == 0) return; + if (line && menu->items[menu->count - 1].name == NULL) + return; menu->items = xreallocarray(menu->items, menu->count + 1, sizeof *menu->items); @@ -160,11 +167,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); } @@ -190,13 +202,17 @@ menu_draw_cb(struct client *c, void *data, struct menu *menu = md->menu; struct screen_write_ctx ctx; u_int i, px = md->px, py = md->py; - struct grid_cell gc; - - style_apply(&gc, c->session->curw->window->options, "mode-style", NULL); screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); - screen_write_menu(&ctx, menu, md->choice, &gc); + + if (md->border_lines != BOX_LINES_NONE) { + screen_write_box(&ctx, menu->width + 4, menu->count + 2, + md->border_lines, &md->border_style, menu->title); + } + + screen_write_menu(&ctx, menu, md->choice, md->border_lines, + &md->style, &md->border_style, &md->selected_style); screen_write_stop(&ctx); for (i = 0; i < screen_size_y(&md->s); i++) { @@ -318,27 +334,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 == '-')) - md->choice++; - if (md->choice == count) - md->choice = -1; + 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 'G': case KEYC_NPAGE: - if (md->choice > count - 6) + if (md->choice > count - 6) { md->choice = count - 1; - else - md->choice += 5; - while (md->choice != -1 && (name == NULL || *name == '-')) + 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++; + name = menu->items[md->choice].name; + } + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case 'G': + 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 */ @@ -384,14 +437,36 @@ chosen: return (1); } +static void +menu_set_style(struct client *c, struct grid_cell *gc, const char *style, + const char *option) +{ + struct style sytmp; + struct options *o = c->session->curw->window->options; + + memcpy(gc, &grid_default_cell, sizeof *gc); + style_apply(gc, o, option, NULL); + if (style != NULL) { + style_set(&sytmp, &grid_default_cell); + if (style_parse(&sytmp, gc, style) == 0) { + gc->fg = sytmp.gc.fg; + gc->bg = sytmp.gc.bg; + } + } + gc->attr = 0; +} + struct menu_data * -menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, - u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, +menu_prepare(struct menu *menu, int flags, int starting_choice, + struct cmdq_item *item, u_int px, u_int py, struct client *c, + enum box_lines lines, const char *style, const char *selected_style, + const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; - u_int i; + int choice; const char *name; + struct options *o = c->session->curw->window->options; if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) return (NULL); @@ -400,9 +475,18 @@ menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, if (py + menu->count + 2 > c->tty.sy) py = c->tty.sy - menu->count - 2; + if (lines == BOX_LINES_DEFAULT) + lines = options_get_number(o, "menu-border-lines"); + md = xcalloc(1, sizeof *md); md->item = item; md->flags = flags; + md->border_lines = lines; + + menu_set_style(c, &md->style, style, "menu-style"); + menu_set_style(c, &md->selected_style, selected_style, + "menu-selected-style"); + menu_set_style(c, &md->border_style, border_style, "menu-border-style"); if (fs != NULL) cmd_find_copy_state(&md->fs, fs); @@ -415,18 +499,38 @@ menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, md->py = py; md->menu = menu; + md->choice = -1; + if (md->flags & MENU_NOMOUSE) { - for (i = 0; i < menu->count; i++) { - name = menu->items[i].name; - if (name != NULL && *name != '-') - break; + if (starting_choice >= (int)menu->count) { + starting_choice = menu->count - 1; + choice = starting_choice + 1; + for (;;) { + name = menu->items[choice - 1].name; + if (name != NULL && *name != '-') { + md->choice = choice - 1; + break; + } + if (--choice == 0) + choice = menu->count; + if (choice == starting_choice + 1) + break; + } + } else if (starting_choice >= 0) { + choice = starting_choice; + for (;;) { + name = menu->items[choice].name; + if (name != NULL && *name != '-') { + md->choice = choice; + break; + } + if (++choice == (int)menu->count) + choice = 0; + if (choice == starting_choice) + break; + } } - if (i != menu->count) - md->choice = i; - else - md->choice = -1; - } else - md->choice = -1; + } md->cb = cb; md->data = data; @@ -434,13 +538,16 @@ menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, } int -menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, - u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, +menu_display(struct menu *menu, int flags, int starting_choice, + struct cmdq_item *item, u_int px, u_int py, struct client *c, + enum box_lines lines, const char *style, const char *selected_style, + const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; - md = menu_prepare(menu, flags, item, px, py, c, fs, cb, data); + md = menu_prepare(menu, flags, starting_choice, item, px, py, c, lines, + style, selected_style, border_style, fs, cb, data); if (md == NULL) return (-1); server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, diff --git a/mode-tree.c b/mode-tree.c index c007e27..cebd4f0 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -497,7 +497,7 @@ mode_tree_build(struct mode_tree_data *mtd) mode_tree_clear_lines(mtd); mode_tree_build_lines(mtd, &mtd->children, 0); - if (tag == UINT64_MAX) + if (mtd->line_list != NULL && tag == UINT64_MAX) tag = mtd->line_list[mtd->current].item->tag; mode_tree_set_current(mtd, tag); @@ -962,8 +962,8 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, x -= (menu->width + 4) / 2; else x = 0; - if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback, - mtm) != 0) + if (menu_display(menu, 0, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL, + NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) menu_free(menu); } @@ -32,6 +32,7 @@ struct notify_entry { struct session *session; struct window *window; int pane; + const char *pbname; }; static struct cmdq_item * @@ -149,6 +150,10 @@ 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); + if (strcmp(ne->name, "paste-buffer-deleted") == 0) + control_notify_paste_buffer_deleted(ne->pbname); notify_insert_hook(item, ne); @@ -164,6 +169,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 +177,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; @@ -186,7 +193,8 @@ notify_add(const char *name, struct cmd_find_state *fs, struct client *c, ne->client = c; ne->session = s; ne->window = w; - ne->pane = (wp != NULL ? wp->id : -1); + ne->pane = (wp != NULL ? (int)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); @@ -232,7 +240,7 @@ notify_hook(struct cmdq_item *item, const char *name) ne.client = cmdq_get_client(item); ne.session = target->s; ne.window = target->w; - ne.pane = (target->wp != NULL ? target->wp->id : -1); + ne.pane = (target->wp != NULL ? (int)target->wp->id : -1); ne.formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS); format_add(ne.formats, "hook", "%s", name); @@ -248,7 +256,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 +268,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 +277,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 +286,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 +295,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 +304,20 @@ 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, int deleted) +{ + struct cmd_find_state fs; + + cmd_find_clear_state(&fs, 0); + if (deleted) { + notify_add("paste-buffer-deleted", &fs, NULL, NULL, NULL, NULL, + pbname); + } else { + notify_add("paste-buffer-changed", &fs, NULL, NULL, NULL, NULL, + pbname); + } } diff --git a/options-table.c b/options-table.c index 17be7ec..f030f2d 100644 --- a/options-table.c +++ b/options-table.c @@ -41,6 +41,9 @@ static const char *options_table_clock_mode_style_list[] = { static const char *options_table_status_list[] = { "off", "on", "2", "3", "4", "5", NULL }; +static const char *options_table_message_line_list[] = { + "0", "1", "2", "3", "4", NULL +}; static const char *options_table_status_keys_list[] = { "emacs", "vi", NULL }; @@ -81,12 +84,18 @@ static const char *options_table_window_size_list[] = { static const char *options_table_remain_on_exit_list[] = { "off", "on", "failed", NULL }; +static const char *options_table_destroy_unattached_list[] = { + "off", "on", "keep-last", "keep-group", NULL +}; static const char *options_table_detach_on_destroy_list[] = { - "off", "on", "no-detached", NULL + "off", "on", "no-detached", "previous", "next", NULL }; 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 \ @@ -320,6 +329,42 @@ const struct options_table_entry options_table[] = { "Empty does not write a history file." }, + { .name = "menu-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .flags = OPTIONS_TABLE_IS_STYLE, + .default_str = "default", + .separator = ",", + .text = "Default style of menu." + }, + + { .name = "menu-selected-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .flags = OPTIONS_TABLE_IS_STYLE, + .default_str = "bg=yellow,fg=black", + .separator = ",", + .text = "Default style of selected menu item." + }, + + { .name = "menu-border-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default style of menu borders." + }, + + { .name = "menu-border-lines", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_WINDOW, + .choices = options_table_popup_border_lines_list, + .default_num = BOX_LINES_SINGLE, + .text = "Type of characters used to draw menu border lines. Some of " + "these are only supported on terminals with UTF-8 support." + }, + { .name = "message-limit", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SERVER, @@ -362,7 +407,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." @@ -440,11 +486,12 @@ const struct options_table_entry options_table[] = { }, { .name = "destroy-unattached", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, + .choices = options_table_destroy_unattached_list, .default_num = 0, .text = "Whether to destroy sessions when they have no attached " - "clients." + "clients, or keep the last session whether in the group." }, { .name = "detach-on-destroy", @@ -523,7 +570,7 @@ const struct options_table_entry options_table[] = { { .name = "lock-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "lock -np", + .default_str = TMUX_LOCK_CMD, .text = "Shell command to run to lock a client." }, @@ -537,13 +584,21 @@ const struct options_table_entry options_table[] = { "'mode-keys' is set to 'vi'." }, + { .name = "message-line", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_SESSION, + .choices = options_table_message_line_list, + .default_num = 0, + .text = "Position (line) of messages and the command prompt." + }, + { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", - .text = "Style of the command prompt." + .text = "Style of messages and the command prompt." }, { .name = "mouse", @@ -802,11 +857,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", @@ -916,8 +974,8 @@ const struct options_table_entry options_table[] = { { .name = "mode-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, + .default_str = "bg=yellow,fg=black", .separator = ",", .text = "Style of indicators and highlighting in modes." }, @@ -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) @@ -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, 1); + 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, 0); } /* Rename a paste buffer. */ @@ -228,11 +240,8 @@ paste_rename(const char *oldname, const char *newname, char **cause) } pb_new = paste_get_name(newname); - if (pb_new != NULL) { - if (cause != NULL) - xasprintf(cause, "buffer %s already exists", newname); - return (-1); - } + if (pb_new != NULL) + paste_free(pb_new); RB_REMOVE(paste_name_tree, &paste_by_name, pb); @@ -245,6 +254,9 @@ paste_rename(const char *oldname, const char *newname, char **cause) RB_INSERT(paste_name_tree, &paste_by_name, pb); + notify_paste_buffer(oldname, 1); + notify_paste_buffer(newname, 0); + return (0); } @@ -293,6 +305,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, 0); + return (0); } @@ -303,6 +317,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, 0); } /* Convert start of buffer into a nice string. */ @@ -252,6 +252,7 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults, palette); } + screen_free(&s); if (pd->md != NULL) { c->overlay_check = NULL; c->overlay_data = NULL; @@ -573,8 +574,8 @@ menu: x = m->x - (pd->menu->width + 4) / 2; else x = 0; - pd->md = menu_prepare(pd->menu, 0, NULL, x, m->y, c, NULL, - popup_menu_done, pd); + pd->md = menu_prepare(pd->menu, 0, 0, NULL, x, m->y, c, + BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, popup_menu_done, pd); c->flags |= CLIENT_REDRAWOVERLAY; out: @@ -635,7 +636,7 @@ int popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px, u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd, int argc, char **argv, const char *cwd, const char *title, struct client *c, - struct session *s, const char* style, const char* border_style, + struct session *s, const char *style, const char *border_style, popup_close_cb cb, void *arg) { struct popup_data *pd; @@ -786,6 +787,8 @@ popup_editor(struct client *c, const char *buf, size_t len, if (fd == -1) return (-1); f = fdopen(fd, "w"); + if (f == NULL) + return (-1); if (fwrite(buf, len, 1, f) != 1) { fclose(f); return (-1); @@ -93,8 +93,9 @@ proc_event_cb(__unused int fd, short events, void *arg) log_debug("peer %p message %d", peer, imsg.hdr.type); if (peer_check_version(peer, &imsg) != 0) { - if (imsg.fd != -1) - close(imsg.fd); + fd = imsg_get_fd(&imsg); + if (fd != -1) + close(fd); imsg_free(&imsg); break; } @@ -193,18 +194,13 @@ proc_start(const char *name) log_debug("%s started (%ld): version %s, socket %s, protocol %d", name, (long)getpid(), getversion(), socket_path, PROTOCOL_VERSION); log_debug("on %s %s %s", u.sysname, u.release, u.version); - log_debug("using libevent %s (%s)" + log_debug("using libevent %s %s", event_get_version(), event_get_method()); #ifdef HAVE_UTF8PROC - "; utf8proc %s" + log_debug("using utf8proc %s", utf8proc_version()); #endif #ifdef NCURSES_VERSION - "; ncurses " NCURSES_VERSION + log_debug("using ncurses %s %06u", NCURSES_VERSION, NCURSES_VERSION_PATCH); #endif - , event_get_version(), event_get_method() -#ifdef HAVE_UTF8PROC - , utf8proc_version() -#endif - ); tp = xcalloc(1, sizeof *tp); tp->name = xstrdup(name); @@ -24,7 +24,7 @@ #include "tmux.h" static void -regsub_copy(char **buf, size_t *len, const char *text, size_t start, size_t end) +regsub_copy(char **buf, ssize_t *len, const char *text, size_t start, size_t end) { size_t add = end - start; @@ -34,7 +34,7 @@ regsub_copy(char **buf, size_t *len, const char *text, size_t start, size_t end) } static void -regsub_expand(char **buf, size_t *len, const char *with, const char *text, +regsub_expand(char **buf, ssize_t *len, const char *with, const char *text, regmatch_t *m, u_int n) { const char *cp; diff --git a/screen-redraw.c b/screen-redraw.c index c4906ab..ce79b41 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); } @@ -856,4 +856,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) tty_default_colours(&defaults, wp); tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette); } + +#ifdef ENABLE_SIXEL + tty_draw_images(c, wp, s); +#endif } diff --git a/screen-write.c b/screen-write.c index 6b6a750..6892d04 100644 --- a/screen-write.c +++ b/screen-write.c @@ -30,11 +30,10 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int, static void screen_write_collect_scroll(struct screen_write_ctx *, u_int); static void screen_write_collect_flush(struct screen_write_ctx *, int, const char *); - static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); -static const struct grid_cell *screen_write_combine(struct screen_write_ctx *, - const struct utf8_data *, u_int *); +static int screen_write_combine(struct screen_write_ctx *, + const struct grid_cell *); struct screen_write_citem { u_int x; @@ -132,6 +131,12 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) { struct window_pane *wp = ttyctx->arg; + if (ttyctx->allow_invisible_panes) { + if (session_has(c->session, wp->window)) + return (1); + return (0); + } + if (c->session->curw->window != wp->window) return (0); if (wp->layout_cell == NULL) @@ -320,7 +325,9 @@ screen_write_reset(struct screen_write_ctx *ctx) screen_reset_tabs(s); screen_write_scrollregion(ctx, 0, screen_size_y(s) - 1); - s->mode = MODE_CURSOR | MODE_WRAP; + s->mode = MODE_CURSOR|MODE_WRAP; + if (options_get_number(global_options, "extended-keys") == 2) + s->mode |= MODE_KEXTENDED; screen_write_clearscreen(ctx, 8); screen_write_set_cursor(ctx, 0, 0); @@ -584,9 +591,46 @@ screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, } } +/* Select character set for drawing border lines. */ +static void +screen_write_box_border_set(enum box_lines lines, int cell_type, + struct grid_cell *gc) +{ + switch (lines) { + case BOX_LINES_NONE: + break; + case BOX_LINES_DOUBLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); + break; + case BOX_LINES_HEAVY: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); + break; + case BOX_LINES_ROUNDED: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type)); + break; + case BOX_LINES_SIMPLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); + break; + case BOX_LINES_PADDED: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_set(&gc->data, PADDED_BORDERS[cell_type]); + break; + case BOX_LINES_SINGLE: + case BOX_LINES_DEFAULT: + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[cell_type]); + break; + } +} + /* Draw a horizontal line on screen. */ void -screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right) +screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right, + enum box_lines lines, const struct grid_cell *border_gc) { struct screen *s = ctx->s; struct grid_cell gc; @@ -595,13 +639,27 @@ screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right) cx = s->cx; cy = s->cy; - memcpy(&gc, &grid_default_cell, sizeof gc); + if (border_gc != NULL) + memcpy(&gc, border_gc, sizeof gc); + else + memcpy(&gc, &grid_default_cell, sizeof gc); gc.attr |= GRID_ATTR_CHARSET; - screen_write_putc(ctx, &gc, left ? 't' : 'q'); + if (left) + screen_write_box_border_set(lines, CELL_LEFTJOIN, &gc); + else + screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_cell(ctx, &gc); + + screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); for (i = 1; i < nx - 1; i++) - screen_write_putc(ctx, &gc, 'q'); - screen_write_putc(ctx, &gc, right ? 'u' : 'q'); + screen_write_cell(ctx, &gc); + + if (right) + screen_write_box_border_set(lines, CELL_RIGHTJOIN, &gc); + else + screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); + screen_write_cell(ctx, &gc); screen_write_set_cursor(ctx, cx, cy); } @@ -633,84 +691,53 @@ screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) /* Draw a menu on screen. */ void -screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, - int choice, const struct grid_cell *choice_gc) +screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice, + enum box_lines lines, const struct grid_cell *menu_gc, + const struct grid_cell *border_gc, const struct grid_cell *choice_gc) { struct screen *s = ctx->s; struct grid_cell default_gc; const struct grid_cell *gc = &default_gc; - u_int cx, cy, i, j; + u_int cx, cy, i, j, width = menu->width; const char *name; cx = s->cx; cy = s->cy; - memcpy(&default_gc, &grid_default_cell, sizeof default_gc); + memcpy(&default_gc, menu_gc, sizeof default_gc); - screen_write_box(ctx, menu->width + 4, menu->count + 2, - BOX_LINES_DEFAULT, &default_gc, menu->title); + screen_write_box(ctx, menu->width + 4, menu->count + 2, lines, + border_gc, menu->title); for (i = 0; i < menu->count; i++) { name = menu->items[i].name; if (name == NULL) { screen_write_cursormove(ctx, cx, cy + 1 + i, 0); - screen_write_hline(ctx, menu->width + 4, 1, 1); - } else { - if (choice >= 0 && i == (u_int)choice && *name != '-') - gc = choice_gc; - screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); - for (j = 0; j < menu->width; j++) - screen_write_putc(ctx, gc, ' '); - screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); - if (*name == '-') { - name++; - default_gc.attr |= GRID_ATTR_DIM; - format_draw(ctx, gc, menu->width, name, NULL, - 0); - default_gc.attr &= ~GRID_ATTR_DIM; - } else - format_draw(ctx, gc, menu->width, name, NULL, - gc == choice_gc); - gc = &default_gc; + screen_write_hline(ctx, width + 4, 1, 1, lines, + border_gc); + continue; } - } - screen_write_set_cursor(ctx, cx, cy); -} + if (choice >= 0 && i == (u_int)choice && *name != '-') + gc = choice_gc; -static void -screen_write_box_border_set(enum box_lines box_lines, int cell_type, - struct grid_cell *gc) -{ - switch (box_lines) { - case BOX_LINES_NONE: - break; - case BOX_LINES_DOUBLE: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); - break; - case BOX_LINES_HEAVY: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); - break; - case BOX_LINES_ROUNDED: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type)); - break; - case BOX_LINES_SIMPLE: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); - break; - case BOX_LINES_PADDED: - gc->attr &= ~GRID_ATTR_CHARSET; - utf8_set(&gc->data, PADDED_BORDERS[cell_type]); - break; - case BOX_LINES_SINGLE: - case BOX_LINES_DEFAULT: - gc->attr |= GRID_ATTR_CHARSET; - utf8_set(&gc->data, CELL_BORDERS[cell_type]); - break; + screen_write_cursormove(ctx, cx + 1, cy + 1 + i, 0); + for (j = 0; j < width + 2; j++) + screen_write_putc(ctx, gc, ' '); + + screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); + if (*name == '-') { + default_gc.attr |= GRID_ATTR_DIM; + format_draw(ctx, gc, width, name + 1, NULL, 0); + default_gc.attr &= ~GRID_ATTR_DIM; + continue; + } + + format_draw(ctx, gc, width, name, NULL, 0); + gc = &default_gc; } + + screen_write_set_cursor(ctx, cx, cy); } /* Draw a box on screen. */ @@ -984,6 +1011,11 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) memcpy(&gc, &grid_default_cell, sizeof gc); utf8_set(&gc.data, 'E'); +#ifdef ENABLE_SIXEL + if (image_free_all(s) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + for (yy = 0; yy < screen_size_y(s); yy++) { for (xx = 0; xx < screen_size_x(s); xx++) grid_view_set_cell(s->grid, xx, yy, &gc); @@ -1018,6 +1050,11 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; @@ -1046,6 +1083,11 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; @@ -1074,6 +1116,11 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; @@ -1092,9 +1139,18 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) struct grid *gd = s->grid; struct tty_ctx ttyctx; +#ifdef ENABLE_SIXEL + u_int sy = screen_size_y(s); +#endif + if (ny == 0) ny = 1; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + if (s->cy < s->rupper || s->cy > s->rlower) { if (ny > screen_size_y(s) - s->cy) ny = screen_size_y(s) - s->cy; @@ -1138,13 +1194,19 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) struct screen *s = ctx->s; struct grid *gd = s->grid; struct tty_ctx ttyctx; + u_int sy = screen_size_y(s); if (ny == 0) ny = 1; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + if (s->cy < s->rupper || s->cy > s->rlower) { - if (ny > screen_size_y(s) - s->cy) - ny = screen_size_y(s) - s->cy; + if (ny > sy - s->cy) + ny = sy - s->cy; if (ny == 0) return; @@ -1190,6 +1252,11 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) return; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); screen_write_collect_clear(ctx, s->cy, 1); @@ -1219,6 +1286,11 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg))) return; +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); before = screen_write_collect_trim(ctx, s->cy, s->cx, sx - s->cx, NULL); @@ -1246,6 +1318,11 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) return; } +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + if (s->cx > sx - 1) grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); else @@ -1294,6 +1371,11 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; if (s->cy == s->rupper) { +#ifdef ENABLE_SIXEL + if (image_free_all(s) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); @@ -1336,13 +1418,17 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) struct screen *s = ctx->s; struct grid *gd = s->grid; struct grid_line *gl; +#ifdef ENABLE_SIXEL + int redraw = 0; +#endif + u_int rupper = s->rupper, rlower = s->rlower; gl = grid_get_line(gd, gd->hsize + s->cy); if (wrapped) gl->flags |= GRID_LINE_WRAPPED; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, - s->rupper, s->rlower); + rupper, rlower); if (bg != ctx->bg) { screen_write_collect_flush(ctx, 1, __func__); @@ -1350,6 +1436,14 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) } if (s->cy == s->rlower) { +#ifdef ENABLE_SIXEL + if (rlower == screen_size_y(s) - 1) + redraw = image_scroll_up(s, 1); + else + redraw = image_check_line(s, rupper, rlower - rupper); + if (redraw && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); screen_write_collect_scroll(ctx, bg); ctx->scrolled++; @@ -1375,6 +1469,11 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) ctx->bg = bg; } +#ifdef ENABLE_SIXEL + if (image_scroll_up(s, lines) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + for (i = 0; i < lines; i++) { grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); screen_write_collect_scroll(ctx, bg); @@ -1399,6 +1498,11 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) else if (lines > s->rlower - s->rupper + 1) lines = s->rlower - s->rupper + 1; +#ifdef ENABLE_SIXEL + if (image_free_all(s) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + for (i = 0; i < lines; i++) grid_view_scroll_region_down(gd, s->rupper, s->rlower, bg); @@ -1423,6 +1527,11 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); +#ifdef ENABLE_SIXEL + if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; @@ -1452,6 +1561,11 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s); +#ifdef ENABLE_SIXEL + if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; @@ -1475,6 +1589,11 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); +#ifdef ENABLE_SIXEL + if (image_free_all(s) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; @@ -1506,7 +1625,8 @@ screen_write_fullredraw(struct screen_write_ctx *ctx) screen_write_collect_flush(ctx, 0, __func__); screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.redraw_cb(&ttyctx); + if (ttyctx.redraw_cb != NULL) + ttyctx.redraw_cb(&ttyctx); } /* Trim collected items. */ @@ -1742,6 +1862,11 @@ screen_write_collect_end(struct screen_write_ctx *ctx) } } +#ifdef ENABLE_SIXEL + if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; +#endif + grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x, ci->used); screen_write_set_cursor(ctx, s->cx + ci->used, -1); @@ -1813,54 +1938,21 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct screen *s = ctx->s; struct grid *gd = s->grid; const struct utf8_data *ud = &gc->data; - const struct utf8_data zwj = { "\342\200\215", 0, 3, 0 }; struct grid_line *gl; struct grid_cell_entry *gce; struct grid_cell tmp_gc, now_gc; struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); - u_int width = gc->data.width, xx, last, cx, cy; + u_int width = ud->width, xx, not_wrap; int selected, skip = 1; /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) return; - /* - * If this is a zero width joiner, set the flag so the next character - * will be treated as zero width and appended. Note that we assume a - * ZWJ will not change the width - the width of the first character is - * used. - */ - if (ud->size == 3 && memcmp(ud->data, "\342\200\215", 3) == 0) { - log_debug("zero width joiner at %u,%u", s->cx, s->cy); - ctx->flags |= SCREEN_WRITE_ZWJ; + /* Get the previous cell to check for combining. */ + if (screen_write_combine(ctx, gc) != 0) return; - } - - /* - * If the width is zero, combine onto the previous character. We always - * combine with the cell to the left of the cursor position. In theory, - * the application could have moved the cursor somewhere else, but if - * they are silly enough to do that, who cares? - */ - if (ctx->flags & SCREEN_WRITE_ZWJ) { - screen_write_collect_flush(ctx, 0, __func__); - screen_write_combine(ctx, &zwj, &xx); - } - if (width == 0 || (ctx->flags & SCREEN_WRITE_ZWJ)) { - ctx->flags &= ~SCREEN_WRITE_ZWJ; - screen_write_collect_flush(ctx, 0, __func__); - if ((gc = screen_write_combine(ctx, ud, &xx)) != NULL) { - cx = s->cx; cy = s->cy; - screen_write_set_cursor(ctx, xx, s->cy); - screen_write_initctx(ctx, &ttyctx, 0); - ttyctx.cell = gc; - tty_write(tty_cmd_cell, &ttyctx); - s->cx = cx; s->cy = cy; - } - return; - } /* Flush any existing scrolling. */ screen_write_collect_flush(ctx, 1, __func__); @@ -1952,11 +2044,11 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) * Move the cursor. If not wrapping, stick at the last character and * replace it. */ - last = !(s->mode & MODE_WRAP); - if (s->cx <= sx - last - width) + not_wrap = !(s->mode & MODE_WRAP); + if (s->cx <= sx - not_wrap - width) screen_write_set_cursor(ctx, s->cx + width, -1); else - screen_write_set_cursor(ctx, sx - last, -1); + screen_write_set_cursor(ctx, sx - not_wrap, -1); /* Create space for character in insert mode. */ if (s->mode & MODE_INSERT) { @@ -1976,49 +2068,102 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } } -/* Combine a UTF-8 zero-width character onto the previous. */ -static const struct grid_cell * -screen_write_combine(struct screen_write_ctx *ctx, const struct utf8_data *ud, - u_int *xx) +/* Combine a UTF-8 zero-width character onto the previous if necessary. */ +static int +screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) { struct screen *s = ctx->s; struct grid *gd = s->grid; - static struct grid_cell gc; - u_int n; - - /* Can't combine if at 0. */ - if (s->cx == 0) - return (NULL); + const struct utf8_data *ud = &gc->data; + u_int n, cx = s->cx, cy = s->cy; + struct grid_cell last; + struct tty_ctx ttyctx; + int force_wide = 0, zero_width = 0; - /* Empty data is out. */ - if (ud->size == 0) - fatalx("UTF-8 data empty"); + /* + * Is this character which makes no sense without being combined? If + * this is true then flag it here and discard the character (return 1) + * if we cannot combine it. + */ + if (utf8_is_zwj(ud)) + zero_width = 1; + else if (utf8_is_vs(ud)) + zero_width = force_wide = 1; + else if (ud->width == 0) + zero_width = 1; + + /* Cannot combine empty character or at left. */ + if (ud->size < 2 || cx == 0) + return (zero_width); + log_debug("%s: character %.*s at %u,%u (width %u)", __func__, + (int)ud->size, ud->data, cx, cy, ud->width); + + /* Find the cell to combine with. */ + n = 1; + grid_view_get_cell(gd, cx - n, cy, &last); + if (cx != 1 && (last.flags & GRID_FLAG_PADDING)) { + n = 2; + grid_view_get_cell(gd, cx - n, cy, &last); + } + if (n != last.data.width || (last.flags & GRID_FLAG_PADDING)) + return (zero_width); - /* Retrieve the previous cell. */ - for (n = 1; n <= s->cx; n++) { - grid_view_get_cell(gd, s->cx - n, s->cy, &gc); - if (~gc.flags & GRID_FLAG_PADDING) - break; + /* + * Check if we need to combine characters. This could be zero width + * (set above), a modifier character (with an existing Unicode + * character) or a previous ZWJ. + */ + if (!zero_width) { + if (utf8_is_modifier(ud)) { + if (last.data.size < 2) + return (0); + force_wide = 1; + } else if (!utf8_has_zwj(&last.data)) + return (0); } - if (n > s->cx) - return (NULL); - *xx = s->cx - n; - /* Check there is enough space. */ - if (gc.data.size + ud->size > sizeof gc.data.data) - return (NULL); + /* Check if this combined character would be too long. */ + if (last.data.size + ud->size > sizeof last.data.data) + return (0); - log_debug("%s: %.*s onto %.*s at %u,%u", __func__, (int)ud->size, - ud->data, (int)gc.data.size, gc.data.data, *xx, s->cy); + /* Combining; flush any pending output. */ + screen_write_collect_flush(ctx, 0, __func__); + + log_debug("%s: %.*s -> %.*s at %u,%u (offset %u, width %u)", __func__, + (int)ud->size, ud->data, (int)last.data.size, last.data.data, + cx - n, cy, n, last.data.width); /* Append the data. */ - memcpy(gc.data.data + gc.data.size, ud->data, ud->size); - gc.data.size += ud->size; + memcpy(last.data.data + last.data.size, ud->data, ud->size); + last.data.size += ud->size; + + /* Force the width to 2 for modifiers and variation selector. */ + if (last.data.width == 1 && force_wide) { + last.data.width = 2; + n = 2; + cx++; + } else + force_wide = 0; /* Set the new cell. */ - grid_view_set_cell(gd, *xx, s->cy, &gc); + grid_view_set_cell(gd, cx - n, cy, &last); + if (force_wide) + grid_view_set_padding(gd, cx, cy); + + /* + * Redraw the combined cell. If forcing the cell to width 2, reset the + * cached cursor position in the tty, since we don't really know + * whether the terminal thought the character was width 1 or width 2 + * and what it is going to do now. + */ + screen_write_set_cursor(ctx, cx - n, cy); + screen_write_initctx(ctx, &ttyctx, 0); + ttyctx.cell = &last; + ttyctx.num = force_wide; /* reset cached cursor position */ + tty_write(tty_cmd_cell, &ttyctx); + screen_write_set_cursor(ctx, cx, cy); - return (&gc); + return (1); } /* @@ -2085,12 +2230,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); @@ -2098,17 +2245,74 @@ 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); } +#ifdef ENABLE_SIXEL +/* Write a SIXEL image. */ +void +screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, + u_int bg) +{ + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines; + struct sixel_image *new; + + sixel_size_in_cells(si, &x, &y); + if (x > screen_size_x(s) || y > screen_size_y(s)) { + if (x > screen_size_x(s) - cx) + sx = screen_size_x(s) - cx; + else + sx = x; + if (y > screen_size_y(s) - 1) + sy = screen_size_y(s) - 1; + else + sy = y; + new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1); + sixel_free(si); + si = new; + sixel_size_in_cells(si, &x, &y); + } + + sy = screen_size_y(s) - cy; + if (sy < y) { + lines = y - sy + 1; + if (image_scroll_up(s, lines) && ctx->wp != NULL) + ctx->wp->flags |= PANE_REDRAW; + for (i = 0; i < lines; i++) { + grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1, + bg); + screen_write_collect_scroll(ctx, bg); + } + ctx->scrolled += lines; + if (lines > cy) + screen_write_cursormove(ctx, -1, 0, 0); + else + screen_write_cursormove(ctx, -1, cy - lines, 0); + } + screen_write_collect_flush(ctx, 0, __func__); + + screen_write_initctx(ctx, &ttyctx, 0); + ttyctx.ptr = image_store(s, si); + + tty_write(tty_cmd_sixelimage, &ttyctx); + + screen_write_cursormove(ctx, 0, cy + y, 0); +} +#endif + /* Turn alternate screen on. */ void screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, @@ -2124,7 +2328,8 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, screen_alternate_on(ctx->s, gc, cursor); screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.redraw_cb(&ttyctx); + if (ttyctx.redraw_cb != NULL) + ttyctx.redraw_cb(&ttyctx); } /* Turn alternate screen off. */ @@ -2142,5 +2347,6 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, screen_alternate_off(ctx->s, gc, cursor); screen_write_initctx(ctx, &ttyctx, 1); - ttyctx.redraw_cb(&ttyctx); + if (ttyctx.redraw_cb != NULL) + ttyctx.redraw_cb(&ttyctx); } @@ -82,13 +82,19 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->cstyle = SCREEN_CURSOR_DEFAULT; s->default_cstyle = SCREEN_CURSOR_DEFAULT; + s->mode = MODE_CURSOR; s->default_mode = 0; s->ccolour = -1; s->default_ccolour = -1; s->tabs = NULL; s->sel = NULL; +#ifdef ENABLE_SIXEL + TAILQ_INIT(&s->images); +#endif + s->write_list = NULL; + s->hyperlinks = NULL; screen_reinit(s); } @@ -118,6 +124,22 @@ screen_reinit(struct screen *s) screen_clear_selection(s); screen_free_titles(s); + +#ifdef ENABLE_SIXEL + image_free_all(s); +#endif + + 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. */ @@ -136,7 +158,13 @@ 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); + +#ifdef ENABLE_SIXEL + image_free_all(s); +#endif } /* Reset tabs to default, eight spaces apart. */ @@ -280,8 +308,12 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, if (sy != screen_size_y(s)) screen_resize_y(s, sy, eat_empty, &cy); - if (reflow) + if (reflow) { +#ifdef ENABLE_SIXEL + image_free_all(s); +#endif screen_reflow(s, sx, &cx, &cy, cursor); + } if (cy >= s->grid->hsize) { s->cx = cx; @@ -611,7 +643,7 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) * before copying back. */ if (s->saved_grid != NULL) - screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 1); + screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 0); /* * Restore the cursor position and cell. This happens even if not @@ -685,9 +717,9 @@ screen_mode_to_string(int mode) if (mode & MODE_CURSOR_VERY_VISIBLE) strlcat(tmp, "CURSOR_VERY_VISIBLE,", sizeof tmp); if (mode & MODE_MOUSE_UTF8) - strlcat(tmp, "UTF8,", sizeof tmp); + strlcat(tmp, "MOUSE_UTF8,", sizeof tmp); if (mode & MODE_MOUSE_SGR) - strlcat(tmp, "SGR,", sizeof tmp); + strlcat(tmp, "MOUSE_SGR,", sizeof tmp); if (mode & MODE_BRACKETPASTE) strlcat(tmp, "BRACKETPASTE,", sizeof tmp); if (mode & MODE_FOCUSON) diff --git a/server-client.c b/server-client.c index 8144bcd..a749450 100644 --- a/server-client.c +++ b/server-client.c @@ -42,6 +42,7 @@ static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); static void server_client_set_path(struct client *); static void server_client_reset_state(struct client *); +static int server_client_is_bracket_pasting(struct client *, key_code); static int server_client_assume_paste(struct session *); static void server_client_update_latest(struct client *); @@ -559,9 +560,9 @@ static key_code server_client_check_mouse(struct client *c, struct key_event *event) { struct mouse_event *m = &event->m; - struct session *s = c->session; - struct winlink *wl; - struct window_pane *wp; + struct session *s = c->session, *fs; + struct winlink *fwl; + struct window_pane *wp, *fwp; u_int x, y, b, sx, sy, px, py; int ignore = 0; key_code key; @@ -667,6 +668,7 @@ have_event: /* Save the session. */ m->s = s->id; m->w = -1; + m->wp = -1; m->ignore = ignore; /* Is this on the status line? */ @@ -683,18 +685,42 @@ have_event: case STYLE_RANGE_NONE: return (KEYC_UNKNOWN); case STYLE_RANGE_LEFT: + log_debug("mouse range: left"); where = STATUS_LEFT; break; case STYLE_RANGE_RIGHT: + log_debug("mouse range: right"); where = STATUS_RIGHT; break; + case STYLE_RANGE_PANE: + fwp = window_pane_find_by_id(sr->argument); + if (fwp == NULL) + return (KEYC_UNKNOWN); + m->wp = sr->argument; + + log_debug("mouse range: pane %%%u", m->wp); + where = STATUS; + break; case STYLE_RANGE_WINDOW: - wl = winlink_find_by_index(&s->windows, + fwl = winlink_find_by_index(&s->windows, sr->argument); - if (wl == NULL) + if (fwl == NULL) return (KEYC_UNKNOWN); - m->w = wl->window->id; + m->w = fwl->window->id; + log_debug("mouse range: window @%u", m->w); + where = STATUS; + break; + case STYLE_RANGE_SESSION: + fs = session_find_by_id(sr->argument); + if (fs == NULL) + return (KEYC_UNKNOWN); + m->s = sr->argument; + + log_debug("mouse range: session $%u", m->s); + where = STATUS; + break; + case STYLE_RANGE_USER: where = STATUS; break; } @@ -1754,6 +1780,25 @@ out: return (key); } +/* Is this a bracket paste key? */ +static int +server_client_is_bracket_pasting(struct client *c, key_code key) +{ + if (key == KEYC_PASTE_START) { + c->flags |= CLIENT_BRACKETPASTING; + log_debug("%s: bracket paste on", c->name); + return (1); + } + + if (key == KEYC_PASTE_END) { + c->flags &= ~CLIENT_BRACKETPASTING; + log_debug("%s: bracket paste off", c->name); + return (1); + } + + return !!(c->flags & CLIENT_BRACKETPASTING); +} + /* Is this fast enough to probably be a paste? */ static int server_client_assume_paste(struct session *s) @@ -1818,7 +1863,7 @@ server_client_key_callback(struct cmdq_item *item, void *data) struct key_binding *bd; int xtimeout, flags; struct cmd_find_state fs; - key_code key0; + key_code key0, prefix, prefix2; /* Check the client is good to accept input. */ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) @@ -1862,8 +1907,14 @@ server_client_key_callback(struct cmdq_item *item, void *data) if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse")) goto forward_key; + /* Forward if bracket pasting. */ + if (server_client_is_bracket_pasting(c, key)) + goto forward_key; + /* Treat everything as a regular key when pasting is detected. */ - if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) + if (!KEYC_IS_MOUSE(key) && + (~key & KEYC_SENT) && + server_client_assume_paste(s)) goto forward_key; /* @@ -1884,9 +1935,11 @@ table_changed: * The prefix always takes precedence and forces a switch to the prefix * table, unless we are already there. */ + prefix = (key_code)options_get_number(s->options, "prefix"); + prefix2 = (key_code)options_get_number(s->options, "prefix2"); key0 = (key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)); - if ((key0 == (key_code)options_get_number(s->options, "prefix") || - key0 == (key_code)options_get_number(s->options, "prefix2")) && + if ((key0 == (prefix & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) || + key0 == (prefix2 & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS))) && strcmp(table->name, "prefix") != 0) { server_client_set_key_table(c, "prefix"); server_status_client(c); @@ -2218,7 +2271,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) @@ -2714,6 +2768,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) break; server_client_update_latest(c); tty_resize(&c->tty); + tty_repeat_requests(&c->tty); recalculate_sizes(); if (c->overlay_resize == NULL) server_client_clear_overlay(c); @@ -2788,8 +2843,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); } @@ -2940,14 +2998,14 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) case MSG_IDENTIFY_STDIN: if (datalen != 0) fatalx("bad MSG_IDENTIFY_STDIN size"); - c->fd = imsg->fd; - log_debug("client %p IDENTIFY_STDIN %d", c, imsg->fd); + c->fd = imsg_get_fd(imsg); + log_debug("client %p IDENTIFY_STDIN %d", c, c->fd); break; case MSG_IDENTIFY_STDOUT: if (datalen != 0) fatalx("bad MSG_IDENTIFY_STDOUT size"); - c->out_fd = imsg->fd; - log_debug("client %p IDENTIFY_STDOUT %d", c, imsg->fd); + c->out_fd = imsg_get_fd(imsg); + log_debug("client %p IDENTIFY_STDOUT %d", c, c->out_fd); break; case MSG_IDENTIFY_ENVIRON: if (datalen == 0 || data[datalen - 1] != '\0') @@ -3210,3 +3268,69 @@ server_client_remove_pane(struct window_pane *wp) } } } + +/* Print to a client. */ +void +server_client_print(struct client *c, int parse, struct evbuffer *evb) +{ + void *data = EVBUFFER_DATA(evb); + size_t size = EVBUFFER_LENGTH(evb); + struct window_pane *wp; + struct window_mode_entry *wme; + char *sanitized, *msg, *line; + + if (!parse) { + utf8_stravisx(&msg, data, size, + VIS_OCTAL|VIS_CSTYLE|VIS_NOSLASH); + log_debug("%s: %s", __func__, msg); + } else { + msg = EVBUFFER_DATA(evb); + if (msg[size - 1] != '\0') + evbuffer_add(evb, "", 1); + } + + if (c == NULL) + goto out; + + if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + if (~c->flags & CLIENT_UTF8) { + sanitized = utf8_sanitize(msg); + if (c->flags & CLIENT_CONTROL) + control_write(c, "%s", sanitized); + else + file_print(c, "%s\n", sanitized); + free(sanitized); + } else { + if (c->flags & CLIENT_CONTROL) + control_write(c, "%s", msg); + else + file_print(c, "%s\n", msg); + } + goto out; + } + + wp = server_client_get_pane(c); + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->mode != &window_view_mode) + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); + if (parse) { + do { + line = evbuffer_readln(evb, NULL, EVBUFFER_EOL_LF); + if (line != NULL) { + window_copy_add(wp, 1, "%s", line); + free(line); + } + } while (line != NULL); + + size = EVBUFFER_LENGTH(evb); + if (size != 0) { + line = EVBUFFER_DATA(evb); + window_copy_add(wp, 1, "%.*s", (int)size, line); + } + } else + window_copy_add(wp, 0, "%s", msg); + +out: + if (!parse) + free(msg); +} diff --git a/server-fn.c b/server-fn.c index 2a79f3e..2f64932 100644 --- a/server-fn.c +++ b/server-fn.c @@ -27,8 +27,7 @@ #include "tmux.h" -static struct session *server_next_session(struct session *); -static void server_destroy_session_group(struct session *); +static void server_destroy_session_group(struct session *); void server_redraw_client(struct client *c) @@ -207,8 +206,8 @@ server_kill_window(struct window *w, int renumber) if (session_detach(s, wl)) { server_destroy_session_group(s); break; - } else - server_redraw_session_group(s); + } + server_redraw_session_group(s); } if (renumber) @@ -385,9 +384,10 @@ server_destroy_session_group(struct session *s) struct session_group *sg; struct session *s1; - if ((sg = session_group_contains(s)) == NULL) + if ((sg = session_group_contains(s)) == NULL) { server_destroy_session(s); - else { + session_destroy(s, 1, __func__); + } else { TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { server_destroy_session(s); session_destroy(s, 1, __func__); @@ -396,52 +396,55 @@ server_destroy_session_group(struct session *s) } static struct session * -server_next_session(struct session *s) +server_find_session(struct session *s, + int (*f)(struct session *, struct session *)) { struct session *s_loop, *s_out = NULL; RB_FOREACH(s_loop, sessions, &sessions) { - if (s_loop == s) - continue; - if (s_out == NULL || - timercmp(&s_loop->activity_time, &s_out->activity_time, <)) + if (s_loop != s && (s_out == NULL || f(s_loop, s_out))) s_out = s_loop; } return (s_out); } -static struct session * -server_next_detached_session(struct session *s) +static int +server_newer_session(struct session *s_loop, struct session *s_out) { - struct session *s_loop, *s_out = NULL; + return (timercmp(&s_loop->activity_time, &s_out->activity_time, <)); +} - RB_FOREACH(s_loop, sessions, &sessions) { - if (s_loop == s || s_loop->attached) - continue; - if (s_out == NULL || - timercmp(&s_loop->activity_time, &s_out->activity_time, <)) - s_out = s_loop; - } - return (s_out); +static int +server_newer_detached_session(struct session *s_loop, struct session *s_out) +{ + if (s_loop->attached) + return (0); + return (server_newer_session(s_loop, s_out)); } void server_destroy_session(struct session *s) { struct client *c; - struct session *s_new; + struct session *s_new = NULL; int detach_on_destroy; detach_on_destroy = options_get_number(s->options, "detach-on-destroy"); if (detach_on_destroy == 0) - s_new = server_next_session(s); + s_new = server_find_session(s, server_newer_session); else if (detach_on_destroy == 2) - s_new = server_next_detached_session(s); - else + s_new = server_find_session(s, server_newer_detached_session); + else if (detach_on_destroy == 3) + s_new = session_previous_session(s); + else if (detach_on_destroy == 4) + s_new = session_next_session(s); + if (s_new == s) s_new = NULL; TAILQ_FOREACH(c, &clients, entry) { if (c->session != s) continue; + c->session = NULL; + c->last_session = NULL; server_client_set_session(c, s_new); if (s_new == NULL) c->flags |= CLIENT_EXIT; @@ -452,7 +455,8 @@ server_destroy_session(struct session *s) void server_check_unattached(void) { - struct session *s; + struct session *s; + struct session_group *sg; /* * If any sessions are no longer attached and have destroy-unattached @@ -461,8 +465,23 @@ server_check_unattached(void) RB_FOREACH(s, sessions, &sessions) { if (s->attached != 0) continue; - if (options_get_number (s->options, "destroy-unattached")) - session_destroy(s, 1, __func__); + switch (options_get_number(s->options, "destroy-unattached")) { + case 0: /* off */ + continue; + case 1: /* on */ + break; + case 2: /* keep-last */ + sg = session_group_contains(s); + if (sg == NULL || session_group_count(sg) <= 1) + continue; + break; + case 3: /* keep-group */ + sg = session_group_contains(s); + if (sg != NULL && session_group_count(sg) == 1) + continue; + break; + } + session_destroy(s, 1, __func__); } } @@ -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 @@ -263,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) { @@ -365,11 +365,9 @@ session_detach(struct session *s, struct winlink *wl) session_group_synchronize_from(s); - if (RB_EMPTY(&s->windows)) { - session_destroy(s, 1, __func__); + if (RB_EMPTY(&s->windows)) return (1); - } - return (0); + return (0); } /* Return if session has window. */ @@ -687,8 +685,10 @@ session_group_synchronize1(struct session *target, struct session *s) TAILQ_INIT(&s->lastw); TAILQ_FOREACH(wl, &old_lastw, sentry) { wl2 = winlink_find_by_index(&s->windows, wl->idx); - if (wl2 != NULL) + if (wl2 != NULL) { TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry); + wl2->flags |= WINLINK_VISITED; + } } /* Then free the old winlinks list. */ @@ -708,7 +708,7 @@ session_renumber_windows(struct session *s) struct winlink *wl, *wl1, *wl_new; struct winlinks old_wins; struct winlink_stack old_lastw; - int new_idx, new_curw_idx; + int new_idx, new_curw_idx, marked_idx = -1; /* Save and replace old window list. */ memcpy(&old_wins, &s->windows, sizeof old_wins); @@ -725,6 +725,8 @@ session_renumber_windows(struct session *s) winlink_set_window(wl_new, wl->window); wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS; + if (wl == marked_pane.wl) + marked_idx = wl_new->idx; if (wl == s->curw) new_curw_idx = wl_new->idx; @@ -735,12 +737,20 @@ session_renumber_windows(struct session *s) memcpy(&old_lastw, &s->lastw, sizeof old_lastw); TAILQ_INIT(&s->lastw); TAILQ_FOREACH(wl, &old_lastw, sentry) { + wl->flags &= ~WINLINK_VISITED; wl_new = winlink_find_by_window(&s->windows, wl->window); - if (wl_new != NULL) + if (wl_new != NULL) { TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry); + wl_new->flags |= WINLINK_VISITED; + } } /* Set the current window. */ + if (marked_idx != -1) { + marked_pane.wl = winlink_find_by_index(&s->windows, marked_idx); + if (marked_pane.wl == NULL) + server_clear_marked(); + } s->curw = winlink_find_by_index(&s->windows, new_curw_idx); /* Free the old winlinks (reducing window references too). */ @@ -113,6 +113,7 @@ spawn_window(struct spawn_context *sc, char **cause) window_pane_resize(sc->wp0, w->sx, w->sy); layout_init(w, sc->wp0); + w->active = NULL; window_set_active_pane(w, sc->wp0, 0); } @@ -380,8 +381,20 @@ spawn_pane(struct spawn_context *sc, char **cause) } /* In the parent process, everything is done now. */ - if (new_wp->pid != 0) + if (new_wp->pid != 0) { +#if defined(HAVE_SYSTEMD) && defined(ENABLE_CGROUPS) + /* + * Move the child process into a new cgroup for systemd-oomd + * isolation. + */ + if (systemd_move_pid_to_new_cgroup(new_wp->pid, cause) < 0) { + log_debug("%s: moving pane to new cgroup failed: %s", + __func__, *cause); + free (*cause); + } +#endif goto complete; + } /* * Child process. Change to the working directory or home if that @@ -389,7 +402,7 @@ spawn_pane(struct spawn_context *sc, char **cause) */ if (chdir(new_wp->cwd) == 0) environ_set(child, "PWD", 0, "%s", new_wp->cwd); - else if ((tmp = find_home()) != NULL || chdir(tmp) == 0) + 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, "/"); @@ -416,8 +429,8 @@ spawn_pane(struct spawn_context *sc, char **cause) _exit(1); /* Clean up file descriptors and signals and update the environment. */ - closefrom(STDERR_FILENO + 1); proc_clear_signals(server_proc, 1); + closefrom(STDERR_FILENO + 1); sigprocmask(SIG_SETMASK, &oldset, NULL); log_close(); environ_push(child); @@ -263,6 +263,17 @@ status_line_size(struct client *c) return (s->statuslines); } +/* Get the prompt line number for client's session. 1 means at the bottom. */ +static u_int +status_prompt_line_at(struct client *c) +{ + struct session *s = c->session; + + if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) + return (1); + return (options_get_number(s->options, "message-line")); +} + /* Get window at window list position. */ struct style_range * status_get_range(struct client *c, u_int x, u_int y) @@ -461,17 +472,26 @@ void status_message_set(struct client *c, int delay, int ignore_styles, int ignore_keys, const char *fmt, ...) { - struct timeval tv; - va_list ap; - - status_message_clear(c); - status_push_screen(c); + struct timeval tv; + va_list ap; + char *s; va_start(ap, fmt); - xvasprintf(&c->message_string, fmt, ap); + xvasprintf(&s, fmt, ap); va_end(ap); - server_add_message("%s message: %s", c->name, c->message_string); + log_debug("%s: %s", __func__, s); + + if (c == NULL) { + server_add_message("message: %s", s); + free(s); + return; + } + + status_message_clear(c); + status_push_screen(c); + c->message_string = s; + server_add_message("%s message: %s", c->name, s); /* * With delay -1, the display-time option is used; zero means wait for @@ -533,7 +553,7 @@ status_message_redraw(struct client *c) struct session *s = c->session; struct screen old_screen; size_t len; - u_int lines, offset; + u_int lines, offset, messageline; struct grid_cell gc; struct format_tree *ft; @@ -546,6 +566,10 @@ status_message_redraw(struct client *c) lines = 1; screen_init(sl->active, c->tty.sx, lines, 0); + messageline = status_prompt_line_at(c); + if (messageline > lines - 1) + messageline = lines - 1; + len = screen_write_strlen("%s", c->message_string); if (len > c->tty.sx) len = c->tty.sx; @@ -555,11 +579,11 @@ status_message_redraw(struct client *c) format_free(ft); screen_write_start(&ctx, sl->active); - screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); - screen_write_cursormove(&ctx, 0, lines - 1, 0); + screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); + screen_write_cursormove(&ctx, 0, messageline, 0); for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); - screen_write_cursormove(&ctx, 0, lines - 1, 0); + screen_write_cursormove(&ctx, 0, messageline, 0); if (c->message_ignore_styles) screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); else @@ -695,7 +719,7 @@ status_prompt_redraw(struct client *c) struct session *s = c->session; struct screen old_screen; u_int i, lines, offset, left, start, width; - u_int pcursor, pwidth; + u_int pcursor, pwidth, promptline; struct grid_cell gc, cursorgc; struct format_tree *ft; @@ -708,6 +732,10 @@ status_prompt_redraw(struct client *c) lines = 1; screen_init(sl->active, c->tty.sx, lines, 0); + promptline = status_prompt_line_at(c); + if (promptline > lines - 1) + promptline = lines - 1; + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); if (c->prompt_mode == PROMPT_COMMAND) style_apply(&gc, s->options, "message-command-style", ft); @@ -723,13 +751,13 @@ status_prompt_redraw(struct client *c) start = c->tty.sx; screen_write_start(&ctx, sl->active); - screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); - screen_write_cursormove(&ctx, 0, lines - 1, 0); + screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); + screen_write_cursormove(&ctx, 0, promptline, 0); for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); - screen_write_cursormove(&ctx, 0, lines - 1, 0); + screen_write_cursormove(&ctx, 0, promptline, 0); format_draw(&ctx, &gc, start, c->prompt_string, NULL, 0); - screen_write_cursormove(&ctx, start, lines - 1, 0); + screen_write_cursormove(&ctx, start, promptline, 0); left = c->tty.sx - start; if (left == 0) @@ -1452,8 +1480,6 @@ process_key: return (0); append_key: - if (key <= 0x1f || (key >= KEYC_BASE && key < KEYC_BASE_END)) - return (0); if (key <= 0x7f) utf8_set(&tmp, key); else if (KEYC_IS_UNICODE(key)) @@ -1747,8 +1773,9 @@ status_prompt_complete_list_menu(struct client *c, char **list, u_int size, else offset = 0; - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, - py, c, NULL, status_prompt_menu_callback, spm) != 0) { + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, + BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, + status_prompt_menu_callback, spm) != 0) { menu_free(menu); free(spm); return (0); @@ -1840,8 +1867,9 @@ status_prompt_complete_window_menu(struct client *c, struct session *s, else offset = 0; - if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, - py, c, NULL, status_prompt_menu_callback, spm) != 0) { + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, + BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, + status_prompt_menu_callback, spm) != 0) { menu_free(menu); free(spm); return (NULL); @@ -30,18 +30,25 @@ /* 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, STYLE_ALIGN_DEFAULT, STYLE_LIST_OFF, - STYLE_RANGE_NONE, 0, + STYLE_RANGE_NONE, 0, "", STYLE_DEFAULT_BASE }; +/* Set range string. */ +static void +style_set_range_string(struct style *sy, const char *s) +{ + strlcpy(sy->range_string, s, sizeof sy->range_string); +} + /* * Parse an embedded style of the form "fg=colour,bg=colour,bright,...". Note * that this adds onto the given style, so it must have been initialized @@ -77,6 +84,7 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) if (strcasecmp(tmp, "default") == 0) { sy->gc.fg = base->fg; sy->gc.bg = base->bg; + sy->gc.us = base->us; sy->gc.attr = base->attr; sy->gc.flags = base->flags; } else if (strcasecmp(tmp, "ignore") == 0) @@ -103,32 +111,67 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) } else if (strcasecmp(tmp, "norange") == 0) { sy->range_type = style_default.range_type; sy->range_argument = style_default.range_type; + strlcpy(sy->range_string, style_default.range_string, + sizeof sy->range_string); } else if (end > 6 && strncasecmp(tmp, "range=", 6) == 0) { found = strchr(tmp + 6, '|'); if (found != NULL) { *found++ = '\0'; if (*found == '\0') goto error; - for (cp = found; *cp != '\0'; cp++) { - if (!isdigit((u_char)*cp)) - goto error; - } } if (strcasecmp(tmp + 6, "left") == 0) { if (found != NULL) goto error; sy->range_type = STYLE_RANGE_LEFT; sy->range_argument = 0; + style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "right") == 0) { if (found != NULL) goto error; sy->range_type = STYLE_RANGE_RIGHT; sy->range_argument = 0; + style_set_range_string(sy, ""); + } else if (strcasecmp(tmp + 6, "pane") == 0) { + if (found == NULL) + goto error; + if (*found != '%' || found[1] == '\0') + goto error; + for (cp = found + 1; *cp != '\0'; cp++) { + if (!isdigit((u_char)*cp)) + goto error; + } + sy->range_type = STYLE_RANGE_PANE; + sy->range_argument = atoi(found + 1); + style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "window") == 0) { if (found == NULL) goto error; + for (cp = found; *cp != '\0'; cp++) { + if (!isdigit((u_char)*cp)) + goto error; + } sy->range_type = STYLE_RANGE_WINDOW; sy->range_argument = atoi(found); + style_set_range_string(sy, ""); + } else if (strcasecmp(tmp + 6, "session") == 0) { + if (found == NULL) + goto error; + if (*found != '$' || found[1] == '\0') + goto error; + for (cp = found + 1; *cp != '\0'; cp++) { + if (!isdigit((u_char)*cp)) + goto error; + } + sy->range_type = STYLE_RANGE_SESSION; + sy->range_argument = atoi(found + 1); + style_set_range_string(sy, ""); + } else if (strcasecmp(tmp + 6, "user") == 0) { + if (found == NULL) + goto error; + sy->range_type = STYLE_RANGE_USER; + sy->range_argument = 0; + style_set_range_string(sy, found); } } else if (strcasecmp(tmp, "noalign") == 0) sy->align = style_default.align; @@ -162,6 +205,13 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->gc.bg = base->bg; } else goto error; + } else if (end > 3 && strncasecmp(tmp, "us=", 3) == 0) { + if ((value = colour_fromstring(tmp + 3)) == -1) + goto error; + if (value != 8) + sy->gc.us = value; + else + sy->gc.us = base->us; } else if (strcasecmp(tmp, "none") == 0) sy->gc.attr = 0; else if (end > 2 && strncasecmp(tmp, "no", 2) == 0) { @@ -192,7 +242,7 @@ style_tostring(struct style *sy) int off = 0; const char *comma = "", *tmp = ""; static char s[256]; - char b[16]; + char b[21]; *s = '\0'; @@ -214,9 +264,19 @@ style_tostring(struct style *sy) tmp = "left"; else if (sy->range_type == STYLE_RANGE_RIGHT) tmp = "right"; - else if (sy->range_type == STYLE_RANGE_WINDOW) { + else if (sy->range_type == STYLE_RANGE_PANE) { + snprintf(b, sizeof b, "pane|%%%u", sy->range_argument); + tmp = b; + } else if (sy->range_type == STYLE_RANGE_WINDOW) { snprintf(b, sizeof b, "window|%u", sy->range_argument); tmp = b; + } else if (sy->range_type == STYLE_RANGE_SESSION) { + snprintf(b, sizeof b, "session|$%u", + sy->range_argument); + tmp = b; + } else if (sy->range_type == STYLE_RANGE_USER) { + snprintf(b, sizeof b, "user|%s", sy->range_string); + tmp = b; } off += xsnprintf(s + off, sizeof s - off, "%srange=%s", comma, tmp); @@ -258,6 +318,11 @@ style_tostring(struct style *sy) colour_tostring(gc->bg)); comma = ","; } + if (gc->us != 8) { + off += xsnprintf(s + off, sizeof s - off, "%sus=%s", comma, + colour_tostring(gc->us)); + comma = ","; + } if (gc->attr != 0) { xsnprintf(s + off, sizeof s - off, "%s%s", comma, attributes_tostring(gc->attr)); @@ -287,6 +352,8 @@ style_add(struct grid_cell *gc, struct options *oo, const char *name, gc->fg = sy->gc.fg; if (sy->gc.bg != 8) gc->bg = sy->gc.bg; + if (sy->gc.us != 8) + gc->us = sy->gc.us; gc->attr |= sy->gc.attr; if (ft0 != NULL) diff --git a/tmux-protocol.h b/tmux-protocol.h index 0842229..3cf00c0 100644 --- a/tmux-protocol.h +++ b/tmux-protocol.h @@ -66,7 +66,8 @@ enum msgtype { MSG_WRITE_OPEN, MSG_WRITE, MSG_WRITE_READY, - MSG_WRITE_CLOSE + MSG_WRITE_CLOSE, + MSG_READ_CANCEL }; /* @@ -92,6 +93,10 @@ struct msg_read_done { int error; }; +struct msg_read_cancel { + int stream; +}; + struct msg_write_open { int stream; int fd; @@ -23,7 +23,7 @@ .Sh SYNOPSIS .Nm tmux .Bk -words -.Op Fl 2CDluvV +.Op Fl 2CDlNuVv .Op Fl c Ar shell-command .Op Fl f Ar file .Op Fl L Ar socket-name @@ -140,10 +140,10 @@ By default, loads the system configuration file from .Pa @SYSCONFDIR@/tmux.conf , if present, then looks for a user configuration file at -.Pa ~/.tmux.conf, +.Pa \[ti]/.tmux.conf, .Pa $XDG_CONFIG_HOME/tmux/tmux.conf or -.Pa ~/.config/tmux/tmux.conf . +.Pa \[ti]/.tmux.conf . .Pp The configuration file is a set of .Nm @@ -206,6 +206,12 @@ If is specified, the default socket directory is not used and any .Fl L flag is ignored. +.It Fl T Ar features +Set terminal features for the client. +This is a comma-separated list of features. +See the +.Ic terminal-features +option. .It Fl u Write UTF-8 output to the terminal even if the first environment variable of @@ -217,14 +223,10 @@ 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. -See the -.Ic terminal-features -option. +.It Fl V +Report the +.Nm +version. .It Fl v Request verbose logging. Log messages will be saved into @@ -249,10 +251,6 @@ signal may be sent to the server process to toggle logging between on (as if .Fl v was given) and off. -.It Fl V -Report the -.Nm -version. .It Ar command Op Ar flags This specifies one of a set of commands used to control .Nm , @@ -292,7 +290,7 @@ Rename the current session. Split the current pane into two, left and right. .It & Kill the current window. -.It ' +.It \[aq] Prompt for a window index to select. .It \&( Switch the attached client to the previous session. @@ -364,7 +362,7 @@ Toggle zoom state of the current pane. Swap the current pane with the previous pane. .It } Swap the current pane with the next pane. -.It ~ +.It \[ti] Show previous messages from .Nm , if any. @@ -410,7 +408,7 @@ the command prompt. For example, the same .Ic set-option command run from the shell prompt, from -.Pa ~/.tmux.conf +.Pa \[ti]/.tmux.conf and bound to a key may look like: .Bd -literal -offset indent $ tmux set-option -g status-style bg=cyan @@ -463,7 +461,7 @@ To execute commands, each client has a .Ql command queue . A global command queue not attached to any client is used on startup for configuration files like -.Pa ~/.tmux.conf . +.Pa \[ti]/.tmux.conf . Parsed commands added to the queue are executed in order. Some commands, like .Ic if-shell @@ -471,7 +469,8 @@ and .Ic confirm-before , parse their argument to create a new command which is inserted immediately after themselves. -This means that arguments can be parsed twice or more - once when the parent command (such as +This means that arguments can be parsed twice or more - once when the parent +command (such as .Ic if-shell ) is parsed and again when it parses and executes its command. Commands like @@ -534,7 +533,7 @@ $ tmux neww \\; splitw .Pp Or: .Bd -literal -offset indent -$ tmux neww ';' splitw +$ tmux neww \[aq];\[aq] splitw .Ed .Pp Or from the tmux command prompt: @@ -547,12 +546,12 @@ for example in these .Xr sh 1 commands: .Bd -literal -offset indent -$ tmux neww\e\e; splitw +$ tmux neww\e; splitw .Ed .Pp Or: .Bd -literal -offset indent -$ tmux 'neww;' splitw +$ tmux \[aq]neww;\[aq] splitw .Ed .Pp As in these examples, when running tmux from the shell extra care must be taken @@ -564,7 +563,7 @@ should be escaped according to the shell conventions. For .Xr sh 1 this typically means quoted (such as -.Ql neww ';' splitw ) +.Ql neww \[aq];\[aq] splitw ) or escaped (such as .Ql neww \e\e\e\e; splitw ) . .It @@ -574,14 +573,14 @@ a second time for .Nm ; for example: .Bd -literal -offset indent -$ tmux neww 'foo\e\e;' bar +$ tmux neww \[aq]foo\e\e;\[aq] bar $ tmux neww foo\e\e\e\e; bar .Ed .It Semicolons that are not individual tokens or trailing another token should only be escaped once according to shell conventions; for example: .Bd -literal -offset indent -$ tmux neww 'foo-;-bar' +$ tmux neww \[aq]foo-;-bar\[aq] $ tmux neww foo-\e\e;-bar .Ed .El @@ -594,8 +593,8 @@ line (the \e and the newline are completely removed). This is called line continuation and applies both inside and outside quoted strings and in comments, but not inside braces. .Pp -Command arguments may be specified as strings surrounded by single (') quotes, -double quotes (") or braces ({}). +Command arguments may be specified as strings surrounded by single (\[aq]) +quotes, double quotes (\[dq]) or braces ({}). .\" " This is required when the argument contains any special character. Single and double quoted strings cannot span multiple lines except with line @@ -610,7 +609,7 @@ global environment (see the .Sx GLOBAL AND SESSION ENVIRONMENT section). .It -A leading ~ or ~user is expanded to the home directory of the current or +A leading \[ti] or \[ti]user is expanded to the home directory of the current or specified user. .It \euXXXX or \euXXXXXXXX is replaced by the Unicode codepoint corresponding to @@ -642,10 +641,10 @@ These two examples produce an identical command - note that no escaping is needed when using {}: .Bd -literal -offset indent if-shell true { - display -p 'brace-dollar-foo: }$foo' + display -p \[aq]brace-dollar-foo: }$foo\[aq] } -if-shell true "display -p 'brace-dollar-foo: }\e$foo'" +if-shell true "display -p \[aq]brace-dollar-foo: }\e$foo\[aq]" .Ed .Pp Braces may be enclosed inside braces, for example: @@ -889,14 +888,14 @@ may consist entirely of the token .Ql {mouse} (alternative form .Ql = ) -to specify the session, window or pane where the most recent mouse event occurred -(see the +to specify the session, window or pane where the most recent mouse event +occurred (see the .Sx MOUSE SUPPORT section) or .Ql {marked} (alternative form -.Ql ~ ) +.Ql \[ti] ) to specify the marked pane (see .Ic select-pane .Fl m ) . @@ -936,12 +935,12 @@ arguments are commands. This may be a single argument passed to the shell, for example: .Bd -literal -offset indent -new-window 'vi ~/.tmux.conf' +new-window \[aq]vi \[ti]/.tmux.conf\[aq] .Ed .Pp Will run: .Bd -literal -offset indent -/bin/sh -c 'vi ~/.tmux.conf' +/bin/sh -c \[aq]vi \[ti]/.tmux.conf\[aq] .Ed .Pp Additionally, the @@ -958,7 +957,7 @@ to be given as multiple arguments and executed directly (without This can avoid issues with shell quoting. For example: .Bd -literal -offset indent -$ tmux new-window vi ~/.tmux.conf +$ tmux new-window vi \[ti]/.tmux.conf .Ed .Pp Will run @@ -966,7 +965,7 @@ Will run directly without invoking the shell. .Pp .Ar command -.Op Ar arguments +.Op Ar argument ... refers to a .Nm command, either passed with the command and arguments separately, for example: @@ -993,7 +992,7 @@ set-option -wt:0 monitor-activity on new-window ; split-window -d -bind-key R source-file ~/.tmux.conf \e; \e +bind-key R source-file \[ti]/.tmux.conf \e; \e display-message "source-file done" .Ed .Pp @@ -1004,7 +1003,7 @@ $ tmux kill-window -t :1 $ tmux new-window \e; split-window -d -$ tmux new-session -d 'vi ~/.tmux.conf' \e; split-window -d \e; attach +$ tmux new-session -d \[aq]vi \[ti]/.tmux.conf\[aq] \e; split-window -d \e; attach .Ed .Sh CLIENTS AND SESSIONS The @@ -1169,13 +1168,17 @@ session. .Tg lsc .It Xo Ic list-clients .Op Fl F Ar format +.Op Fl f Ar filter .Op Fl t Ar target-session .Xc .D1 Pq alias: Ic lsc List all clients attached to the server. -For the meaning of the .Fl F -flag, see the +specifies the format of each line and +.Fl f +a filter. +Only clients for which the filter is true are shown. +See the .Sx FORMATS section. If @@ -1278,7 +1281,10 @@ behave like .Ic attach-session if .Ar session-name -already exists; in this case, +already exists; +if +.Fl A +is given, .Fl D behaves like .Fl d @@ -1462,7 +1468,7 @@ requests the clipboard from the client using the .Xr xterm 1 escape sequence. If -Ar target-pane +.Ar target-pane is given, the clipboard is sent (in encoded form), otherwise it is stored in a new paste buffer. .Pp @@ -1543,8 +1549,8 @@ show debugging information about jobs and terminals. .Tg source .It Xo Ic source-file .Op Fl Fnqv -.Ar path -.Ar ... +.Op Fl t Ar target-pane +.Ar path ... .Xc .D1 Pq alias: Ic source Execute commands from one or more files specified by @@ -1576,8 +1582,9 @@ server, if not already running, without creating any sessions. .Pp Note that as by default the .Nm -server will exit with no sessions, this is only useful if a session is created in -.Pa ~/.tmux.conf , +server will exit with no sessions, this is only useful if a session is created +in +.Pa \[ti]/.tmux.conf , .Ic exit-empty is turned off, or another command is run as part of the same command sequence. For example: @@ -1739,88 +1746,267 @@ Key tables may be viewed with the command. .Pp The following commands are supported in copy mode: -.Bl -column "CommandXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "viXXXXXXXXXX" "emacs" -offset indent -.It Sy "Command" Ta Sy "vi" Ta Sy "emacs" -.It Li "append-selection" Ta "" Ta "" -.It Li "append-selection-and-cancel" Ta "A" Ta "" -.It Li "back-to-indentation" Ta "^" Ta "M-m" -.It Li "begin-selection" Ta "Space" Ta "C-Space" -.It Li "bottom-line" Ta "L" Ta "" -.It Li "cancel" Ta "q" Ta "Escape" -.It Li "clear-selection" Ta "Escape" Ta "C-g" -.It Li "copy-end-of-line [<prefix>]" Ta "" Ta "" -.It Li "copy-end-of-line-and-cancel [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe-end-of-line [<command>] [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe-end-of-line-and-cancel [<command>] [<prefix>]" Ta "D" Ta "C-k" -.It Li "copy-line [<prefix>]" Ta "" Ta "" -.It Li "copy-line-and-cancel [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe-line [<command>] [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe-line-and-cancel [<command>] [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe [<command>] [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe-no-clear [<command>] [<prefix>]" Ta "" Ta "" -.It Li "copy-pipe-and-cancel [<command>] [<prefix>]" Ta "" Ta "" -.It Li "copy-selection [<prefix>]" Ta "" Ta "" -.It Li "copy-selection-no-clear [<prefix>]" Ta "" Ta "" -.It Li "copy-selection-and-cancel [<prefix>]" Ta "Enter" Ta "M-w" -.It Li "cursor-down" Ta "j" Ta "Down" -.It Li "cursor-down-and-cancel" Ta "" Ta "" -.It Li "cursor-left" Ta "h" Ta "Left" -.It Li "cursor-right" Ta "l" Ta "Right" -.It Li "cursor-up" Ta "k" Ta "Up" -.It Li "end-of-line" Ta "$" Ta "C-e" -.It Li "goto-line <line>" Ta ":" Ta "g" -.It Li "halfpage-down" Ta "C-d" Ta "M-Down" -.It Li "halfpage-down-and-cancel" Ta "" Ta "" -.It Li "halfpage-up" Ta "C-u" Ta "M-Up" -.It Li "history-bottom" Ta "G" Ta "M->" -.It Li "history-top" Ta "g" Ta "M-<" -.It Li "jump-again" Ta ";" Ta ";" -.It Li "jump-backward <to>" Ta "F" Ta "F" -.It Li "jump-forward <to>" Ta "f" Ta "f" -.It Li "jump-reverse" Ta "," Ta "," -.It Li "jump-to-backward <to>" Ta "T" Ta "" -.It Li "jump-to-forward <to>" Ta "t" Ta "" -.It Li "jump-to-mark" Ta "M-x" Ta "M-x" -.It Li "middle-line" Ta "M" Ta "M-r" -.It Li "next-matching-bracket" Ta "%" Ta "M-C-f" -.It Li "next-paragraph" Ta "}" Ta "M-}" -.It Li "next-space" Ta "W" Ta "" -.It Li "next-space-end" Ta "E" Ta "" -.It Li "next-word" Ta "w" Ta "" -.It Li "next-word-end" Ta "e" Ta "M-f" -.It Li "other-end" Ta "o" Ta "" -.It Li "page-down" Ta "C-f" Ta "PageDown" -.It Li "page-down-and-cancel" Ta "" Ta "" -.It Li "page-up" Ta "C-b" Ta "PageUp" -.It Li "pipe [<command>] [<prefix>]" Ta "" Ta "" -.It Li "pipe-no-clear [<command>] [<prefix>]" Ta "" Ta "" -.It Li "pipe-and-cancel [<command>] [<prefix>]" Ta "" Ta "" -.It Li "previous-matching-bracket" Ta "" Ta "M-C-b" -.It Li "previous-paragraph" Ta "{" Ta "M-{" -.It Li "previous-space" Ta "B" Ta "" -.It Li "previous-word" Ta "b" Ta "M-b" -.It Li "rectangle-on" Ta "" Ta "" -.It Li "rectangle-off" Ta "" Ta "" -.It Li "rectangle-toggle" Ta "v" Ta "R" -.It Li "refresh-from-pane" Ta "r" Ta "r" -.It Li "scroll-down" Ta "C-e" Ta "C-Down" -.It Li "scroll-down-and-cancel" Ta "" Ta "" -.It Li "scroll-up" Ta "C-y" Ta "C-Up" -.It Li "search-again" Ta "n" Ta "n" -.It Li "search-backward <for>" Ta "?" Ta "" -.It Li "search-backward-incremental <for>" Ta "" Ta "C-r" -.It Li "search-backward-text <for>" Ta "" Ta "" -.It Li "search-forward <for>" Ta "/" Ta "" -.It Li "search-forward-incremental <for>" Ta "" Ta "C-s" -.It Li "search-forward-text <for>" Ta "" Ta "" -.It Li "search-reverse" Ta "N" Ta "N" -.It Li "select-line" Ta "V" Ta "" -.It Li "select-word" Ta "" Ta "" -.It Li "set-mark" Ta "X" Ta "X" -.It Li "start-of-line" Ta "0" Ta "C-a" -.It Li "stop-selection" Ta "" Ta "" -.It Li "toggle-position" Ta "P" Ta "P" -.It Li "top-line" Ta "H" Ta "M-R" +.Bl -tag -width Ds +.It Xo +.Ic append-selection +.Xc +Append the selection to the top paste buffer. +.It Xo +.Ic append-selection-and-cancel +(vi: A) +.Xc +Append the selection to the top paste buffer and exit copy mode. +.It Xo +.Ic back-to-indentation +(vi: ^) +(emacs: M-m) +.Xc +Move the cursor back to the indentation. +.It Xo +.Ic begin-selection +(vi: Space) +(emacs: C-Space) +.Xc +Begin selection. +.It Xo +.Ic bottom-line +(vi: L) +.Xc +Move to the bottom line. +.It Xo +.Ic cancel +(vi: q) +(emacs: Escape) +.Xc +Exit copy mode. +.It Xo +.Ic clear-selection +(vi: Escape) +(emacs: C-g) +.Xc +Clear the current selection. +.It Xo +.Ic copy-end-of-line +.Op Ar prefix +.Xc +Copy from the cursor position to the end of the line. +.Ar prefix +is used to name the new paste buffer. +.It Xo +.Ic copy-end-of-line-and-cancel +.Op Ar prefix +.Xc +Copy from the cursor position and exit copy mode. +.It Xo +.Ic copy-line +.Op Ar prefix +.Xc +Copy the entire line. +.It Xo +.Ic copy-line-and-cancel +.Op Ar prefix +.Xc +Copy the entire line and exit copy mode. +.It Xo +.Ic copy-selection +.Op Ar prefix +.Xc +Copies the current selection. +.It Xo +.Ic copy-selection-and-cancel +.Op Ar prefix +(vi: Enter) +(emacs: M-w) +.Xc +Copy the current selection and exit copy mode. +.It Xo +.Ic cursor-down +(vi: j) +(emacs: Down) +.Xc +Move the cursor down. +.It Xo +.Ic cursor-left +(vi: h) +(emacs: Left) +.Xc +Move the cursor left. +.It Xo +.Ic cursor-right +(vi: l) +(emacs: Right) +.Xc +Move the cursor right. +.It Xo +.Ic cursor-up +(vi: k) +(emacs: Up) +.Xc +Move the cursor up. +.It Xo +.Ic end-of-line +(vi: $) +(emacs: C-e) +.Xc +Move the cursor to the end of the line. +.It Xo +.Ic goto-line +.Ar line +(vi: :) +(emacs: g) +.Xc +Move the cursor to a specific line. +.It Xo +.Ic history-bottom +(vi: G) +(emacs: M->) +.Xc +Scroll to the bottom of the history. +.It Xo +.Ic history-top +(vi: g) +(emacs: M-<) +.Xc +Scroll to the top of the history. +.It Xo +.Ic jump-again +(vi: ;) +(emacs: ;) +.Xc +Repeat the last jump. +.It Xo +.Ic jump-backward +.Ar to +(vi: F) +(emacs: F) +.Xc +Jump backwards to the specified text. +.It Xo +.Ic jump-forward +.Ar to +(vi: f) +(emacs: f) +.Xc +Jump forward to the specified text. +.It Xo +.Ic jump-to-mark +(vi: M-x) +(emacs: M-x) +.Xc +Jump to the last mark. +.It Xo +.Ic middle-line +(vi: M) +(emacs: M-r) +.Xc +Move to the middle line. +.It Xo +.Ic next-matching-bracket +(vi: %) +(emacs: M-C-f) +.Xc +Move to the next matching bracket. +.It Xo +.Ic next-paragraph +(vi: }) +(emacs: M-}) +.Xc +Move to the next paragraph. +.It Xo +.Ic next-prompt +.Op Fl o +.Xc +Move to the next prompt. +.It Xo +.Ic next-word +(vi: w) +.Xc +Move to the next word. +.It Xo +.Ic page-down +(vi: C-f) +(emacs: PageDown) +.Xc +Scroll down by one page. +.It Xo +.Ic page-up +(vi: C-b) +(emacs: PageUp) +.Xc +Scroll up by one page. +.It Xo +.Ic previous-matching-bracket +(emacs: M-C-b) +.Xc +Move to the previous matching bracket. +.It Xo +.Ic previous-paragraph +(vi: {) +(emacs: M-{) +.Xc +Move to the previous paragraph. +.It Xo +.Ic previous-prompt +.Op Fl o +.Xc +Move to the previous prompt. +.It Xo +.Ic previous-word +(vi: b) +(emacs: M-b) +.Xc +Move to the previous word. +.It Xo +.Ic rectangle-toggle +(vi: v) +(emacs: R) +.Xc +Toggle rectangle selection mode. +.It Xo +.Ic refresh-from-pane +(vi: r) +(emacs: r) +.Xc +Refresh the content from the pane. +.It Xo +.Ic search-again +(vi: n) +(emacs: n) +.Xc +Repeat the last search. +.It Xo +.Ic search-backward +.Ar text +(vi: ?) +.Xc +Search backwards for the specified text. +.It Xo +.Ic search-forward +.Ar text +(vi: /) +.Xc +Search forward for the specified text. +.It Xo +.Ic select-line +(vi: V) +.Xc +Select the current line. +.It Xo +.Ic select-word +.Xc +Select the current word. +.It Xo +.Ic start-of-line +(vi: 0) +(emacs: C-a) +.Xc +Move the cursor to the start of the line. +.It Xo +.Ic top-line +(vi: H) +(emacs: M-R) +.Xc +Move to the top line. .El .Pp The search commands come in several varieties: @@ -1843,6 +2029,19 @@ repeats the last search and does the same but reverses the direction (forward becomes backward and backward becomes forward). .Pp +The +.Ql next-prompt +and +.Ql previous-prompt +move between shell prompts, but require the shell to emit an escape sequence +(\e033]133;A\e033\e\e) to tell +.Nm +where the prompts are located; if the shell does not do this, these commands +will do nothing. +The +.Fl o +flag jumps to the beginning of the command output instead of the shell prompt. +.Pp Copy commands may take an optional buffer prefix argument which is used to generate the buffer name (the default is .Ql buffer @@ -1928,7 +2127,8 @@ bind PageUp copy-mode -eu .Ed .El .Pp -A number of preset arrangements of panes are available, these are called layouts. +A number of preset arrangements of panes are available, these are called +layouts. These may be selected with the .Ic select-layout command or cycled with @@ -1974,7 +2174,7 @@ For example: $ tmux list-windows 0: ksh [159x48] layout: bb62,159x48,0,0{79x48,0,0,79x48,80,0} -$ tmux select-layout bb62,159x48,0,0{79x48,0,0,79x48,80,0} +$ tmux select-layout \[aq]bb62,159x48,0,0{79x48,0,0,79x48,80,0}\[aq] .Ed .Pp .Nm @@ -2015,7 +2215,7 @@ but a different format may be specified with .Fl F . .Tg capturep .It Xo Ic capture-pane -.Op Fl aepPqCJN +.Op Fl aAepPqCJN .Op Fl b Ar buffer-name .Op Fl E Ar end-line .Op Fl S Ar start-line @@ -2040,10 +2240,15 @@ is given, the output includes escape sequences for text and background attributes. .Fl C also escapes non-printable characters as octal \exxx. +.Fl T +ignores trailing positions that do not contain a character. .Fl N preserves trailing spaces at each line's end and .Fl J -preserves trailing spaces and joins any wrapped lines. +preserves trailing spaces and joins any wrapped lines; +.Fl J +implies +.Fl T . .Fl P captures only any output that the pane has received that is the beginning of an as-yet incomplete escape sequence. @@ -2109,15 +2314,17 @@ is replaced by the client name in and the result executed as a command. If .Ar template -is not given, "detach-client -t '%%'" is used. +is not given, "detach-client -t \[aq]%%\[aq]" is used. .Pp .Fl O 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 @@ -2192,14 +2399,15 @@ are replaced by the target in and the result executed as a command. If .Ar template -is not given, "switch-client -t '%%'" is used. +is not given, "switch-client -t \[aq]%%\[aq]" is used. .Pp .Fl O 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 @@ -2303,7 +2511,7 @@ to be executed as a command with substituted by the pane ID. The default .Ar template -is "select-pane -t '%%'". +is "select-pane -t \[aq]%%\[aq]". With .Fl b , other commands are not blocked from running until the indicator is closed. @@ -2665,7 +2873,7 @@ The option only opens a new pipe if no previous pipe exists, allowing a pipe to be toggled with a single key, for example: .Bd -literal -offset indent -bind-key C-p pipe-pane -o 'cat >>~/output.#I-#P' +bind-key C-p pipe-pane -o \[aq]cat >>\[ti]/output.#I-#P\[aq] .Ed .Tg prevl .It Xo Ic previous-layout @@ -2969,7 +3177,7 @@ zooms if the window is not zoomed, or keeps it zoomed if already zoomed. .Pp An empty .Ar shell-command -('') will create a pane with no command running in it. +(\[aq]\[aq]) will create a pane with no command running in it. Output can be sent to such a pane with the .Ic display-message command. @@ -3096,11 +3304,11 @@ and Note that to bind the .Ql \&" or -.Ql ' +.Ql \[aq] keys, quotation marks are necessary, for example: .Bd -literal -offset indent -bind-key '"' split-window -bind-key "'" new-window +bind-key \[aq]"\[aq] split-window +bind-key "\[aq]" new-window .Ed .Pp A command bound to the @@ -3114,7 +3322,7 @@ Commands related to key bindings are as follows: .Op Fl nr .Op Fl N Ar note .Op Fl T Ar key-table -.Ar key command Op Ar arguments +.Ar key command Op Ar argument ... .Xc .D1 Pq alias: Ic bind Bind key @@ -3206,13 +3414,14 @@ lists only the first matching key. lists the command for keys that do not have a note rather than skipping them. .Tg send .It Xo Ic send-keys -.Op Fl FHlMRX +.Op Fl FHKlMRX +.Op Fl c Ar target-client .Op Fl N Ar repeat-count .Op Fl t Ar target-pane -.Ar key Ar ... +.Ar key ... .Xc .D1 Pq alias: Ic send -Send a key or keys to a window. +Send a key or keys to a window or client. Each argument .Ar key is the name of the key (such as @@ -3221,6 +3430,12 @@ or .Ql NPage ) to send; if the string is not recognised as a key, it is sent as a series of characters. +If +.Fl K +is given, keys are sent to +.Ar target-client , +so they are looked up in the client's key table, rather than to +.Ar target-pane . All arguments are sent sequentially from first to last. If no keys are given and the command is bound to a key, then that key is used. .Pp @@ -3489,7 +3704,7 @@ it is replaced with .Ar value . For example, after: .Pp -.Dl set -s command-alias[100] zoom='resize-pane -Z' +.Dl set -s command-alias[100] zoom=\[aq]resize-pane -Z\[aq] .Pp Using: .Pp @@ -3659,6 +3874,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 @@ -3673,6 +3896,8 @@ Supports the overline SGR attribute. Supports the DECFRA rectangle fill escape sequence. .It RGB Supports RGB colour with the SGR escape sequences. +.It sixel +Supports SIXEL graphics. .It strikethrough Supports the strikethrough SGR escape sequence. .It sync @@ -3717,7 +3942,7 @@ and so on. .Pp For example: .Bd -literal -offset indent -set -s user-keys[0] "\ee[5;30012~" +set -s user-keys[0] "\ee[5;30012\[ti]" bind User0 resize-pane -L 3 .Ed .El @@ -3801,21 +4026,40 @@ The value is the width and height separated by an character. The default is 80x24. .It Xo Ic destroy-unattached -.Op Ic on | off +.Op Ic off | on | keep-last | keep-group .Xc -If enabled and the session is no longer attached to any clients, it is -destroyed. +If +.Ic on , +destroy the session after the last client has detached. +If +.Ic off +(the default), leave the session orphaned. +If +.Ic keep-last , +destroy the session only if it is in a group and has other sessions in that group. +If +.Ic keep-group , +destroy the session unless it is in a group and is the only session in that group. .It Xo Ic detach-on-destroy -.Op Ic off | on | no-detached +.Op Ic off | on | no-detached | previous | next .Xc -If on (the default), the client is detached when the session it is attached to +If +.Ic on +(the default), the client is detached when the session it is attached to is destroyed. -If off, the client is switched to the most recently active of the remaining +If +.Ic off , +the client is switched to the most recently active of the remaining sessions. If .Ic no-detached , the client is detached only if there are no detached sessions; if detached sessions exist, the client is switched to the most recently active. +If +.Ic previous +or +.Ic next , +the client is switched to the previous or next session in alphabetical order. .It Ic display-panes-active-colour Ar colour Set the colour used by the .Ic display-panes @@ -3856,6 +4100,33 @@ The default is to run .Xr lock 1 with .Fl np . +.It Ic menu-style Ar style +Set the menu style. +See the +.Sx STYLES +section on how to specify +.Ar style . +Attributes are ignored. +.It Ic menu-selected-style Ar style +Set the selected menu item style. +See the +.Sx STYLES +section on how to specify +.Ar style . +Attributes are ignored. +.It Ic menu-border-style Ar style +Set the menu border style. +See the +.Sx STYLES +section on how to specify +.Ar style . +Attributes are ignored. +.It Ic menu-border-lines Ar type +Set the type of characters used for drawing menu borders. +See +.Ic popup-border-lines +for possible values for +.Ar border-lines . .It Ic message-command-style Ar style Set status line message command style. This is used for the command prompt with @@ -3866,6 +4137,10 @@ For how to specify see the .Sx STYLES section. +.It Xo Ic message-line +.Op Ic 0 | 1 | 2 | 3 | 4 +.Xc +Set line on which status line messages and the command prompt are shown. .It Ic message-style Ar style Set status line message style. This is used for messages and for the command prompt. @@ -4319,20 +4594,18 @@ Attributes are ignored. .Pp .It Ic popup-style Ar style Set the popup style. -For how to specify -.Ar style , -see the +See the .Sx STYLES -section. +section on how to specify +.Ar style . Attributes are ignored. .Pp .It Ic popup-border-style Ar style Set the popup border style. -For how to specify -.Ar style , -see the +See the .Sx STYLES -section. +section on how to specify +.Ar style . Attributes are ignored. .Pp .It Ic popup-border-lines Ar type @@ -4455,11 +4728,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 @@ -4559,7 +4838,8 @@ hook and there are a number of hooks not associated with commands. .Pp Hooks are stored as array options, members of the array are executed in order when the hook is triggered. -Like options different hooks may be global or belong to a session, window or pane. +Like options different hooks may be global or belong to a session, window or +pane. Hooks may be configured with the .Ic set-hook or @@ -4571,8 +4851,8 @@ or .Fl H . The following two commands are equivalent: .Bd -literal -offset indent. -set-hook -g pane-mode-changed[42] 'set -g status-left-style bg=red' -set-option -g pane-mode-changed[42] 'set -g status-left-style bg=red' +set-hook -g pane-mode-changed[42] \[aq]set -g status-left-style bg=red\[aq] +set-option -g pane-mode-changed[42] \[aq]set -g status-left-style bg=red\[aq] .Ed .Pp Setting a hook without specifying an array index clears the hook and sets the @@ -4742,7 +5022,8 @@ or .Ar target-pane in commands bound to mouse key bindings. It resolves to the window or pane over which the mouse event took place -(for example, the window in the status line over which button 1 was released for a +(for example, the window in the status line over which button 1 was released +for a .Ql MouseUp1Status binding, or the pane over which the wheel was scrolled for a .Ql WheelDownPane @@ -4882,13 +5163,16 @@ ignores case. For example: .Ql #{C/r:^Start} .Pp -Numeric operators may be performed by prefixing two comma-separated alternatives with an +Numeric operators may be performed by prefixing two comma-separated alternatives +with an .Ql e and an operator. An optional .Ql f -flag may be given after the operator to use floating point numbers, otherwise integers are used. -This may be followed by a number giving the number of decimal places to use for the result. +flag may be given after the operator to use floating point numbers, otherwise +integers are used. +This may be followed by a number giving the number of decimal places to use for +the result. The available operators are: addition .Ql + , @@ -5018,10 +5302,11 @@ but also expands .Xr strftime 3 specifiers. .Ql S:\& , -.Ql W:\& -or +.Ql W:\& , .Ql P:\& -will loop over each session, window or pane and insert the format once +or +.Ql L:\& +will loop over each session, window, pane or client and insert the format once for each. For windows and panes, two comma-separated formats may be given: the second is used for the current window or active pane. @@ -5048,7 +5333,8 @@ will substitute with .Ql bar throughout. -The first argument may be an extended regular expression and a final argument may be +The first argument may be an extended regular expression and a final argument +may be .Ql i to ignore case, for example .Ql s/a(.)/\e1x/i:\& @@ -5056,6 +5342,15 @@ would change .Ql abABab into .Ql bxBxbx . +A different delimiter character may also be used, to avoid collisions with +literal slashes in the pattern. +For example, +.Ql s|foo/|bar/|:\& +will substitute +.Ql foo/ +with +.Ql bar/ +throughout. .Pp In addition, the last line of a shell command's output may be inserted using .Ql #() . @@ -5066,10 +5361,10 @@ When constructing formats, .Nm does not wait for .Ql #() -commands to finish; instead, the previous result from running the same command is used, -or a placeholder if the command has not been run before. -If the command hasn't exited, the most recent line of output will be used, but the status -line will not be updated more than once a second. +commands to finish; instead, the previous result from running the same command +is used, or a placeholder if the command has not been run before. +If the command hasn't exited, the most recent line of output will be used, but +the status line will not be updated more than once a second. Commands are executed using .Pa /bin/sh and with the @@ -5155,9 +5450,12 @@ 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" +.It Li "mouse_status_line" Ta "" Ta "Status line on which mouse event took place" +.It Li "mouse_status_range" Ta "" Ta "Range type or argument of mouse event on status line" .It Li "mouse_utf8_flag" Ta "" Ta "Pane mouse UTF-8 flag" .It Li "mouse_word" Ta "" Ta "Word under mouse, if any" .It Li "mouse_x" Ta "" Ta "Mouse X position, if any" @@ -5201,6 +5499,7 @@ The following variables are available, where appropriate: .It Li "pane_title" Ta "#T" Ta "Title of pane (can be set by application)" .It Li "pane_top" Ta "" Ta "Top of pane" .It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane" +.It Li "pane_unseen_changes" Ta "" Ta "1 if there were changes in pane while in mode" .It Li "pane_width" Ta "" Ta "Width of pane" .It Li "pid" Ta "" Ta "Server PID" .It Li "rectangle_toggle" Ta "" Ta "1 if rectangle selection is activated" @@ -5215,6 +5514,7 @@ The following variables are available, where appropriate: .It Li "selection_present" Ta "" Ta "1 if selection started in copy mode" .It Li "selection_start_x" Ta "" Ta "X position of the start of the selection" .It Li "selection_start_y" Ta "" Ta "Y position of the start of the selection" +.It Li "server_sessions" Ta "" Ta "Number of sessions" .It Li "session_activity" Ta "" Ta "Time of session last activity" .It Li "session_alerts" Ta "" Ta "List of window indexes with alerts" .It Li "session_attached" Ta "" Ta "Number of clients session is attached to" @@ -5323,6 +5623,8 @@ for the terminal default colour; or a hexadecimal RGB string such as .Ql #ffffff . .It Ic bg=colour Set the background colour. +.It Ic us=colour +Set the underscore colour. .It Ic none Set no attributes (turn off any active attributes). .It Xo Ic acs , @@ -5369,8 +5671,8 @@ option: .Ic list=on marks the start of the list; .Ic list=focus -is the part of the list that should be kept in focus if the entire list won't fit -in the available space (typically the current window); +is the part of the list that should be kept in focus if the entire list won't +fit in the available space (typically the current window); .Ic list=left-marker and .Ic list=right-marker @@ -5392,26 +5694,56 @@ Only one default may be pushed (each replaces the previous saved default). .It Xo Ic range=left , .Ic range=right , +.Ic range=session|X , .Ic range=window|X , +.Ic range=pane|X , +.Ic range=user|X , .Ic norange .Xc -Mark a range in the +Mark a range for mouse events in the .Ic status-format option. +When a mouse event occurs in the .Ic range=left -and +or .Ic range=right -are the text used for the +range, the .Ql StatusLeft and .Ql StatusRight -mouse keys. +key bindings are triggered. +.Pp +.Ic range=session|X , .Ic range=window|X -is the range for a window passed to the +and +.Ic range=pane|X +are ranges for a session, window or pane. +These trigger the .Ql Status -mouse key, where +mouse key with the target session, window or pane given by the .Ql X -is a window index. +argument. +.Ql X +is a session ID, window index in the current session or a pane ID. +For these, the +.Ic mouse_status_range +format variable will be set to +.Ql session , +.Ql window +or +.Ql pane . +.Pp +.Ic range=user|X +is a user-defined range; it triggers the +.Ql Status +mouse key. +The argument +.Ql X +will be available in the +.Ic mouse_status_range +format variable. +.Ql X +must be at most 15 bytes in length. .El .Pp Examples are: @@ -5459,7 +5791,7 @@ An escape sequence (if the .Ic allow-rename option is turned on): .Bd -literal -offset indent -$ printf '\e033kWINDOW_NAME\e033\e\e' +$ printf \[aq]\e033kWINDOW_NAME\e033\e\e\[aq] .Ed .It Automatic renaming, which sets the name to the active command in the window's @@ -5472,7 +5804,7 @@ option. When a pane is first created, its title is the hostname. A pane's title can be set via the title setting escape sequence, for example: .Bd -literal -offset indent -$ printf '\e033]2;My Title\e033\e\e' +$ printf \[aq]\e033]2;My Title\e033\e\e\[aq] .Ed .Pp It can also be modified with the @@ -5597,7 +5929,7 @@ The flag is one of the following symbols appended to the window name: .It Li "-" Ta "Marks the last window (previously selected)." .It Li "#" Ta "Window activity is monitored and activity has been detected." .It Li "\&!" Ta "Window bells are monitored and a bell has occurred in the window." -.It Li "~" Ta "The window has been silent for the monitor-silence interval." +.It Li "\[ti]" Ta "The window has been silent for the monitor-silence interval." .It Li "M" Ta "The window contains the marked pane." .It Li "Z" Ta "The window's active pane is zoomed." .El @@ -5747,7 +6079,8 @@ the prompt is shown in the background and the invoking client does not exit until it is dismissed. .Tg confirm .It Xo Ic confirm-before -.Op Fl b +.Op Fl by +.Op Fl c Ar confirm-key .Op Fl p Ar prompt .Op Fl t Ar target-client .Ar command @@ -5768,18 +6101,30 @@ With .Fl b , the prompt is shown in the background and the invoking client does not exit until it is dismissed. +.Fl y +changes the default behaviour (if Enter alone is pressed) of the prompt to +run the command. +.Fl c +changes the confirmation key to +.Ar confirm-key ; +the default is +.Ql y . .Tg menu .It Xo Ic display-menu .Op Fl O +.Op Fl b Ar border-lines .Op Fl c Ar target-client +.Op Fl C Ar starting-choice +.Op Fl H Ar selected-style +.Op Fl s Ar style +.Op Fl S Ar border-style .Op Fl t Ar target-pane .Op Fl T Ar title .Op Fl x Ar position .Op Fl y Ar position .Ar name .Ar key -.Ar command -.Ar ... +.Ar command Op Ar argument ... .Xc .D1 Pq alias: Ic menu Display a menu on @@ -5800,10 +6145,31 @@ may not be chosen. The name may be empty for a separator line, in which case both the key and command should be omitted. .Pp +.Fl b +sets the type of characters used for drawing menu borders. +See +.Ic popup-border-lines +for possible values for +.Ar border-lines . +.Pp +.Fl H +sets the style for the selected menu item (see +.Sx STYLES ) . +.Pp +.Fl s +sets the style for the menu and +.Fl S +sets the style for the menu border (see +.Sx STYLES ) . +.Pp .Fl T is a format for the menu title (see .Sx FORMATS ) . .Pp +.Fl C +sets the menu item selected by default, if the menu is not bound to a mouse key +binding. +.Pp .Fl x and .Fl y @@ -5862,7 +6228,7 @@ The following keys are also available: .El .Tg display .It Xo Ic display-message -.Op Fl aINpv +.Op Fl aIlNpv .Op Fl c Ar target-client .Op Fl d Ar delay .Op Fl t Ar target-pane @@ -5884,7 +6250,12 @@ is not given, the option is used; a delay of zero waits for a key press. .Ql N ignores key presses and closes only after the delay expires. -The format of +If +.Fl l +is given, +.Ar message +is printed unchanged. +Otherwise, the format of .Ar message is described in the .Sx FORMATS @@ -5910,8 +6281,8 @@ forwards any input read from stdin to the empty pane given by .Op Fl d Ar start-directory .Op Fl e Ar environment .Op Fl h Ar height -.Op Fl s Ar style -.Op Fl S Ar border-style +.Op Fl s Ar border-style +.Op Fl S Ar style .Op Fl t Ar target-pane .Op Fl T Ar title .Op Fl w Ar width @@ -5954,7 +6325,7 @@ If omitted, half of the terminal size is used. does not surround the popup by a border. .Pp .Fl b -sets the type of border line for the popup. +sets the type of characters used for drawing popup borders. When .Fl B is specified, the @@ -5968,12 +6339,8 @@ for possible values for .Fl s sets the style for the popup and .Fl S -sets the style for the popup border. -For how to specify -.Ar style , -see the -.Sx STYLES -section. +sets the style for the popup border (see +.Sx STYLES ) . .Pp .Fl e takes the form @@ -6097,11 +6464,12 @@ is replaced by the buffer name in and the result executed as a command. If .Ar template -is not given, "paste-buffer -b '%%'" is used. +is not given, "paste-buffer -b \[aq]%%\[aq]" is used. .Pp .Fl O specifies the initial sort field: one of -.Ql time , +.Ql time +(creation), .Ql name or .Ql size . @@ -6119,9 +6487,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 @@ -6277,6 +6650,7 @@ option. .Tg run .It Xo Ic run-shell .Op Fl bC +.Op Fl c Ar start-directory .Op Fl d Ar delay .Op Fl t Ar target-pane .Op Ar shell-command @@ -6304,6 +6678,10 @@ waits for .Ar delay seconds before starting the command. If +.Fl c +is given, the current working directory is set to +.Ar start-directory . +If .Fl C is not given, any output to stdout is displayed in view mode (in the pane specified by @@ -6385,7 +6763,7 @@ If set, a sequence such as this may be used to change the cursor colour from inside .Nm : .Bd -literal -offset indent -$ printf '\e033]12;red\e033\e\e' +$ printf \[aq]\e033]12;red\e033\e\e\[aq] .Ed .Pp The colour is an @@ -6409,6 +6787,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 @@ -6420,16 +6804,22 @@ Set a styled underscore. The single parameter is one of: 0 for no underscore, 1 for normal underscore, 2 for double underscore, 3 for curly underscore, 4 for dotted underscore and 5 for dashed underscore. -.It Em \&Setulc , \&ol +.It Em \&Setulc , \&Setulc1, \&ol Set the underscore colour or reset to the default. -The argument is (red * 65536) + (green * 256) + blue where each is between 0 +.Em Setulc +is for RGB colours and +.Em Setulc1 +for ANSI or 256 colours. +The +.Em Setulc +argument is (red * 65536) + (green * 256) + blue where each is between 0 and 255. .It Em \&Ss , Se Set or reset the cursor style. If set, a sequence such as this may be used to change the cursor to an underline: .Bd -literal -offset indent -$ printf '\e033[4 q' +$ printf \[aq]\e033[4 q\[aq] .Ed .Pp If @@ -6440,6 +6830,8 @@ Set the opening sequence for the working directory notification. The sequence is terminated using the standard .Em fsl capability. +.It Em \&Sxl +Indicates that the terminal supports SIXEL. .It Em \&Sync Start (parameter is 1) or end (parameter is 2) a synchronized update. .It Em \&Tc @@ -6498,8 +6890,8 @@ and matching .Em %end or .Em %error -have three arguments: an integer time (as seconds from epoch), command number and -flags (currently not used). +have three arguments: an integer time (as seconds from epoch), command number +and flags (currently not used). For example: .Bd -literal -offset indent %begin 1363006971 2 1 @@ -6526,6 +6918,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 @@ -6547,11 +6941,17 @@ sent when the .Ar pause-after flag is set. .Ar age -is the time in milliseconds for which tmux had buffered the output before it was sent. +is the time in milliseconds for which tmux had buffered the output before it +was sent. Any subsequent arguments up until a single .Ql \&: are for future use and should be ignored. -.It Ic %layout-change Ar window-id Ar window-layout Ar window-visible-layout Ar window-flags +.It Xo Ic %layout-change +.Ar window-id +.Ar window-layout +.Ar window-visible-layout +.Ar window-flags +.Xc The layout of a window with ID .Ar window-id changed. @@ -6561,6 +6961,10 @@ The window's visible layout is .Ar window-visible-layout and the window flags are .Ar window-flags . +.It Ic %message Ar message +A message sent with the +.Ic display-message +command. .It Ic %output Ar pane-id Ar value A window pane produced output. .Ar value @@ -6569,6 +6973,14 @@ 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 %paste-buffer-deleted Ar name +Paste buffer +.Ar name +has been deleted. .It Ic %pause Ar pane-id The pane has been paused (if the .Ar pause-after @@ -6726,9 +7138,9 @@ options. .El .Sh FILES .Bl -tag -width "@SYSCONFDIR@/tmux.confXXX" -compact -.It Pa ~/.tmux.conf +.It Pa \[ti]/.tmux.conf .It Pa $XDG_CONFIG_HOME/tmux/tmux.conf -.It Pa ~/.config/tmux/tmux.conf +.It Pa \[ti]/.config/tmux/tmux.conf Default .Nm configuration file. @@ -6794,7 +7206,7 @@ to exit from it. Commands to be run when the .Nm server is started may be placed in the -.Pa ~/.tmux.conf +.Pa \[ti]/.tmux.conf configuration file. Common examples include: .Pp @@ -6821,8 +7233,8 @@ set-option -g lock-after-time 1800 Creating new key bindings: .Bd -literal -offset indent bind-key b set-option status -bind-key / command-prompt "split-window 'exec man %%'" -bind-key S command-prompt "new-window -n %1 'ssh %1'" +bind-key / command-prompt "split-window \[aq]exec man %%\[aq]" +bind-key S command-prompt "new-window -n %1 \[aq]ssh %1\[aq]" .Ed .Sh SEE ALSO .Xr pty 4 @@ -53,7 +53,7 @@ static __dead void usage(void) { fprintf(stderr, - "usage: %s [-2CDlNuvV] [-c shell-command] [-f file] [-L socket-name]\n" + "usage: %s [-2CDlNuVv] [-c shell-command] [-f file] [-L socket-name]\n" " [-S socket-path] [-T features] [command [flags]]\n", getprogname()); exit(1); @@ -391,7 +391,7 @@ main(int argc, char **argv) cfg_quiet = 0; break; case 'V': - printf("%s %s\n", getprogname(), getversion()); + printf("tmux %s\n", getversion()); exit(0); case 'l': flags |= CLIENT_LOGIN; @@ -26,6 +26,7 @@ #include <stdarg.h> #include <stdio.h> #include <termios.h> +#include <wchar.h> #ifdef HAVE_UTEMPTER #include <utempter.h> @@ -50,6 +51,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; @@ -62,6 +65,11 @@ struct screen_write_citem; struct screen_write_cline; struct screen_write_ctx; struct session; + +#ifdef ENABLE_SIXEL +struct sixel_image; +#endif + struct tty_ctx; struct tty_code; struct tty_key; @@ -79,6 +87,9 @@ struct winlink; #ifndef TMUX_TERM #define TMUX_TERM "screen" #endif +#ifndef TMUX_LOCK_CMD +#define TMUX_LOCK_CMD "lock -np" +#endif /* Minimum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 @@ -130,12 +141,14 @@ struct winlink; #define KEYC_SHIFT 0x00400000000000ULL /* Key flag bits. */ -#define KEYC_LITERAL 0x01000000000000ULL -#define KEYC_KEYPAD 0x02000000000000ULL -#define KEYC_CURSOR 0x04000000000000ULL +#define KEYC_LITERAL 0x01000000000000ULL +#define KEYC_KEYPAD 0x02000000000000ULL +#define KEYC_CURSOR 0x04000000000000ULL #define KEYC_IMPLIED_META 0x08000000000000ULL #define KEYC_BUILD_MODIFIERS 0x10000000000000ULL -#define KEYC_VI 0x20000000000000ULL +#define KEYC_VI 0x20000000000000ULL +#define KEYC_EXTENDED 0x40000000000000ULL +#define KEYC_SENT 0x80000000000000ULL /* Masks for key bits. */ #define KEYC_MASK_MODIFIERS 0x00f00000000000ULL @@ -154,7 +167,9 @@ struct winlink; #define KEYC_IS_UNICODE(key) \ (((key) & KEYC_MASK_KEY) > 0x7f && \ (((key) & KEYC_MASK_KEY) < KEYC_BASE || \ - ((key) & KEYC_MASK_KEY) >= KEYC_BASE_END)) + ((key) & KEYC_MASK_KEY) >= KEYC_BASE_END) && \ + (((key) & KEYC_MASK_KEY) < KEYC_USER || \ + ((key) & KEYC_MASK_KEY) >= KEYC_USER + KEYC_NUSER)) /* Multiple click timeout. */ #define KEYC_CLICK_TIMEOUT 300 @@ -366,6 +381,7 @@ enum tty_code_code { TTYC_ENFCS, TTYC_ENMG, TTYC_FSL, + TTYC_HLS, TTYC_HOME, TTYC_HPA, TTYC_ICH, @@ -512,6 +528,7 @@ enum tty_code_code { TTYC_KUP6, TTYC_KUP7, TTYC_MS, + TTYC_NOBR, TTYC_OL, TTYC_OP, TTYC_RECT, @@ -529,6 +546,7 @@ enum tty_code_code { TTYC_SETRGBB, TTYC_SETRGBF, TTYC_SETULC, + TTYC_SETULC1, TTYC_SGR0, TTYC_SITM, TTYC_SMACS, @@ -539,6 +557,7 @@ enum tty_code_code { TTYC_SMUL, TTYC_SMULX, TTYC_SMXX, + TTYC_SXL, TTYC_SS, TTYC_SWD, TTYC_SYNC, @@ -663,7 +682,17 @@ struct colour_palette { #define GRID_LINE_WRAPPED 0x1 #define GRID_LINE_EXTENDED 0x2 #define GRID_LINE_DEAD 0x4 +#define GRID_LINE_START_PROMPT 0x8 +#define GRID_LINE_START_OUTPUT 0x10 + +/* Grid string flags. */ +#define GRID_STRING_WITH_SEQUENCES 0x1 +#define GRID_STRING_ESCAPE_SEQUENCES 0x2 +#define GRID_STRING_TRIM_SPACES 0x4 +#define GRID_STRING_USED_ONLY 0x8 +#define GRID_STRING_EMPTY_CELLS 0x10 +/* Cell positions. */ #define CELL_INSIDE 0 #define CELL_TOPBOTTOM 1 #define CELL_LEFTRIGHT 2 @@ -678,6 +707,7 @@ struct colour_palette { #define CELL_JOIN 11 #define CELL_OUTSIDE 12 +/* Cell borders. */ #define CELL_BORDERS " xqlkmjwvtun~" #define SIMPLE_BORDERS " |-+++++++++." #define PADDED_BORDERS " " @@ -690,6 +720,7 @@ struct grid_cell { int fg; int bg; int us; + u_int link; }; /* Grid extended cell entry. */ @@ -700,11 +731,11 @@ struct grid_extd_entry { int fg; int bg; int us; + u_int link; } __packed; /* Grid cell entry. */ struct grid_cell_entry { - u_char flags; union { u_int offset; struct { @@ -714,6 +745,7 @@ struct grid_cell_entry { u_char data; } data; }; + u_char flags; } __packed; /* Grid line. */ @@ -726,6 +758,7 @@ struct grid_line { u_int extdsize; int flags; + time_t time; }; /* Entire grid of cells. */ @@ -773,11 +806,15 @@ enum style_range_type { STYLE_RANGE_NONE, STYLE_RANGE_LEFT, STYLE_RANGE_RIGHT, - STYLE_RANGE_WINDOW + STYLE_RANGE_PANE, + STYLE_RANGE_WINDOW, + STYLE_RANGE_SESSION, + STYLE_RANGE_USER }; struct style_range { enum style_range_type type; u_int argument; + char string[16]; u_int start; u_int end; /* not included */ @@ -804,10 +841,29 @@ struct style { enum style_range_type range_type; u_int range_argument; + char range_string[16]; enum style_default_type default_type; }; +#ifdef ENABLE_SIXEL +/* Image. */ +struct image { + struct screen *s; + struct sixel_image *data; + char *fallback; + + u_int px; + u_int py; + u_int sx; + u_int sy; + + TAILQ_ENTRY (image) all_entry; + TAILQ_ENTRY (image) entry; +}; +TAILQ_HEAD(images, image); +#endif + /* Cursor style. */ enum screen_cursor_style { SCREEN_CURSOR_DEFAULT, @@ -849,7 +905,13 @@ struct screen { bitstr_t *tabs; struct screen_sel *sel; +#ifdef ENABLE_SIXEL + struct images images; +#endif + struct screen_write_cline *write_list; + + struct hyperlinks *hyperlinks; }; /* Screen write context. */ @@ -861,7 +923,6 @@ struct screen_write_ctx { int flags; #define SCREEN_WRITE_SYNC 0x1 -#define SCREEN_WRITE_ZWJ 0x2 screen_write_init_ctx_cb init_ctx_cb; void *arg; @@ -1015,7 +1076,7 @@ struct window_pane { #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 -/* 0x8 unused */ +#define PANE_VISITED 0x8 /* 0x10 unused */ /* 0x20 unused */ #define PANE_INPUTOFF 0x40 @@ -1025,6 +1086,7 @@ struct window_pane { #define PANE_STATUSDRAWN 0x400 #define PANE_EMPTY 0x800 #define PANE_STYLECHANGED 0x1000 +#define PANE_UNSEENCHANGES 0x2000 int argc; char **argv; @@ -1069,7 +1131,8 @@ struct window_pane { int border_gc_set; struct grid_cell border_gc; - TAILQ_ENTRY(window_pane) entry; + TAILQ_ENTRY(window_pane) entry; /* link in list of all panes */ + TAILQ_ENTRY(window_pane) sentry; /* link in list of last visited */ RB_ENTRY(window_pane) tree_entry; }; TAILQ_HEAD(window_panes, window_pane); @@ -1090,7 +1153,7 @@ struct window { struct timeval activity_time; struct window_pane *active; - struct window_pane *last; + struct window_panes last_panes; struct window_panes panes; int lastlayout; @@ -1143,6 +1206,7 @@ struct winlink { #define WINLINK_ACTIVITY 0x2 #define WINLINK_SILENCE 0x4 #define WINLINK_ALERTFLAGS (WINLINK_BELL|WINLINK_ACTIVITY|WINLINK_SILENCE) +#define WINLINK_VISITED 0x8 RB_ENTRY(winlink) entry; TAILQ_ENTRY(winlink) wentry; @@ -1331,6 +1395,7 @@ struct tty_term { #define TERM_DECFRA 0x8 #define TERM_RGBCOLOURS 0x10 #define TERM_VT100LIKE 0x20 +#define TERM_SIXEL 0x40 int flags; LIST_ENTRY(tty_term) entry; @@ -1342,9 +1407,11 @@ struct tty { struct client *client; struct event start_timer; struct event clipboard_timer; + time_t last_requests; u_int sx; u_int sy; + /* Cell size in pixels. */ u_int xpixel; u_int ypixel; @@ -1353,6 +1420,8 @@ struct tty { enum screen_cursor_style cstyle; int ccolour; + /* Properties of the area being drawn on. */ + /* When true, the drawing area is bigger than the terminal. */ int oflag; u_int oox; u_int ooy; @@ -1360,6 +1429,8 @@ struct tty { u_int osy; int mode; + int fg; + int bg; u_int rlower; u_int rupper; @@ -1387,9 +1458,12 @@ struct tty { #define TTY_OPENED 0x20 #define TTY_OSC52QUERY 0x40 #define TTY_BLOCK 0x80 -#define TTY_HAVEDA 0x100 +#define TTY_HAVEDA 0x100 /* Primary DA. */ #define TTY_HAVEXDA 0x200 #define TTY_SYNCING 0x400 +#define TTY_HAVEDA2 0x800 /* Secondary DA. */ +#define TTY_ALL_REQUEST_FLAGS \ + (TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA) int flags; struct tty_term *term; @@ -1422,39 +1496,47 @@ struct tty_ctx { u_int num; void *ptr; + 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. */ @@ -1776,6 +1858,7 @@ struct client { #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL #define CLIENT_WINDOWSIZECHANGED 0x400000000ULL #define CLIENT_CLIPBOARDBUFFER 0x800000000ULL +#define CLIENT_BRACKETPASTING 0x1000000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ @@ -2043,10 +2126,11 @@ extern char **cfg_files; extern u_int cfg_nfiles; extern int cfg_quiet; void start_cfg(void); -int load_cfg(const char *, struct client *, struct cmdq_item *, int, - struct cmdq_item **); +int load_cfg(const char *, struct client *, struct cmdq_item *, + struct cmd_find_state *, int, struct cmdq_item **); int load_cfg_from_buffer(const void *, size_t, const char *, - struct client *, struct cmdq_item *, int, struct cmdq_item **); + struct client *, struct cmdq_item *, struct cmd_find_state *, + int, struct cmdq_item **); void printflike(1, 2) cfg_add_cause(const char *, ...); void cfg_print_causes(struct cmdq_item *); void cfg_show_causes(struct session *); @@ -2058,6 +2142,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 *); @@ -2094,6 +2179,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 *, @@ -2116,6 +2202,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 */ @@ -2134,6 +2222,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 *, int); /* options.c */ struct options *options_create(struct options *); @@ -2243,42 +2332,50 @@ void tty_update_window_offset(struct window *); void tty_update_client_offset(struct client *); void tty_raw(struct tty *, const char *); void tty_attributes(struct tty *, const struct grid_cell *, - const struct grid_cell *, struct colour_palette *); + const struct grid_cell *, struct colour_palette *, + struct hyperlinks *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); void tty_cursor(struct tty *, u_int, u_int); void tty_clipboard_query(struct tty *); void tty_putcode(struct tty *, enum tty_code_code); -void tty_putcode1(struct tty *, enum tty_code_code, int); -void tty_putcode2(struct tty *, enum tty_code_code, int, int); -void tty_putcode3(struct tty *, enum tty_code_code, int, int, int); -void tty_putcode_ptr1(struct tty *, enum tty_code_code, const void *); -void tty_putcode_ptr2(struct tty *, enum tty_code_code, const void *, - const void *); +void tty_putcode_i(struct tty *, enum tty_code_code, int); +void tty_putcode_ii(struct tty *, enum tty_code_code, int, int); +void tty_putcode_iii(struct tty *, enum tty_code_code, int, int, int); +void tty_putcode_s(struct tty *, enum tty_code_code, const char *); +void tty_putcode_ss(struct tty *, enum tty_code_code, const char *, + const char *); 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); void tty_start_tty(struct tty *); void tty_send_requests(struct tty *); +void tty_repeat_requests(struct tty *); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *); + +#ifdef ENABLE_SIXEL +void tty_draw_images(struct client *, struct window_pane *, struct screen *); +#endif + void tty_sync_start(struct tty *); void tty_sync_end(struct tty *); 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 *); @@ -2302,6 +2399,11 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *); void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); + +#ifdef ENABLE_SIXEL +void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); +#endif + void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *); @@ -2318,15 +2420,15 @@ int tty_term_read_list(const char *, int, char ***, u_int *, void tty_term_free_list(char **, u_int); int tty_term_has(struct tty_term *, enum tty_code_code); const char *tty_term_string(struct tty_term *, enum tty_code_code); -const char *tty_term_string1(struct tty_term *, enum tty_code_code, int); -const char *tty_term_string2(struct tty_term *, enum tty_code_code, int, +const char *tty_term_string_i(struct tty_term *, enum tty_code_code, int); +const char *tty_term_string_ii(struct tty_term *, enum tty_code_code, int, int); -const char *tty_term_string3(struct tty_term *, enum tty_code_code, int, +const char *tty_term_string_iii(struct tty_term *, enum tty_code_code, int, int, int); -const char *tty_term_ptr1(struct tty_term *, enum tty_code_code, - const void *); -const char *tty_term_ptr2(struct tty_term *, enum tty_code_code, - const void *, const void *); +const char *tty_term_string_s(struct tty_term *, enum tty_code_code, + const char *); +const char *tty_term_string_ss(struct tty_term *, enum tty_code_code, + const char *, const char *); int tty_term_number(struct tty_term *, enum tty_code_code); int tty_term_flag(struct tty_term *, enum tty_code_code); const char *tty_term_describe(struct tty_term *, enum tty_code_code); @@ -2351,7 +2453,7 @@ void tty_keys_free(struct tty *); int tty_keys_next(struct tty *); /* arguments.c */ -void args_set(struct args *, u_char, struct args_value *); +void args_set(struct args *, u_char, struct args_value *, int); struct args *args_create(void); struct args *args_parse(const struct args_parse *, struct args_value *, u_int, char **); @@ -2383,10 +2485,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 *, @@ -2478,7 +2586,8 @@ struct cmd_parse_result *cmd_parse_from_arguments(struct args_value *, u_int, struct cmdq_state *cmdq_new_state(struct cmd_find_state *, struct key_event *, int); struct cmdq_state *cmdq_link_state(struct cmdq_state *); -struct cmdq_state *cmdq_copy_state(struct cmdq_state *); +struct cmdq_state *cmdq_copy_state(struct cmdq_state *, + struct cmd_find_state *); void cmdq_free_state(struct cmdq_state *); void printflike(3, 4) cmdq_add_format(struct cmdq_state *, const char *, const char *, ...); @@ -2508,6 +2617,7 @@ u_int cmdq_next(struct client *); struct cmdq_item *cmdq_running(struct client *); void cmdq_guard(struct cmdq_item *, const char *, int); void printflike(2, 3) cmdq_print(struct cmdq_item *, const char *, ...); +void cmdq_print_data(struct cmdq_item *, int, struct evbuffer *); void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...); /* cmd-wait-for.c */ @@ -2562,7 +2672,9 @@ void file_print_buffer(struct client *, void *, size_t); void printflike(2, 3) file_error(struct client *, const char *, ...); void file_write(struct client *, const char *, int, const void *, size_t, client_file_cb, void *); -void file_read(struct client *, const char *, client_file_cb, void *); +struct client_file *file_read(struct client *, const char *, client_file_cb, + void *); +void file_cancel(struct client_file *); void file_push(struct client_file *); int file_write_left(struct client_files *); void file_write_open(struct client_files *, struct tmuxpeer *, @@ -2574,12 +2686,14 @@ void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *, void file_write_ready(struct client_files *, struct imsg *); void file_read_data(struct client_files *, struct imsg *); void file_read_done(struct client_files *, struct imsg *); +void file_read_cancel(struct client_files *, struct imsg *); /* server.c */ 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); @@ -2624,6 +2738,7 @@ struct client_window *server_client_add_client_window(struct client *, u_int); struct window_pane *server_client_get_pane(struct client *); void server_client_set_pane(struct client *, struct window_pane *); void server_client_remove_pane(struct window_pane *); +void server_client_print(struct client *, int, struct evbuffer *); /* server-fn.c */ void server_redraw_client(struct client *); @@ -2716,6 +2831,7 @@ int colour_fromstring(const char *s); int colour_256toRGB(int); int colour_256to16(int); int colour_byname(const char *); +int colour_parseX11(const char *); void colour_palette_init(struct colour_palette *); void colour_palette_clear(struct colour_palette *); void colour_palette_free(struct colour_palette *); @@ -2754,7 +2870,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, struct screen *); void grid_duplicate_lines(struct grid *, u_int, struct grid *, u_int, u_int); void grid_reflow(struct grid *, u_int); @@ -2827,12 +2943,14 @@ void screen_write_putc(struct screen_write_ctx *, const struct grid_cell *, u_char); void screen_write_fast_copy(struct screen_write_ctx *, struct screen *, u_int, u_int, u_int, u_int); -void screen_write_hline(struct screen_write_ctx *, u_int, int, int); +void screen_write_hline(struct screen_write_ctx *, u_int, int, int, + enum box_lines, const struct grid_cell *); void screen_write_vline(struct screen_write_ctx *, u_int, int, int); void screen_write_menu(struct screen_write_ctx *, struct menu *, int, + enum box_lines, const struct grid_cell *, const struct grid_cell *, const struct grid_cell *); -void screen_write_box(struct screen_write_ctx *, u_int, u_int, int, - const struct grid_cell *, const char *); +void screen_write_box(struct screen_write_ctx *, u_int, u_int, + enum box_lines, const struct grid_cell *, const char *); void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, u_int); void screen_write_backspace(struct screen_write_ctx *); @@ -2867,8 +2985,14 @@ 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); +#ifdef ENABLE_SIXEL +void screen_write_sixelimage(struct screen_write_ctx *, + struct sixel_image *, u_int); +#endif void screen_write_alternateon(struct screen_write_ctx *, struct grid_cell *, int); void screen_write_alternateoff(struct screen_write_ctx *, @@ -2883,6 +3007,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 *); @@ -2971,6 +3096,7 @@ int window_pane_key(struct window_pane *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); int window_pane_visible(struct window_pane *); +int window_pane_exited(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, int); const char *window_printable_flags(struct winlink *, int); @@ -2978,6 +3104,10 @@ struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); struct window_pane *window_pane_find_right(struct window_pane *); +void window_pane_stack_push(struct window_panes *, + struct window_pane *); +void window_pane_stack_remove(struct window_panes *, + struct window_pane *); void window_set_name(struct window *, const char *); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); @@ -2990,6 +3120,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 *); @@ -3111,6 +3242,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 *); @@ -3141,6 +3273,8 @@ 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 *); +void control_notify_paste_buffer_deleted(const char *); /* session.c */ extern struct sessions sessions; @@ -3183,6 +3317,8 @@ u_int session_group_attached_count(struct session_group *); void session_renumber_windows(struct session *); /* utf8.c */ +enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *); +int utf8_in_table(wchar_t, const wchar_t *, u_int); utf8_char utf8_build_one(u_char); enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *); void utf8_to_data(utf8_char, struct utf8_data *); @@ -3209,6 +3345,16 @@ char *osdep_get_name(int, char *); char *osdep_get_cwd(int); struct event_base *osdep_event_init(void); +/* utf8-combined.c */ +int utf8_has_zwj(const struct utf8_data *); +int utf8_is_zwj(const struct utf8_data *); +int utf8_is_vs(const struct utf8_data *); +int utf8_is_modifier(const struct utf8_data *); + +/* procname.c */ +char *get_proc_name(int, char *); +char *get_proc_cwd(int); + /* log.c */ void log_add_level(void); int log_get_level(void); @@ -3231,11 +3377,13 @@ void menu_add_item(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, struct cmd_find_state *); void menu_free(struct menu *); -struct menu_data *menu_prepare(struct menu *, int, struct cmdq_item *, u_int, - u_int, struct client *, struct cmd_find_state *, +struct menu_data *menu_prepare(struct menu *, int, int, struct cmdq_item *, + u_int, u_int, struct client *, enum box_lines, const char *, + const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); -int menu_display(struct menu *, int, struct cmdq_item *, u_int, - u_int, struct client *, struct cmd_find_state *, +int menu_display(struct menu *, int, int, struct cmdq_item *, + u_int, u_int, struct client *, enum box_lines, const char *, + const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); void menu_check_cb(struct client *, void *, u_int, u_int, u_int, @@ -3251,11 +3399,11 @@ int menu_key_cb(struct client *, void *, struct key_event *); #define POPUP_INTERNAL 0x4 typedef void (*popup_close_cb)(int, void *); typedef void (*popup_finish_edit_cb)(char *, size_t, void *); -int popup_display(int, int, struct cmdq_item *, u_int, u_int, - u_int, u_int, struct environ *, const char *, int, char **, - const char *, const char *, struct client *, - struct session *, const char *, const char *, - popup_close_cb, void *); +int popup_display(int, enum box_lines, struct cmdq_item *, u_int, + u_int, u_int, u_int, struct environ *, const char *, int, + char **, const char *, const char *, struct client *, + struct session *, const char *, const char *, + popup_close_cb, void *); int popup_editor(struct client *, const char *, size_t, popup_finish_edit_cb, void *); @@ -3277,6 +3425,27 @@ struct window_pane *spawn_pane(struct spawn_context *, char **); /* regsub.c */ char *regsub(const char *, const char *, const char *, int); +#ifdef ENABLE_SIXEL +/* image.c */ +int image_free_all(struct screen *); +struct image *image_store(struct screen *, struct sixel_image *); +int image_check_line(struct screen *, u_int, u_int); +int image_check_area(struct screen *, u_int, u_int, u_int, u_int); +int image_scroll_up(struct screen *, u_int); + +/* image-sixel.c */ +#define SIXEL_COLOUR_REGISTERS 1024 +struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int); +void sixel_free(struct sixel_image *); +void sixel_log(struct sixel_image *); +void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *); +struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int, + u_int, u_int, u_int, int); +char *sixel_print(struct sixel_image *, struct sixel_image *, + size_t *); +struct screen *sixel_to_screen(struct sixel_image *); +#endif + /* server-acl.c */ void server_acl_init(void); struct server_acl_user *server_acl_user_find(uid_t); @@ -3288,4 +3457,13 @@ 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 */ @@ -155,8 +155,8 @@ static const struct utf8_data tty_acs_rounded_borders_list[] = { { "\342\225\257", 0, 3, 1 }, /* U+256F */ { "\342\224\263", 0, 3, 1 }, /* U+2533 */ { "\342\224\273", 0, 3, 1 }, /* U+253B */ - { "\342\224\243", 0, 3, 1 }, /* U+2523 */ - { "\342\224\253", 0, 3, 1 }, /* U+252B */ + { "\342\224\234", 0, 3, 1 }, /* U+2524 */ + { "\342\224\244", 0, 3, 1 }, /* U+251C */ { "\342\225\213", 0, 3, 1 }, /* U+254B */ { "\302\267", 0, 2, 1 } /* U+00B7 */ }; diff --git a/tty-features.c b/tty-features.c index 2848b4d..9bd0d84 100644 --- a/tty-features.c +++ b/tty-features.c @@ -21,6 +21,12 @@ #include <stdlib.h> #include <string.h> +#if defined(HAVE_CURSES_H) +#include <curses.h> +#elif defined(HAVE_NCURSES_H) +#include <ncurses.h> +#endif + #include "tmux.h" /* @@ -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,9 +152,10 @@ 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", + "Setulc1=\\E[58::5::%p1%dm", "ol=\\E[59m", NULL }; @@ -144,7 +166,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 +178,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 +190,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 +202,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 +214,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,8 +225,8 @@ static const struct tty_feature tty_feature_strikethrough = { }; /* Terminal supports synchronized updates. */ -static const char *tty_feature_sync_capabilities[] = { - "Sync=\\EP=%p1%ds\\E\\\\", +static const char *const tty_feature_sync_capabilities[] = { + "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;", NULL }; static const struct tty_feature tty_feature_sync = { @@ -214,7 +236,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 +248,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 +262,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,21 +272,109 @@ 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 +}; + +/* Terminal has sixel capability. */ +static const char *const tty_feature_sixel_capabilities[] = { + "Sxl", + NULL +}; +static const struct tty_feature tty_feature_sixel = { + "sixel", + tty_feature_sixel_capabilities, + TERM_SIXEL +}; + /* 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, &tty_feature_overline, &tty_feature_rectfill, &tty_feature_rgb, + &tty_feature_sixel, &tty_feature_strikethrough, &tty_feature_sync, &tty_feature_title, @@ -323,9 +433,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 +466,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 +479,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,osc7" + ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" }, { .name = "XTerm", /* @@ -55,8 +55,11 @@ static int tty_keys_clipboard(struct tty *, const char *, size_t, size_t *); static int tty_keys_device_attributes(struct tty *, const char *, size_t, size_t *); +static int tty_keys_device_attributes2(struct tty *, const char *, size_t, + size_t *); static int tty_keys_extended_device_attributes(struct tty *, const char *, size_t, size_t *); +static int tty_keys_colours(struct tty *, const char *, size_t, size_t *); /* A key tree entry. */ struct tty_key { @@ -126,7 +129,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 +142,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 +153,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 +190,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 +203,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 }, @@ -215,6 +211,9 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { /* Paste keys. */ { "\033[200~", KEYC_PASTE_START }, { "\033[201~", KEYC_PASTE_END }, + + /* Extended keys. */ + { "\033[1;5Z", '\011'|KEYC_CTRL|KEYC_SHIFT }, }; /* Default xterm keys. */ @@ -688,7 +687,7 @@ tty_keys_next(struct tty *tty) goto partial_key; } - /* Is this a device attributes response? */ + /* Is this a primary device attributes response? */ switch (tty_keys_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ key = KEYC_UNKNOWN; @@ -699,6 +698,17 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this a secondary device attributes response? */ + switch (tty_keys_device_attributes2(tty, buf, len, &size)) { + case 0: /* yes */ + key = KEYC_UNKNOWN; + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + /* Is this an extended device attributes response? */ switch (tty_keys_extended_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ @@ -710,6 +720,17 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this a colours response? */ + switch (tty_keys_colours(tty, buf, len, &size)) { + case 0: /* yes */ + key = KEYC_UNKNOWN; + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + /* Is this a mouse key press? */ switch (tty_keys_mouse(tty, buf, len, &size, &m)) { case 0: /* yes */ @@ -798,6 +819,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 +962,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 */ } /* @@ -1003,7 +1008,8 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, /* * Handle mouse key input. Returns 0 for success, -1 for failure, 1 for partial - * (probably a mouse sequence but need more data). + * (probably a mouse sequence but need more data), -2 if an invalid mouse + * sequence. */ static int tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, @@ -1064,7 +1070,7 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, if (b < MOUSE_PARAM_BTN_OFF || x < MOUSE_PARAM_POS_OFF || y < MOUSE_PARAM_POS_OFF) - return (-1); + return (-2); b -= MOUSE_PARAM_BTN_OFF; x -= MOUSE_PARAM_POS_OFF; y -= MOUSE_PARAM_POS_OFF; @@ -1106,7 +1112,7 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, /* Check and return the mouse input. */ if (x < 1 || y < 1) - return (-1); + return (-2); x--; y--; b = sgr_b; @@ -1154,7 +1160,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; struct window_pane *wp; - size_t end, terminator, needed; + size_t end, terminator = 0, needed; char *copy, *out; int outlen; u_int i; @@ -1255,7 +1261,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) } /* - * Handle secondary device attributes input. Returns 0 for success, -1 for + * Handle primary device attributes input. Returns 0 for success, -1 for * failure, 1 for partial. */ static int @@ -1263,17 +1269,100 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; + int *features = &c->term_features; u_int i, n = 0; - char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; + char tmp[128], *endptr, p[32] = { 0 }, *cp, *next; *size = 0; if (tty->flags & TTY_HAVEDA) return (-1); + /* First three bytes are always \033[?. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != '[') + return (-1); + if (len == 2) + return (1); + if (buf[2] != '?') + return (-1); + if (len == 3) + return (1); + + /* Copy the rest up to a c. */ + for (i = 0; i < (sizeof tmp); i++) { + if (3 + i == len) + return (1); + if (buf[3 + i] == 'c') + break; + tmp[i] = buf[3 + i]; + } + if (i == (sizeof tmp)) + return (-1); + tmp[i] = '\0'; + *size = 4 + i; + + /* Convert all arguments to numbers. */ + cp = tmp; + while ((next = strsep(&cp, ";")) != NULL) { + p[n] = strtoul(next, &endptr, 10); + if (*endptr != '\0') + p[n] = 0; + if (++n == nitems(p)) + break; + } + /* - * First three bytes are always \033[>. Some older Terminal.app - * versions respond as for DA (\033[?) so accept and ignore that. + * Add terminal features. Hardware level 5 does not offer SIXEL but + * some terminal emulators report it anyway and it does not harm + * to check it here. + * + * DECSLRM and DECFRA should be supported by level 5 as well as level + * 4, but VTE has rather ruined it by advertising level 5 despite not + * supporting them. */ + switch (p[0]) { + case 64: /* level 4 */ + tty_add_features(features, "margins,rectfill", ","); + /* FALLTHROUGH */ + case 62: /* level 2 */ + case 63: /* level 3 */ + case 65: /* level 5 */ + for (i = 1; i < n; i++) { + log_debug("%s: DA feature: %d", c->name, p[i]); + if (p[i] == 4) + tty_add_features(features, "sixel", ","); + } + break; + } + log_debug("%s: received primary DA %.*s", c->name, (int)*size, buf); + + tty_update_features(tty); + tty->flags |= TTY_HAVEDA; + + return (0); +} + +/* + * Handle secondary device attributes input. Returns 0 for success, -1 for + * failure, 1 for partial. + */ +static int +tty_keys_device_attributes2(struct tty *tty, const char *buf, size_t len, + size_t *size) +{ + struct client *c = tty->client; + int *features = &c->term_features; + u_int i, n = 0; + char tmp[128], *endptr, p[32] = { 0 }, *cp, *next; + + *size = 0; + if (tty->flags & TTY_HAVEDA2) + return (-1); + + /* First three bytes are always \033[>. */ if (buf[0] != '\033') return (-1); if (len == 1) @@ -1282,56 +1371,59 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, return (-1); if (len == 2) return (1); - if (buf[2] != '>' && buf[2] != '?') + if (buf[2] != '>') return (-1); if (len == 3) return (1); - /* Copy the rest up to a 'c'. */ - for (i = 0; i < (sizeof tmp) - 1; i++) { + /* Copy the rest up to a c. */ + for (i = 0; i < (sizeof tmp); i++) { if (3 + i == len) return (1); if (buf[3 + i] == 'c') break; tmp[i] = buf[3 + i]; } - if (i == (sizeof tmp) - 1) + if (i == (sizeof tmp)) return (-1); tmp[i] = '\0'; *size = 4 + i; - /* Ignore DA response. */ - if (buf[2] == '?') - return (0); - /* Convert all arguments to numbers. */ cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); if (*endptr != '\0') p[n] = 0; - n++; + if (++n == nitems(p)) + break; } - /* Add terminal features. */ + /* + * Add terminal features. We add DECSLRM and DECFRA for some + * identification codes here, notably 64 will catch VT520, even though + * we can't use level 5 from DA because of VTE. + */ switch (p[0]) { case 41: /* VT420 */ - tty_add_features(&c->term_features, "margins,rectfill", ","); + case 61: /* VT510 */ + case 64: /* VT520 */ + tty_add_features(features, "margins,rectfill", ","); break; case 'M': /* mintty */ - tty_default_features(&c->term_features, "mintty", 0); + tty_default_features(features, "mintty", 0); break; case 'T': /* tmux */ - tty_default_features(&c->term_features, "tmux", 0); + tty_default_features(features, "tmux", 0); break; case 'U': /* rxvt-unicode */ - tty_default_features(&c->term_features, "rxvt-unicode", 0); + tty_default_features(features, "rxvt-unicode", 0); break; } log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf); tty_update_features(tty); - tty->flags |= TTY_HAVEDA; + tty->flags |= TTY_HAVEDA2; return (0); } @@ -1345,6 +1437,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; + int *features = &c->term_features; u_int i; char tmp[128]; @@ -1370,7 +1463,7 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, if (len == 4) return (1); - /* Copy the rest up to a '\033\\'. */ + /* Copy the rest up to \033\. */ for (i = 0; i < (sizeof tmp) - 1; i++) { if (4 + i == len) return (1); @@ -1385,13 +1478,13 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, /* Add terminal features. */ if (strncmp(tmp, "iTerm2 ", 7) == 0) - tty_default_features(&c->term_features, "iTerm2", 0); + tty_default_features(features, "iTerm2", 0); else if (strncmp(tmp, "tmux ", 5) == 0) - tty_default_features(&c->term_features, "tmux", 0); + tty_default_features(features, "tmux", 0); else if (strncmp(tmp, "XTerm(", 6) == 0) - tty_default_features(&c->term_features, "XTerm", 0); + tty_default_features(features, "XTerm", 0); else if (strncmp(tmp, "mintty ", 7) == 0) - tty_default_features(&c->term_features, "mintty", 0); + tty_default_features(features, "mintty", 0); log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); free(c->term_type); @@ -1402,3 +1495,69 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf, return (0); } + +/* + * Handle foreground or background input. Returns 0 for success, -1 for + * failure, 1 for partial. + */ +static int +tty_keys_colours(struct tty *tty, const char *buf, size_t len, size_t *size) +{ + struct client *c = tty->client; + u_int i; + char tmp[128]; + int n; + + *size = 0; + + /* First four bytes are always \033]1 and 0 or 1 and ;. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != ']') + return (-1); + if (len == 2) + return (1); + if (buf[2] != '1') + return (-1); + if (len == 3) + return (1); + if (buf[3] != '0' && buf[3] != '1') + return (-1); + if (len == 4) + return (1); + if (buf[4] != ';') + return (-1); + if (len == 5) + return (1); + + /* Copy the rest up to \033\ or \007. */ + for (i = 0; i < (sizeof tmp) - 1; i++) { + if (5 + i == len) + return (1); + if (buf[5 + i - 1] == '\033' && buf[5 + i] == '\\') + break; + if (buf[5 + i] == '\007') + break; + tmp[i] = buf[5 + i]; + } + if (i == (sizeof tmp) - 1) + return (-1); + if (tmp[i - 1] == '\033') + tmp[i - 1] = '\0'; + else + tmp[i] = '\0'; + *size = 6 + i; + + n = colour_parseX11(tmp); + if (n != -1 && buf[3] == '0') { + log_debug("%s: foreground is %s", c->name, colour_tostring(n)); + tty->fg = n; + } else if (n != -1) { + log_debug("%s: background is %s", c->name, colour_tostring(n)); + tty->bg = n; + } + + return (0); +} @@ -103,6 +103,7 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, + [TTYC_HLS] = { TTYCODE_STRING, "Hls" }, [TTYC_HOME] = { TTYCODE_STRING, "home" }, [TTYC_HPA] = { TTYCODE_STRING, "hpa" }, [TTYC_ICH1] = { TTYCODE_STRING, "ich1" }, @@ -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" }, @@ -265,7 +267,9 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_SETRGBB] = { TTYCODE_STRING, "setrgbb" }, [TTYC_SETRGBF] = { TTYCODE_STRING, "setrgbf" }, [TTYC_SETULC] = { TTYCODE_STRING, "Setulc" }, + [TTYC_SETULC1] = { TTYCODE_STRING, "Setulc1" }, [TTYC_SE] = { TTYCODE_STRING, "Se" }, + [TTYC_SXL] = { TTYCODE_FLAG, "Sxl" }, [TTYC_SGR0] = { TTYCODE_STRING, "sgr0" }, [TTYC_SITM] = { TTYCODE_STRING, "sitm" }, [TTYC_SMACS] = { TTYCODE_STRING, "smacs" }, @@ -454,6 +458,9 @@ tty_term_apply_overrides(struct tty_term *term) a = options_array_next(a); } + /* Log the SIXEL flag. */ + log_debug("SIXEL flag is %d", !!(term->flags & TERM_SIXEL)); + /* Update the RGB flag if the terminal has RGB colours. */ if (tty_term_has(term, TTYC_SETRGBF) && tty_term_has(term, TTYC_SETRGBB)) @@ -712,7 +719,7 @@ tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps, s = tmp; break; case TTYCODE_FLAG: - n = tigetflag((char *) ent->name); + n = tigetflag((char *)ent->name); if (n == -1) continue; if (n) @@ -720,6 +727,8 @@ tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps, else s = "0"; break; + default: + fatalx("unknown capability type"); } *caps = xreallocarray(*caps, (*ncaps) + 1, sizeof **caps); xasprintf(&(*caps)[*ncaps], "%s=%s", ent->name, s); @@ -760,35 +769,100 @@ tty_term_string(struct tty_term *term, enum tty_code_code code) } const char * -tty_term_string1(struct tty_term *term, enum tty_code_code code, int a) +tty_term_string_i(struct tty_term *term, enum tty_code_code code, int a) { - return (tparm((char *) tty_term_string(term, code), a, 0, 0, 0, 0, 0, 0, 0, 0)); + const char *x = tty_term_string(term, code), *s; + +#if defined(HAVE_TIPARM_S) + s = tiparm_s(1, 0, x, a); +#elif defined(HAVE_TIPARM) + s = tiparm(x, a); +#else + s = tparm((char *)x, a, 0, 0, 0, 0, 0, 0, 0, 0); +#endif + if (s == NULL) { + log_debug("could not expand %s", tty_term_codes[code].name); + return (""); + } + return (s); } const char * -tty_term_string2(struct tty_term *term, enum tty_code_code code, int a, int b) +tty_term_string_ii(struct tty_term *term, enum tty_code_code code, int a, int b) { - return (tparm((char *) tty_term_string(term, code), a, b, 0, 0, 0, 0, 0, 0, 0)); + const char *x = tty_term_string(term, code), *s; + +#if defined(HAVE_TIPARM_S) + s = tiparm_s(2, 0, x, a, b); +#elif defined(HAVE_TIPARM) + s = tiparm(x, a, b); +#else + s = tparm((char *)x, a, b, 0, 0, 0, 0, 0, 0, 0); +#endif + if (s == NULL) { + log_debug("could not expand %s", tty_term_codes[code].name); + return (""); + } + return (s); } const char * -tty_term_string3(struct tty_term *term, enum tty_code_code code, int a, int b, - int c) +tty_term_string_iii(struct tty_term *term, enum tty_code_code code, int a, + int b, int c) { - return (tparm((char *) tty_term_string(term, code), a, b, c, 0, 0, 0, 0, 0, 0)); + const char *x = tty_term_string(term, code), *s; + +#if defined(HAVE_TIPARM_S) + s = tiparm_s(3, 0, x, a, b, c); +#elif defined(HAVE_TIPARM) + s = tiparm(x, a, b, c); +#else + s = tparm((char *)x, a, b, c, 0, 0, 0, 0, 0, 0); +#endif + if (s == NULL) { + log_debug("could not expand %s", tty_term_codes[code].name); + return (""); + } + return (s); } const char * -tty_term_ptr1(struct tty_term *term, enum tty_code_code code, const void *a) +tty_term_string_s(struct tty_term *term, enum tty_code_code code, const char *a) { - return (tparm((char *) tty_term_string(term, code), (long)a, 0, 0, 0, 0, 0, 0, 0, 0)); + const char *x = tty_term_string(term, code), *s; + +#if defined(HAVE_TIPARM_S) + s = tiparm_s(1, 1, x, a); +#elif defined(HAVE_TIPARM) + s = tiparm(x, a); +#else + s = tparm((char *)x, (long)a, 0, 0, 0, 0, 0, 0, 0, 0); +#endif + if (s == NULL) { + log_debug("could not expand %s", tty_term_codes[code].name); + return (""); + } + return (s); } const char * -tty_term_ptr2(struct tty_term *term, enum tty_code_code code, const void *a, - const void *b) +tty_term_string_ss(struct tty_term *term, enum tty_code_code code, + const char *a, const char *b) { - return (tparm((char *) tty_term_string(term, code), (long)a, (long)b, 0, 0, 0, 0, 0, 0, 0)); + const char *x = tty_term_string(term, code), *s; + +#if defined(HAVE_TIPARM_S) + s = tiparm_s(2, 3, x, a, b); +#elif defined(HAVE_TIPARM) + s = tiparm(x, a, b); +#else + s = tparm((char *)x, (long)a, (long)b, 0, 0, 0, 0, 0, 0, 0); +#endif + if (s == NULL) { + log_debug("could not expand %s", tty_term_codes[code].name); + return (""); + } + return (s); } int @@ -34,8 +34,6 @@ static int tty_log_fd = -1; -static int tty_client_ready(struct client *); - static void tty_set_italics(struct tty *); static int tty_try_colour(struct tty *, int, const char *); static void tty_force_cursor_colour(struct tty *, int); @@ -69,11 +67,16 @@ 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 *); +#ifdef ENABLE_SIXEL +static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), + struct client *, struct tty_ctx *); +#endif + #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) #define tty_full_width(tty, ctx) \ @@ -84,6 +87,7 @@ static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, #define TTY_BLOCK_STOP(tty) (1 + ((tty)->sx * (tty)->sy) / 8) #define TTY_QUERY_TIMEOUT 5 +#define TTY_REQUEST_LIMIT 30 void tty_create_log(void) @@ -108,6 +112,7 @@ tty_init(struct tty *tty, struct client *c) tty->cstyle = SCREEN_CURSOR_DEFAULT; tty->ccolour = -1; + tty->fg = tty->bg = -1; if (tcgetattr(c->fd, &tty->tio) != 0) return (-1); @@ -286,7 +291,6 @@ tty_open(struct tty *tty, char **cause) evtimer_set(&tty->timer, tty_timer_callback, tty); tty_start_tty(tty); - tty_keys_build(tty); return (0); @@ -299,9 +303,9 @@ tty_start_timer_callback(__unused int fd, __unused short events, void *data) struct client *c = tty->client; log_debug("%s: start timer fired", c->name); - if ((tty->flags & (TTY_HAVEDA|TTY_HAVEXDA)) == 0) + if ((tty->flags & (TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA)) == 0) tty_update_features(tty); - tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); + tty->flags |= TTY_ALL_REQUEST_FLAGS; } void @@ -341,6 +345,8 @@ tty_start_tty(struct tty *tty) tty_puts(tty, "\033[?1000l\033[?1002l\033[?1003l"); tty_puts(tty, "\033[?1006l\033[?1005l"); } + if (tty_term_has(tty->term, TTYC_ENBP)) + tty_putcode(tty, TTYC_ENBP); evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); @@ -363,12 +369,35 @@ tty_send_requests(struct tty *tty) return; if (tty->term->flags & TERM_VT100LIKE) { - if (~tty->flags & TTY_HAVEDA) + if (~tty->term->flags & TTY_HAVEDA) + tty_puts(tty, "\033[c"); + if (~tty->flags & TTY_HAVEDA2) tty_puts(tty, "\033[>c"); if (~tty->flags & TTY_HAVEXDA) tty_puts(tty, "\033[>q"); + tty_puts(tty, "\033]10;?\033\\"); + tty_puts(tty, "\033]11;?\033\\"); } else - tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); + tty->flags |= TTY_ALL_REQUEST_FLAGS; + tty->last_requests = time (NULL); +} + +void +tty_repeat_requests(struct tty *tty) +{ + time_t t = time (NULL); + + if (~tty->flags & TTY_STARTED) + return; + + if (t - tty->last_requests <= TTY_REQUEST_LIMIT) + return; + tty->last_requests = t; + + if (tty->term->flags & TERM_VT100LIKE) { + tty_puts(tty, "\033]10;?\033\\"); + tty_puts(tty, "\033]11;?\033\\"); + } } void @@ -399,7 +428,7 @@ tty_stop_tty(struct tty *tty) if (tcsetattr(c->fd, TCSANOW, &tty->tio) == -1) return; - tty_raw(tty, tty_term_string2(tty->term, TTYC_CSR, 0, ws.ws_row - 1)); + tty_raw(tty, tty_term_string_ii(tty->term, TTYC_CSR, 0, ws.ws_row - 1)); if (tty_acs_needed(tty)) tty_raw(tty, tty_term_string(tty->term, TTYC_RMACS)); tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0)); @@ -409,10 +438,8 @@ tty_stop_tty(struct tty *tty) if (tty_term_has(tty->term, TTYC_SE)) tty_raw(tty, tty_term_string(tty->term, TTYC_SE)); else if (tty_term_has(tty->term, TTYC_SS)) - tty_raw(tty, tty_term_string1(tty->term, TTYC_SS, 0)); + tty_raw(tty, tty_term_string_i(tty->term, TTYC_SS, 0)); } - if (tty->mode & MODE_BRACKETPASTE) - tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (tty->ccolour != -1) tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); @@ -421,6 +448,8 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, "\033[?1000l\033[?1002l\033[?1003l"); tty_raw(tty, "\033[?1006l\033[?1005l"); } + if (tty_term_has(tty->term, TTYC_DSBP)) + tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (tty->term->flags & TERM_VT100LIKE) tty_raw(tty, "\033[?7727l"); @@ -476,6 +505,14 @@ tty_update_features(struct tty *tty) tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS)); if (tty->term->flags & TERM_VT100LIKE) tty_puts(tty, "\033[?7727h"); + + /* + * Features might have changed since the first draw during attach. For + * example, this happens when DA responses are received. + */ + server_redraw_client(c); + + tty_invalidate(tty); } void @@ -506,42 +543,42 @@ tty_putcode(struct tty *tty, enum tty_code_code code) } void -tty_putcode1(struct tty *tty, enum tty_code_code code, int a) +tty_putcode_i(struct tty *tty, enum tty_code_code code, int a) { if (a < 0) return; - tty_puts(tty, tty_term_string1(tty->term, code, a)); + tty_puts(tty, tty_term_string_i(tty->term, code, a)); } void -tty_putcode2(struct tty *tty, enum tty_code_code code, int a, int b) +tty_putcode_ii(struct tty *tty, enum tty_code_code code, int a, int b) { if (a < 0 || b < 0) return; - tty_puts(tty, tty_term_string2(tty->term, code, a, b)); + tty_puts(tty, tty_term_string_ii(tty->term, code, a, b)); } void -tty_putcode3(struct tty *tty, enum tty_code_code code, int a, int b, int c) +tty_putcode_iii(struct tty *tty, enum tty_code_code code, int a, int b, int c) { if (a < 0 || b < 0 || c < 0) return; - tty_puts(tty, tty_term_string3(tty->term, code, a, b, c)); + tty_puts(tty, tty_term_string_iii(tty->term, code, a, b, c)); } void -tty_putcode_ptr1(struct tty *tty, enum tty_code_code code, const void *a) +tty_putcode_s(struct tty *tty, enum tty_code_code code, const char *a) { if (a != NULL) - tty_puts(tty, tty_term_ptr1(tty->term, code, a)); + tty_puts(tty, tty_term_string_s(tty->term, code, a)); } void -tty_putcode_ptr2(struct tty *tty, enum tty_code_code code, const void *a, - const void *b) +tty_putcode_ss(struct tty *tty, enum tty_code_code code, const char *a, + const char *b) { if (a != NULL && b != NULL) - tty_puts(tty, tty_term_ptr2(tty->term, code, a, b)); + tty_puts(tty, tty_term_string_ss(tty->term, code, a, b)); } static void @@ -603,7 +640,7 @@ tty_putc(struct tty *tty, u_char ch) * it works on sensible terminals as well. */ if (tty->term->flags & TERM_NOAM) - tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx); + tty_putcode_ii(tty, TTYC_CUP, tty->cy, tty->cx); } else tty->cx++; } @@ -671,7 +708,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); @@ -682,7 +719,7 @@ tty_force_cursor_colour(struct tty *tty, int c) else { colour_split_rgb(c, &r, &g, &b); xsnprintf(s, sizeof s, "rgb:%02hhx/%02hhx/%02hhx", r, g, b); - tty_putcode_ptr1(tty, TTYC_CS, s); + tty_putcode_s(tty, TTYC_CS, s); } tty->ccolour = c; } @@ -743,7 +780,7 @@ tty_update_cursor(struct tty *tty, int mode, struct screen *s) if (tty_term_has(tty->term, TTYC_SE)) tty_putcode(tty, TTYC_SE); else - tty_putcode1(tty, TTYC_SS, 0); + tty_putcode_i(tty, TTYC_SS, 0); } if (cmode & (MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)) tty_putcode(tty, TTYC_CVVIS); @@ -751,27 +788,27 @@ tty_update_cursor(struct tty *tty, int mode, struct screen *s) case SCREEN_CURSOR_BLOCK: if (tty_term_has(tty->term, TTYC_SS)) { if (cmode & MODE_CURSOR_BLINKING) - tty_putcode1(tty, TTYC_SS, 1); + tty_putcode_i(tty, TTYC_SS, 1); else - tty_putcode1(tty, TTYC_SS, 2); + tty_putcode_i(tty, TTYC_SS, 2); } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_UNDERLINE: if (tty_term_has(tty->term, TTYC_SS)) { if (cmode & MODE_CURSOR_BLINKING) - tty_putcode1(tty, TTYC_SS, 3); + tty_putcode_i(tty, TTYC_SS, 3); else - tty_putcode1(tty, TTYC_SS, 4); + tty_putcode_i(tty, TTYC_SS, 4); } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_BAR: if (tty_term_has(tty->term, TTYC_SS)) { if (cmode & MODE_CURSOR_BLINKING) - tty_putcode1(tty, TTYC_SS, 5); + tty_putcode_i(tty, TTYC_SS, 5); else - tty_putcode1(tty, TTYC_SS, 6); + tty_putcode_i(tty, TTYC_SS, 6); } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; @@ -819,12 +856,6 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s) else if (mode & MODE_MOUSE_STANDARD) tty_puts(tty, "\033[?1000h"); } - if (changed & MODE_BRACKETPASTE) { - if (mode & MODE_BRACKETPASTE) - tty_putcode(tty, TTYC_ENBP); - else - tty_putcode(tty, TTYC_DSBP); - } tty->mode = mode; } @@ -833,7 +864,7 @@ tty_emulate_repeat(struct tty *tty, enum tty_code_code code, enum tty_code_code code1, u_int n) { if (tty_term_has(tty->term, code)) - tty_putcode1(tty, code, n); + tty_putcode_i(tty, code, n); else { while (n-- > 0) tty_putcode(tty, code1); @@ -1122,7 +1153,7 @@ tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, /* Section of line. Use ECH if possible. */ if (tty_term_has(tty->term, TTYC_ECH)) { tty_cursor(tty, px, py); - tty_putcode1(tty, TTYC_ECH, nx); + tty_putcode_i(tty, TTYC_ECH, nx); return; } } @@ -1263,7 +1294,7 @@ tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, tty_term_has(tty->term, TTYC_INDN)) { tty_region(tty, py, py + ny - 1); tty_margin_off(tty); - tty_putcode1(tty, TTYC_INDN, ny); + tty_putcode_i(tty, TTYC_INDN, ny); return; } @@ -1278,7 +1309,7 @@ tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, tty_term_has(tty->term, TTYC_INDN)) { tty_region(tty, py, py + ny - 1); tty_margin(tty, px, px + nx - 1); - tty_putcode1(tty, TTYC_INDN, ny); + tty_putcode_i(tty, TTYC_INDN, ny); return; } } @@ -1455,7 +1486,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 +1512,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 +1549,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 +1567,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 +1580,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 +1596,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); } @@ -1568,6 +1605,58 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, tty_update_mode(tty, tty->mode, s); } +#ifdef ENABLE_SIXEL +/* Update context for client. */ +static int +tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) +{ + struct window_pane *wp = ttyctx->arg; + + if (c->session->curw->window != wp->window) + return (0); + if (wp->layout_cell == NULL) + return (0); + + /* Set the properties relevant to the current client. */ + ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, + &ttyctx->wsx, &ttyctx->wsy); + + ttyctx->yoff = ttyctx->ryoff = wp->yoff; + if (status_at_line(c) == 0) + ttyctx->yoff += status_line_size(c); + + return (1); +} + +void +tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s) +{ + struct image *im; + struct tty_ctx ttyctx; + + TAILQ_FOREACH(im, &s->images, entry) { + memset(&ttyctx, 0, sizeof ttyctx); + + /* Set the client independent properties. */ + ttyctx.ocx = im->px; + ttyctx.ocy = im->py; + + ttyctx.orlower = s->rlower; + ttyctx.orupper = s->rupper; + + ttyctx.xoff = ttyctx.rxoff = wp->xoff; + ttyctx.sx = wp->sx; + ttyctx.sy = wp->sy; + + ttyctx.ptr = im; + ttyctx.arg = wp; + ttyctx.set_client_cb = tty_set_client_cb; + ttyctx.allow_invisible_panes = 1; + tty_write_one(tty_cmd_sixelimage, c, &ttyctx); + } +} +#endif + void tty_sync_start(struct tty *tty) { @@ -1579,7 +1668,7 @@ tty_sync_start(struct tty *tty) if (tty_term_has(tty->term, TTYC_SYNC)) { log_debug("%s sync start", tty->client->name); - tty_putcode1(tty, TTYC_SYNC, 1); + tty_putcode_i(tty, TTYC_SYNC, 1); } } @@ -1594,16 +1683,26 @@ tty_sync_end(struct tty *tty) if (tty_term_has(tty->term, TTYC_SYNC)) { log_debug("%s sync end", tty->client->name); - tty_putcode1(tty, TTYC_SYNC, 2); + tty_putcode_i(tty, TTYC_SYNC, 2); } } static int -tty_client_ready(struct client *c) +tty_client_ready(const struct tty_ctx *ctx, struct client *c) { if (c->session == NULL || c->tty.term == NULL) return (0); - if (c->flags & (CLIENT_REDRAWWINDOW|CLIENT_SUSPENDED)) + if (c->flags & CLIENT_SUSPENDED) + return (0); + + /* + * If invisible panes are allowed (used for passthrough), don't care if + * redrawing or frozen. + */ + if (ctx->allow_invisible_panes) + return (1); + + if (c->flags & CLIENT_REDRAWWINDOW) return (0); if (c->tty.flags & TTY_FREEZE) return (0); @@ -1620,17 +1719,30 @@ 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; - cmdfn(&c->tty, ctx); + if (tty_client_ready(ctx, c)) { + state = ctx->set_client_cb(ctx, c); + if (state == -1) + break; + if (state == 0) + continue; + cmdfn(&c->tty, ctx); + } } } +#ifdef ENABLE_SIXEL +/* Only write to the incoming tty instead of every client. */ +static void +tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *), + struct client *c, struct tty_ctx *ctx) +{ + if (ctx->set_client_cb == NULL) + return; + if ((ctx->set_client_cb(ctx, c)) == 1) + cmdfn(&c->tty, ctx); +} +#endif + void tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { @@ -1646,7 +1758,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); @@ -1668,7 +1781,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); @@ -1678,7 +1792,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); } @@ -1700,7 +1815,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); @@ -1727,7 +1843,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); @@ -1740,7 +1857,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); } @@ -1750,7 +1868,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); } @@ -1758,7 +1877,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); } @@ -1784,7 +1904,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); @@ -1793,7 +1914,7 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) if (tty_term_has(tty->term, TTYC_RI)) tty_putcode(tty, TTYC_RI); else - tty_putcode1(tty, TTYC_RIN, 1); + tty_putcode_i(tty, TTYC_RIN, 1); } void @@ -1815,7 +1936,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); @@ -1855,7 +1977,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); @@ -1872,7 +1995,7 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) tty_cursor(tty, 0, 0); else tty_cursor(tty, 0, tty->cy); - tty_putcode1(tty, TTYC_INDN, ctx->num); + tty_putcode_i(tty, TTYC_INDN, ctx->num); } } @@ -1895,14 +2018,15 @@ 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); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper); if (tty_term_has(tty->term, TTYC_RIN)) - tty_putcode1(tty, TTYC_RIN, ctx->num); + tty_putcode_i(tty, TTYC_RIN, ctx->num); else { for (i = 0; i < ctx->num; i++) tty_putcode(tty, TTYC_RI); @@ -1914,7 +2038,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); @@ -1938,7 +2063,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); @@ -1962,7 +2088,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); @@ -1985,7 +2112,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); @@ -2031,7 +2159,11 @@ 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); + + if (ctx->num == 1) + tty_invalidate(tty); } void @@ -2062,7 +2194,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; @@ -2082,11 +2214,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; @@ -2101,7 +2234,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_ss(tty, TTYC_MS, flags, encoded); free(encoded); } @@ -2114,6 +2247,58 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) tty_invalidate(tty); } +#ifdef ENABLE_SIXEL +void +tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) +{ + struct image *im = ctx->ptr; + struct sixel_image *si = im->data; + struct sixel_image *new; + char *data; + size_t size; + u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy; + u_int i, j, x, y, rx, ry; + int fallback = 0; + + if ((~tty->term->flags & TERM_SIXEL) && + !tty_term_has(tty->term, TTYC_SXL)) + fallback = 1; + if (tty->xpixel == 0 || tty->ypixel == 0) + fallback = 1; + + sixel_size_in_cells(si, &sx, &sy); + log_debug("%s: image is %ux%u", __func__, sx, sy); + if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry)) + return; + log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry); + + if (fallback == 1) { + data = xstrdup(im->fallback); + size = strlen(data); + } else { + new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0); + if (new == NULL) + return; + + data = sixel_print(new, si, &size); + } + if (data != NULL) { + log_debug("%s: %zu bytes: %s", __func__, size, data); + tty_region_off(tty); + tty_margin_off(tty); + tty_cursor(tty, x, y); + + tty->flags |= TTY_NOBLOCK; + tty_add(tty, data, size); + tty_invalidate(tty); + free(data); + } + + if (fallback == 0) + sixel_free(new); +} +#endif + void tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) { @@ -2135,7 +2320,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; @@ -2151,11 +2337,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); @@ -2172,6 +2358,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_ss(tty, TTYC_HLS, "", ""); if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_RMACS); tty_putcode(tty, TTYC_SGR0); @@ -2246,7 +2434,7 @@ tty_region(struct tty *tty, u_int rupper, u_int rlower) tty_cursor(tty, 0, tty->cy); } - tty_putcode2(tty, TTYC_CSR, tty->rupper, tty->rlower); + tty_putcode_ii(tty, TTYC_CSR, tty->rupper, tty->rlower); tty->cx = tty->cy = UINT_MAX; } @@ -2274,7 +2462,7 @@ tty_margin(struct tty *tty, u_int rleft, u_int rright) if (tty->rleft == rleft && tty->rright == rright) return; - tty_putcode2(tty, TTYC_CSR, tty->rupper, tty->rlower); + tty_putcode_ii(tty, TTYC_CSR, tty->rupper, tty->rlower); tty->rleft = rleft; tty->rright = rright; @@ -2282,7 +2470,7 @@ tty_margin(struct tty *tty, u_int rleft, u_int rright) if (rleft == 0 && rright == tty->sx - 1) tty_putcode(tty, TTYC_CLMG); else - tty_putcode2(tty, TTYC_CMG, rleft, rright); + tty_putcode_ii(tty, TTYC_CMG, rleft, rright); tty->cx = tty->cy = UINT_MAX; } @@ -2392,7 +2580,7 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) * the cursor with CUB/CUF. */ if ((u_int) abs(change) > cx && tty_term_has(term, TTYC_HPA)) { - tty_putcode1(tty, TTYC_HPA, cx); + tty_putcode_i(tty, TTYC_HPA, cx); goto out; } else if (change > 0 && tty_term_has(term, TTYC_CUB) && @@ -2402,12 +2590,12 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) tty_putcode(tty, TTYC_CUB1); goto out; } - tty_putcode1(tty, TTYC_CUB, change); + tty_putcode_i(tty, TTYC_CUB, change); goto out; } else if (change < 0 && tty_term_has(term, TTYC_CUF) && !tty_use_margin(tty)) { - tty_putcode1(tty, TTYC_CUF, -change); + tty_putcode_i(tty, TTYC_CUF, -change); goto out; } } else if (cx == thisx) { @@ -2440,30 +2628,50 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) (change < 0 && cy - change > tty->rlower) || (change > 0 && cy - change < tty->rupper)) { if (tty_term_has(term, TTYC_VPA)) { - tty_putcode1(tty, TTYC_VPA, cy); + tty_putcode_i(tty, TTYC_VPA, cy); goto out; } } else if (change > 0 && tty_term_has(term, TTYC_CUU)) { - tty_putcode1(tty, TTYC_CUU, change); + tty_putcode_i(tty, TTYC_CUU, change); goto out; } else if (change < 0 && tty_term_has(term, TTYC_CUD)) { - tty_putcode1(tty, TTYC_CUD, -change); + tty_putcode_i(tty, TTYC_CUD, -change); goto out; } } absolute: /* Absolute movement. */ - tty_putcode2(tty, TTYC_CUP, cy, cx); + tty_putcode_ii(tty, TTYC_CUP, cy, cx); out: tty->cx = cx; 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_ss(tty, TTYC_HLS, "", ""); + else + tty_putcode_ss(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; @@ -2481,7 +2689,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; /* @@ -2533,13 +2742,13 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, !tty_term_has(tty->term, TTYC_SMULX)) tty_putcode(tty, TTYC_SMUL); else if (changed & GRID_ATTR_UNDERSCORE_2) - tty_putcode1(tty, TTYC_SMULX, 2); + tty_putcode_i(tty, TTYC_SMULX, 2); else if (changed & GRID_ATTR_UNDERSCORE_3) - tty_putcode1(tty, TTYC_SMULX, 3); + tty_putcode_i(tty, TTYC_SMULX, 3); else if (changed & GRID_ATTR_UNDERSCORE_4) - tty_putcode1(tty, TTYC_SMULX, 4); + tty_putcode_i(tty, TTYC_SMULX, 4); else if (changed & GRID_ATTR_UNDERSCORE_5) - tty_putcode1(tty, TTYC_SMULX, 5); + tty_putcode_i(tty, TTYC_SMULX, 5); } if (changed & GRID_ATTR_BLINK) tty_putcode(tty, TTYC_BLINK); @@ -2558,6 +2767,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); } @@ -2593,14 +2805,14 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) if (have_ax) tty_puts(tty, "\033[39m"); else if (tc->fg != 7) - tty_putcode1(tty, TTYC_SETAF, 7); + tty_putcode_i(tty, TTYC_SETAF, 7); tc->fg = gc->fg; } if (COLOUR_DEFAULT(gc->bg) && !COLOUR_DEFAULT(tc->bg)) { if (have_ax) tty_puts(tty, "\033[49m"); else if (tc->bg != 0) - tty_putcode1(tty, TTYC_SETAB, 0); + tty_putcode_i(tty, TTYC_SETAB, 0); tc->bg = gc->bg; } } @@ -2632,12 +2844,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; @@ -2743,9 +2957,13 @@ tty_check_us(__unused struct tty *tty, struct colour_palette *palette, gc->us = c; } - /* Underscore colour is set as RGB so convert a 256 colour to RGB. */ - if (gc->us & COLOUR_FLAG_256) - gc->us = colour_256toRGB (gc->us); + /* Convert underscore colour if only RGB can be supported. */ + if (!tty_term_has(tty->term, TTYC_SETULC1)) { + if ((c = colour_force_rgb (gc->us)) == -1) + gc->us = 8; + else + gc->us = c; + } } static void @@ -2768,12 +2986,12 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc) xsnprintf(s, sizeof s, "\033[%dm", gc->fg); tty_puts(tty, s); } else - tty_putcode1(tty, TTYC_SETAF, gc->fg - 90 + 8); + tty_putcode_i(tty, TTYC_SETAF, gc->fg - 90 + 8); goto save; } /* Otherwise set the foreground colour. */ - tty_putcode1(tty, TTYC_SETAF, gc->fg); + tty_putcode_i(tty, TTYC_SETAF, gc->fg); save: /* Save the new values in the terminal current cell. */ @@ -2800,12 +3018,12 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc) xsnprintf(s, sizeof s, "\033[%dm", gc->bg + 10); tty_puts(tty, s); } else - tty_putcode1(tty, TTYC_SETAB, gc->bg - 90 + 8); + tty_putcode_i(tty, TTYC_SETAB, gc->bg - 90 + 8); goto save; } /* Otherwise set the background colour. */ - tty_putcode1(tty, TTYC_SETAB, gc->bg); + tty_putcode_i(tty, TTYC_SETAB, gc->bg); save: /* Save the new values in the terminal current cell. */ @@ -2820,14 +3038,22 @@ tty_colours_us(struct tty *tty, const struct grid_cell *gc) u_char r, g, b; /* Clear underline colour. */ - if (gc->us == 0) { + if (COLOUR_DEFAULT(gc->us)) { tty_putcode(tty, TTYC_OL); goto save; } - /* Must be an RGB colour - this should never happen. */ - if (~gc->us & COLOUR_FLAG_RGB) + /* + * If this is not an RGB colour, use Setulc1 if it exists, otherwise + * convert. + */ + if (~gc->us & COLOUR_FLAG_RGB) { + c = gc->us; + if ((~c & COLOUR_FLAG_256) && (c >= 90 && c <= 97)) + c -= 82; + tty_putcode_i(tty, TTYC_SETULC1, c & ~COLOUR_FLAG_256); return; + } /* * Setulc and setal follows the ncurses(3) one argument "direct colour" @@ -2841,10 +3067,10 @@ tty_colours_us(struct tty *tty, const struct grid_cell *gc) * non-RGB version may be wrong. */ if (tty_term_has(tty->term, TTYC_SETULC)) - tty_putcode1(tty, TTYC_SETULC, c); + tty_putcode_i(tty, TTYC_SETULC, c); else if (tty_term_has(tty->term, TTYC_SETAL) && tty_term_has(tty->term, TTYC_RGB)) - tty_putcode1(tty, TTYC_SETAL, c); + tty_putcode_i(tty, TTYC_SETAL, c); save: /* Save the new values in the terminal current cell. */ @@ -2858,18 +3084,18 @@ tty_try_colour(struct tty *tty, int colour, const char *type) if (colour & COLOUR_FLAG_256) { if (*type == '3' && tty_term_has(tty->term, TTYC_SETAF)) - tty_putcode1(tty, TTYC_SETAF, colour & 0xff); + tty_putcode_i(tty, TTYC_SETAF, colour & 0xff); else if (tty_term_has(tty->term, TTYC_SETAB)) - tty_putcode1(tty, TTYC_SETAB, colour & 0xff); + tty_putcode_i(tty, TTYC_SETAB, colour & 0xff); return (0); } if (colour & COLOUR_FLAG_RGB) { colour_split_rgb(colour & 0xffffff, &r, &g, &b); if (*type == '3' && tty_term_has(tty->term, TTYC_SETRGBF)) - tty_putcode3(tty, TTYC_SETRGBF, r, g, b); + tty_putcode_iii(tty, TTYC_SETRGBF, r, g, b); else if (tty_term_has(tty->term, TTYC_SETRGBB)) - tty_putcode3(tty, TTYC_SETRGBB, r, g, b); + tty_putcode_iii(tty, TTYC_SETRGBB, r, g, b); return (0); } @@ -2923,13 +3149,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 @@ -2953,7 +3179,7 @@ tty_clipboard_query(struct tty *tty) if ((~tty->flags & TTY_STARTED) || (tty->flags & TTY_OSC52QUERY)) return; - tty_putcode_ptr2(tty, TTYC_MS, "", "?"); + tty_putcode_ss(tty, TTYC_MS, "", "?"); tty->flags |= TTY_OSC52QUERY; evtimer_set(&tty->clipboard_timer, tty_clipboard_query_callback, tty); diff --git a/utf8-combined.c b/utf8-combined.c new file mode 100644 index 0000000..05958d4 --- /dev/null +++ b/utf8-combined.c @@ -0,0 +1,100 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2023 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> +#include <wchar.h> + +#include "tmux.h" + +static const wchar_t utf8_modifier_table[] = { + 0x1F1E6, + 0x1F1E7, + 0x1F1E8, + 0x1F1E9, + 0x1F1EA, + 0x1F1EB, + 0x1F1EC, + 0x1F1ED, + 0x1F1EE, + 0x1F1EF, + 0x1F1F0, + 0x1F1F1, + 0x1F1F2, + 0x1F1F3, + 0x1F1F4, + 0x1F1F5, + 0x1F1F6, + 0x1F1F7, + 0x1F1F8, + 0x1F1F9, + 0x1F1FA, + 0x1F1FB, + 0x1F1FC, + 0x1F1FD, + 0x1F1FE, + 0x1F1FF, + 0x1F3FB, + 0x1F3FC, + 0x1F3FD, + 0x1F3FE, + 0x1F3FF +}; + +/* Has this got a zero width joiner at the end? */ +int +utf8_has_zwj(const struct utf8_data *ud) +{ + if (ud->size < 3) + return (0); + return (memcmp(ud->data + ud->size - 3, "\342\200\215", 3) == 0); +} + +/* Is this a zero width joiner? */ +int +utf8_is_zwj(const struct utf8_data *ud) +{ + if (ud->size != 3) + return (0); + return (memcmp(ud->data, "\342\200\215", 3) == 0); +} + +/* Is this a variation selector? */ +int +utf8_is_vs(const struct utf8_data *ud) +{ + if (ud->size != 3) + return (0); + return (memcmp(ud->data, "\357\270\217", 3) == 0); +} + +/* Is this in the modifier table? */ +int +utf8_is_modifier(const struct utf8_data *ud) +{ + wchar_t wc; + + if (utf8_towc(ud, &wc) != UTF8_DONE) + return (0); + if (!utf8_in_table(wc, utf8_modifier_table, + nitems(utf8_modifier_table))) + return (0); + return (1); +} @@ -26,6 +26,171 @@ #include "tmux.h" +static const wchar_t utf8_force_wide[] = { + 0x0261D, + 0x026F9, + 0x0270A, + 0x0270B, + 0x0270C, + 0x0270D, + 0x1F1E6, + 0x1F1E7, + 0x1F1E8, + 0x1F1E9, + 0x1F1EA, + 0x1F1EB, + 0x1F1EC, + 0x1F1ED, + 0x1F1EE, + 0x1F1EF, + 0x1F1F0, + 0x1F1F1, + 0x1F1F2, + 0x1F1F3, + 0x1F1F4, + 0x1F1F5, + 0x1F1F6, + 0x1F1F7, + 0x1F1F8, + 0x1F1F9, + 0x1F1FA, + 0x1F1FB, + 0x1F1FC, + 0x1F1FD, + 0x1F1FE, + 0x1F1FF, + 0x1F385, + 0x1F3C2, + 0x1F3C3, + 0x1F3C4, + 0x1F3C7, + 0x1F3CA, + 0x1F3CB, + 0x1F3CC, + 0x1F3FB, + 0x1F3FC, + 0x1F3FD, + 0x1F3FE, + 0x1F3FF, + 0x1F442, + 0x1F443, + 0x1F446, + 0x1F447, + 0x1F448, + 0x1F449, + 0x1F44A, + 0x1F44B, + 0x1F44C, + 0x1F44D, + 0x1F44E, + 0x1F44F, + 0x1F450, + 0x1F466, + 0x1F467, + 0x1F468, + 0x1F469, + 0x1F46B, + 0x1F46C, + 0x1F46D, + 0x1F46E, + 0x1F470, + 0x1F471, + 0x1F472, + 0x1F473, + 0x1F474, + 0x1F475, + 0x1F476, + 0x1F477, + 0x1F478, + 0x1F47C, + 0x1F481, + 0x1F482, + 0x1F483, + 0x1F485, + 0x1F486, + 0x1F487, + 0x1F48F, + 0x1F491, + 0x1F4AA, + 0x1F574, + 0x1F575, + 0x1F57A, + 0x1F590, + 0x1F595, + 0x1F596, + 0x1F645, + 0x1F646, + 0x1F647, + 0x1F64B, + 0x1F64C, + 0x1F64D, + 0x1F64E, + 0x1F64F, + 0x1F6A3, + 0x1F6B4, + 0x1F6B5, + 0x1F6B6, + 0x1F6C0, + 0x1F6CC, + 0x1F90C, + 0x1F90F, + 0x1F918, + 0x1F919, + 0x1F91A, + 0x1F91B, + 0x1F91C, + 0x1F91D, + 0x1F91E, + 0x1F91F, + 0x1F926, + 0x1F930, + 0x1F931, + 0x1F932, + 0x1F933, + 0x1F934, + 0x1F935, + 0x1F936, + 0x1F937, + 0x1F938, + 0x1F939, + 0x1F93D, + 0x1F93E, + 0x1F977, + 0x1F9B5, + 0x1F9B6, + 0x1F9B8, + 0x1F9B9, + 0x1F9BB, + 0x1F9CD, + 0x1F9CE, + 0x1F9CF, + 0x1F9D1, + 0x1F9D2, + 0x1F9D3, + 0x1F9D4, + 0x1F9D5, + 0x1F9D6, + 0x1F9D7, + 0x1F9D8, + 0x1F9D9, + 0x1F9DA, + 0x1F9DB, + 0x1F9DC, + 0x1F9DD, + 0x1FAC3, + 0x1FAC4, + 0x1FAC5, + 0x1FAF0, + 0x1FAF1, + 0x1FAF2, + 0x1FAF3, + 0x1FAF4, + 0x1FAF5, + 0x1FAF6, + 0x1FAF7, + 0x1FAF8 +}; + struct utf8_item { RB_ENTRY(utf8_item) index_entry; u_int index; @@ -71,7 +236,7 @@ static u_int utf8_next_index; /* Get a UTF-8 item from data. */ static struct utf8_item * -utf8_item_by_data(const char *data, size_t size) +utf8_item_by_data(const u_char *data, size_t size) { struct utf8_item ui; @@ -94,7 +259,7 @@ utf8_item_by_index(u_int index) /* Add a UTF-8 item. */ static int -utf8_put_item(const char *data, size_t size, u_int *index) +utf8_put_item(const u_char *data, size_t size, u_int *index) { struct utf8_item *ui; @@ -122,6 +287,28 @@ utf8_put_item(const char *data, size_t size, u_int *index) return (0); } +static int +utf8_table_cmp(const void *vp1, const void *vp2) +{ + const wchar_t *wc1 = vp1, *wc2 = vp2; + + if (*wc1 < *wc2) + return (-1); + if (*wc1 > *wc2) + return (1); + return (0); +} + +/* Check if character in table. */ +int +utf8_in_table(wchar_t find, const wchar_t *table, u_int count) +{ + wchar_t *found; + + found = bsearch(&find, table, count, sizeof *table, utf8_table_cmp); + return (found != NULL); +} + /* Get UTF-8 character from data. */ enum utf8_state utf8_from_data(const struct utf8_data *ud, utf8_char *uc) @@ -135,8 +322,8 @@ utf8_from_data(const struct utf8_data *ud, utf8_char *uc) goto fail; if (ud->size <= 3) { index = (((utf8_char)ud->data[2] << 16)| - ((utf8_char)ud->data[1] << 8)| - ((utf8_char)ud->data[0])); + ((utf8_char)ud->data[1] << 8)| + ((utf8_char)ud->data[0])); } else if (utf8_put_item(ud->data, ud->size, &index) != 0) goto fail; *uc = UTF8_SET_SIZE(ud->size)|UTF8_SET_WIDTH(ud->width)|index; @@ -216,10 +403,39 @@ utf8_width(struct utf8_data *ud, int *width) { wchar_t wc; + if (utf8_towc(ud, &wc) != UTF8_DONE) + return (UTF8_ERROR); + if (utf8_in_table(wc, utf8_force_wide, nitems(utf8_force_wide))) { + *width = 2; + return (UTF8_DONE); + } +#ifdef HAVE_UTF8PROC + *width = utf8proc_wcwidth(wc); + log_debug("utf8proc_wcwidth(%05X) returned %d", (u_int)wc, *width); +#else + *width = wcwidth(wc); + log_debug("wcwidth(%05X) returned %d", (u_int)wc, *width); + if (*width < 0) { + /* + * C1 control characters are nonprintable, so they are always + * zero width. + */ + *width = (wc >= 0x80 && wc <= 0x9f) ? 0 : 1; + } +#endif + if (*width >= 0 && *width <= 0xff) + return (UTF8_DONE); + return (UTF8_ERROR); +} + +/* Convert UTF-8 character to wide character. */ +enum utf8_state +utf8_towc(const struct utf8_data *ud, wchar_t *wc) +{ #ifdef HAVE_UTF8PROC - switch (utf8proc_mbtowc(&wc, ud->data, ud->size)) { + switch (utf8proc_mbtowc(wc, ud->data, ud->size)) { #else - switch (mbtowc(&wc, ud->data, ud->size)) { + switch (mbtowc(wc, ud->data, ud->size)) { #endif case -1: log_debug("UTF-8 %.*s, mbtowc() %d", (int)ud->size, ud->data, @@ -229,15 +445,8 @@ utf8_width(struct utf8_data *ud, int *width) case 0: return (UTF8_ERROR); } -#ifdef HAVE_UTF8PROC - *width = utf8proc_wcwidth(wc); -#else - *width = wcwidth(wc); -#endif - if (*width >= 0 && *width <= 0xff) - return (UTF8_DONE); - log_debug("UTF-8 %.*s, wcwidth() %d", (int)ud->size, ud->data, *width); - return (UTF8_ERROR); + log_debug("UTF-8 %.*s is %05X", (int)ud->size, ud->data, (u_int)*wc); + return (UTF8_DONE); } /* diff --git a/window-buffer.c b/window-buffer.c index 2f52ee3..2e0a9e8 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -508,6 +508,11 @@ window_buffer_key(struct window_mode_entry *wme, struct client *c, struct window_buffer_itemdata *item; int finished; + if (paste_get_top(NULL) == NULL) { + finished = 1; + goto out; + } + finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); switch (key) { case 'e': @@ -534,6 +539,8 @@ window_buffer_key(struct window_mode_entry *wme, struct client *c, finished = 1; break; } + +out: if (finished || paste_get_top(NULL) == NULL) window_pane_reset_mode(wp); else { diff --git a/window-client.c b/window-client.c index 8d501b0..b704b53 100644 --- a/window-client.c +++ b/window-client.c @@ -242,7 +242,7 @@ window_client_draw(__unused void *modedata, void *itemdata, screen_write_cursormove(ctx, cx, cy + 2, 0); else screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0); - screen_write_hline(ctx, sx, 0, 0); + screen_write_hline(ctx, sx, 0, 0, BOX_LINES_DEFAULT, NULL); if (at != 0) screen_write_cursormove(ctx, cx, cy, 0); diff --git a/window-copy.c b/window-copy.c index 0307055..bc3713b 100644 --- a/window-copy.c +++ b/window-copy.c @@ -131,6 +131,8 @@ static void window_copy_cursor_previous_word_pos(struct window_mode_entry *, const char *, u_int *, u_int *); static void window_copy_cursor_previous_word(struct window_mode_entry *, const char *, int); +static void window_copy_cursor_prompt(struct window_mode_entry *, int, + const char *); static void window_copy_scroll_up(struct window_mode_entry *, u_int); static void window_copy_scroll_down(struct window_mode_entry *, u_int); static void window_copy_rectangle_set(struct window_mode_entry *, int); @@ -293,6 +295,7 @@ struct window_copy_mode_data { int timeout; /* search has timed out */ #define WINDOW_COPY_SEARCH_TIMEOUT 10000 #define WINDOW_COPY_SEARCH_ALL_TIMEOUT 200 +#define WINDOW_COPY_SEARCH_MAX_LINE 2000 int jumptype; struct utf8_data *jumpchar; @@ -1250,6 +1253,64 @@ window_copy_cmd_cursor_right(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_NOTHING); } +/* Scroll line containing the cursor to the given position. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int oy, delta; + int scroll_up; /* >0 up, <0 down */ + + scroll_up = data->cy - to; + delta = abs(scroll_up); + oy = screen_hsize(data->backing) - data->oy; + + /* + * oy is the maximum scroll down amount, while data->oy is the maximum + * scroll up amount. + */ + 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); +} + +/* Scroll line containing the cursor to the bottom. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_bottom(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + u_int bottom; + + bottom = screen_size_y(&data->screen) - 1; + return (window_copy_cmd_scroll_to(cs, bottom)); +} + +/* Scroll line containing the cursor to the middle. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + u_int mid_value; + + mid_value = (screen_size_y(&data->screen) - 1) / 2; + return (window_copy_cmd_scroll_to(cs, mid_value)); +} + +/* Scroll line containing the cursor to the top. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_top(struct window_copy_cmd_state *cs) +{ + return (window_copy_cmd_scroll_to(cs, 0)); +} + static enum window_copy_cmd_action window_copy_cmd_cursor_up(struct window_copy_cmd_state *cs) { @@ -2183,6 +2244,26 @@ window_copy_cmd_jump_to_mark(struct window_copy_cmd_state *cs) } static enum window_copy_cmd_action +window_copy_cmd_next_prompt(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + const char *arg1 = args_string(cs->args, 1); + + window_copy_cursor_prompt(wme, 1, arg1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_previous_prompt(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + const char *arg1 = args_string(cs->args, 1); + + window_copy_cursor_prompt(wme, 0, arg1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action window_copy_cmd_search_backward(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; @@ -2636,6 +2717,18 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_jump_to_mark }, + { .command = "next-prompt", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_next_prompt + }, + { .command = "previous-prompt", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_previous_prompt + }, { .command = "middle-line", .minargs = 0, .maxargs = 0, @@ -2768,6 +2861,12 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_refresh_from_pane }, + { .command = "scroll-bottom", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_bottom + }, { .command = "scroll-down", .minargs = 0, .maxargs = 0, @@ -2780,6 +2879,18 @@ 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-top", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_top + }, { .command = "scroll-up", .minargs = 0, .maxargs = 0, @@ -3095,7 +3206,9 @@ window_copy_search_lr_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py, len = gd->sx - first; endline = gd->hsize + gd->sy - 1; pywrap = py; - while (buf != NULL && pywrap <= endline) { + while (buf != NULL && + pywrap <= endline && + len < WINDOW_COPY_SEARCH_MAX_LINE) { gl = grid_get_line(gd, pywrap); if (~gl->flags & GRID_LINE_WRAPPED) break; @@ -3152,7 +3265,9 @@ window_copy_search_rl_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py, len = gd->sx - first; endline = gd->hsize + gd->sy - 1; pywrap = py; - while (buf != NULL && (pywrap <= endline)) { + while (buf != NULL && + pywrap <= endline && + len < WINDOW_COPY_SEARCH_MAX_LINE) { gl = grid_get_line(gd, pywrap); if (~gl->flags & GRID_LINE_WRAPPED) break; @@ -3491,10 +3606,11 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap, int direction, int regex) { - u_int i, px, sx, ssize = 1; - int found = 0, cflags = REG_EXTENDED; - char *sbuf; - regex_t reg; + u_int i, px, sx, ssize = 1; + int found = 0, cflags = REG_EXTENDED; + char *sbuf; + regex_t reg; + struct grid_line *gl; if (regex) { sbuf = xmalloc(ssize); @@ -3511,6 +3627,9 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, if (direction) { for (i = fy; i <= endline; i++) { + gl = grid_get_line(gd, i); + if (i != endline && gl->flags & GRID_LINE_WRAPPED) + continue; if (regex) { found = window_copy_search_lr_regex(gd, &px, &sx, i, fx, gd->sx, ®); @@ -3524,6 +3643,9 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, } } else { for (i = fy + 1; endline < i; i--) { + gl = grid_get_line(gd, i - 1); + if (i != endline && gl->flags & GRID_LINE_WRAPPED) + continue; if (regex) { found = window_copy_search_rl_regex(gd, &px, &sx, i - 1, 0, fx + 1, ®); @@ -3612,6 +3734,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; @@ -3651,8 +3775,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) } } endline = gd->hsize + gd->sy - 1; - } - else { + } else { window_copy_move_left(s, &fx, &fy, wrapflag); endline = 0; } @@ -3671,6 +3794,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); @@ -3693,8 +3817,7 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) data->cy = fy - screen_hsize(data->backing) + data-> oy; } - } - else { + } else { /* * When searching backward, position the cursor at the * beginning of the mark. @@ -3703,6 +3826,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; @@ -4092,8 +4216,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); @@ -4107,23 +4232,29 @@ window_copy_write_line(struct window_mode_entry *wme, mkgc.flags |= GRID_FLAG_NOPALETTE; if (py == 0 && s->rupper < s->rlower && !data->hide_position) { + gl = grid_get_line(data->backing->grid, hsize - data->oy); + if (gl->time == 0) + xsnprintf(tmp, sizeof tmp, "[%u/%u]", data->oy, hsize); + else { + t = format_pretty_time(gl->time, 1); + xsnprintf(tmp, sizeof tmp, "%s [%u/%u]", t, data->oy, + hsize); + free(t); + } + if (data->searchmark == NULL) { if (data->timeout) { size = xsnprintf(hdr, sizeof hdr, - "(timed out) [%u/%u]", data->oy, hsize); - } else { - size = xsnprintf(hdr, sizeof hdr, - "[%u/%u]", data->oy, hsize); - } + "(timed out) %s", tmp); + } else + size = xsnprintf(hdr, sizeof hdr, "%s", tmp); } else { - if (data->searchcount == -1) { - size = xsnprintf(hdr, sizeof hdr, - "[%u/%u]", data->oy, hsize); - } else { + if (data->searchcount == -1) + size = xsnprintf(hdr, sizeof hdr, "%s", tmp); + else { size = xsnprintf(hdr, sizeof hdr, - "(%d%s results) [%u/%u]", data->searchcount, - data->searchmore ? "+" : "", data->oy, - hsize); + "(%d%s results) %s", data->searchcount, + data->searchmore ? "+" : "", tmp); } } if (size > screen_size_x(s)) @@ -4570,7 +4701,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); } @@ -4644,7 +4775,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); } @@ -5271,6 +5402,54 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, } static void +window_copy_cursor_prompt(struct window_mode_entry *wme, int direction, + const char *args) +{ + struct window_copy_mode_data *data = wme->data; + struct screen *s = data->backing; + struct grid *gd = s->grid; + u_int end_line; + u_int line = gd->hsize - data->oy + data->cy; + int add, line_flag; + + if (args != NULL && strcmp(args, "-o") == 0) + line_flag = GRID_LINE_START_OUTPUT; + else + line_flag = GRID_LINE_START_PROMPT; + + if (direction == 0) { /* up */ + add = -1; + end_line = 0; + } else { /* down */ + add = 1; + end_line = gd->hsize + gd->sy - 1; + } + + if (line == end_line) + return; + for (;;) { + if (line == end_line) + return; + line += add; + + if (grid_get_line(gd, line)->flags & line_flag) + break; + } + + data->cx = 0; + if (line > gd->hsize) { + data->cy = line - gd->hsize; + data->oy = 0; + } else { + data->cy = 0; + data->oy = gd->hsize - line; + } + + window_copy_update_selection(wme, 1, 0); + window_copy_redraw_screen(wme); +} + +static void window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) { struct window_pane *wp = wme->wp; diff --git a/window-tree.c b/window-tree.c index d90bf81..b2f397f 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; @@ -668,9 +671,9 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, struct window_pane *wp; u_int cx = ctx->s->cx, cy = ctx->s->cy; u_int loop, total, visible, each, width, offset; - u_int current, start, end, remaining, i; + u_int current, start, end, remaining, i, pane_idx; struct grid_cell gc; - int colour, active_colour, left, right, pane_idx; + int colour, active_colour, left, right; char *label; total = window_count_panes(w); @@ -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--; @@ -64,6 +64,7 @@ static u_int next_active_point; struct window_pane_input_data { struct cmdq_item *item; u_int wp; + struct client_file *file; }; static struct window_pane *window_pane_create(struct window *, u_int, u_int, @@ -245,21 +246,15 @@ winlink_stack_push(struct winlink_stack *stack, struct winlink *wl) winlink_stack_remove(stack, wl); TAILQ_INSERT_HEAD(stack, wl, sentry); + wl->flags |= WINLINK_VISITED; } void winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl) { - struct winlink *wl2; - - if (wl == NULL) - return; - - TAILQ_FOREACH(wl2, stack, sentry) { - if (wl2 == wl) { - TAILQ_REMOVE(stack, wl, sentry); - return; - } + if (wl != NULL && (wl->flags & WINLINK_VISITED)) { + TAILQ_REMOVE(stack, wl, sentry); + wl->flags &= ~WINLINK_VISITED; } } @@ -309,6 +304,7 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) w->flags = 0; TAILQ_INIT(&w->panes); + TAILQ_INIT(&w->last_panes); w->active = NULL; w->lastlayout = -1; @@ -342,6 +338,7 @@ window_destroy(struct window *w) { log_debug("window @%u destroyed (%d references)", w->id, w->references); + window_unzoom(w); RB_REMOVE(windows, &windows, w); if (w->layout_root != NULL) @@ -518,18 +515,23 @@ window_pane_update_focus(struct window_pane *wp) int window_set_active_pane(struct window *w, struct window_pane *wp, int notify) { + struct window_pane *lastwp; + log_debug("%s: pane %%%u", __func__, wp->id); if (wp == w->active) return (0); - w->last = w->active; + lastwp = w->active; + + window_pane_stack_remove(&w->last_panes, wp); + window_pane_stack_push(&w->last_panes, lastwp); w->active = wp; w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; if (options_get_number(global_options, "focus-events")) { - window_pane_update_focus(w->last); + window_pane_update_focus(lastwp); window_pane_update_focus(w->active); } @@ -752,21 +754,21 @@ window_lost_pane(struct window *w, struct window_pane *wp) if (wp == marked_pane.wp) server_clear_marked(); + window_pane_stack_remove(&w->last_panes, wp); if (wp == w->active) { - w->active = w->last; - w->last = NULL; + w->active = TAILQ_FIRST(&w->last_panes); if (w->active == NULL) { w->active = TAILQ_PREV(wp, window_panes, entry); if (w->active == NULL) w->active = TAILQ_NEXT(wp, entry); } if (w->active != NULL) { + window_pane_stack_remove(&w->last_panes, w->active); w->active->flags |= PANE_CHANGED; notify_window("window-pane-changed", w); window_update_focus(w); } - } else if (wp == w->last) - w->last = NULL; + } } void @@ -850,6 +852,11 @@ window_destroy_panes(struct window *w) { struct window_pane *wp; + while (!TAILQ_EMPTY(&w->last_panes)) { + wp = TAILQ_FIRST(&w->last_panes); + window_pane_stack_remove(&w->last_panes, wp); + } + while (!TAILQ_EMPTY(&w->panes)) { wp = TAILQ_FIRST(&w->panes); TAILQ_REMOVE(&w->panes, wp, entry); @@ -940,6 +947,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 +1050,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); @@ -1126,6 +1136,7 @@ window_pane_reset_mode(struct window_pane *wp) next = TAILQ_FIRST(&wp->modes); if (next == NULL) { + wp->flags &= ~PANE_UNSEENCHANGES; log_debug("%s: no next mode", __func__); wp->screen = &wp->base; } else { @@ -1203,6 +1214,12 @@ window_pane_visible(struct window_pane *wp) return (wp == wp->window->active); } +int +window_pane_exited(struct window_pane *wp) +{ + return (wp->fd == -1 || (wp->flags & PANE_EXITED)); +} + u_int window_pane_search(struct window_pane *wp, const char *term, int regex, int ignore) @@ -1483,6 +1500,25 @@ window_pane_find_right(struct window_pane *wp) return (best); } +void +window_pane_stack_push(struct window_panes *stack, struct window_pane *wp) +{ + if (wp != NULL) { + window_pane_stack_remove(stack, wp); + TAILQ_INSERT_HEAD(stack, wp, sentry); + wp->flags |= PANE_VISITED; + } +} + +void +window_pane_stack_remove(struct window_panes *stack, struct window_pane *wp) +{ + if (wp != NULL && (wp->flags & PANE_VISITED)) { + TAILQ_REMOVE(stack, wp, sentry); + wp->flags &= ~PANE_VISITED; + } +} + /* Clear alert flags for a winlink */ void winlink_clear_flags(struct winlink *wl) @@ -1540,18 +1576,18 @@ window_pane_input_callback(struct client *c, __unused const char *path, size_t len = EVBUFFER_LENGTH(buffer); wp = window_pane_find_by_id(cdata->wp); - if (wp == NULL || closed || error != 0 || (c->flags & CLIENT_DEAD)) { - if (wp == NULL) + if (cdata->file != NULL && (wp == NULL || c->flags & CLIENT_DEAD)) { + if (wp == NULL) { + c->retval = 1; c->flags |= CLIENT_EXIT; - - evbuffer_drain(buffer, len); + } + file_cancel(cdata->file); + } else if (cdata->file == NULL || closed || error != 0) { cmdq_continue(cdata->item); - server_client_unref(c); free(cdata); - return; - } - input_parse_buffer(wp, buf, len); + } else + input_parse_buffer(wp, buf, len); evbuffer_drain(buffer, len); } @@ -1574,9 +1610,8 @@ window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, cdata = xmalloc(sizeof *cdata); cdata->item = item; cdata->wp = wp->id; - + cdata->file = file_read(c, "-", window_pane_input_callback, cdata); c->references++; - file_read(c, "-", window_pane_input_callback, cdata); return (0); } @@ -1618,3 +1653,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); +} |