summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:34:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:34:38 +0000
commitfa5845db13c5cc87a03062fedf5d9cc237d14604 (patch)
tree800970ee68d1ce0659dfbde71e1149e6a2cacf9f
parentInitial commit. (diff)
downloadtmux-fa5845db13c5cc87a03062fedf5d9cc237d14604.tar.xz
tmux-fa5845db13c5cc87a03062fedf5d9cc237d14604.zip
Adding upstream version 3.3a.upstream/3.3a
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--CHANGES3415
-rw-r--r--COPYING18
-rw-r--r--Makefile.am236
-rw-r--r--Makefile.in1296
-rw-r--r--README86
-rw-r--r--README.ja62
-rw-r--r--aclocal.m41428
-rw-r--r--alerts.c325
-rw-r--r--arguments.c906
-rw-r--r--attributes.c108
-rw-r--r--cfg.c260
-rw-r--r--client.c801
-rw-r--r--cmd-attach-session.c172
-rw-r--r--cmd-bind-key.c107
-rw-r--r--cmd-break-pane.c142
-rw-r--r--cmd-capture-pane.c245
-rw-r--r--cmd-choose-tree.c117
-rw-r--r--cmd-command-prompt.c238
-rw-r--r--cmd-confirm-before.c142
-rw-r--r--cmd-copy-mode.c96
-rw-r--r--cmd-detach-client.c109
-rw-r--r--cmd-display-menu.c465
-rw-r--r--cmd-display-message.c149
-rw-r--r--cmd-display-panes.c312
-rw-r--r--cmd-find-window.c113
-rw-r--r--cmd-find.c1314
-rw-r--r--cmd-if-shell.c190
-rw-r--r--cmd-join-pane.c171
-rw-r--r--cmd-kill-pane.c67
-rw-r--r--cmd-kill-server.c61
-rw-r--r--cmd-kill-session.c71
-rw-r--r--cmd-kill-window.c110
-rw-r--r--cmd-list-buffers.c81
-rw-r--r--cmd-list-clients.c92
-rw-r--r--cmd-list-keys.c365
-rw-r--r--cmd-list-panes.c148
-rw-r--r--cmd-list-sessions.c90
-rw-r--r--cmd-list-windows.c130
-rw-r--r--cmd-load-buffer.c113
-rw-r--r--cmd-lock-server.c79
-rw-r--r--cmd-move-window.c122
-rw-r--r--cmd-new-session.c374
-rw-r--r--cmd-new-window.c156
-rw-r--r--cmd-parse.c2229
-rw-r--r--cmd-parse.y1705
-rw-r--r--cmd-paste-buffer.c108
-rw-r--r--cmd-pipe-pane.c230
-rw-r--r--cmd-queue.c903
-rw-r--r--cmd-refresh-client.c304
-rw-r--r--cmd-rename-session.c81
-rw-r--r--cmd-rename-window.c62
-rw-r--r--cmd-resize-pane.c215
-rw-r--r--cmd-resize-window.c116
-rw-r--r--cmd-respawn-pane.c98
-rw-r--r--cmd-respawn-window.c95
-rw-r--r--cmd-rotate-window.c115
-rw-r--r--cmd-run-shell.c280
-rw-r--r--cmd-save-buffer.c117
-rw-r--r--cmd-select-layout.c149
-rw-r--r--cmd-select-pane.c238
-rw-r--r--cmd-select-window.c150
-rw-r--r--cmd-send-keys.c221
-rw-r--r--cmd-server-access.c147
-rw-r--r--cmd-set-buffer.c127
-rw-r--r--cmd-set-environment.c119
-rw-r--r--cmd-set-option.c239
-rw-r--r--cmd-show-environment.c143
-rw-r--r--cmd-show-messages.c107
-rw-r--r--cmd-show-options.c260
-rw-r--r--cmd-show-prompt-history.c108
-rw-r--r--cmd-source-file.c205
-rw-r--r--cmd-split-window.c220
-rw-r--r--cmd-swap-pane.c148
-rw-r--r--cmd-swap-window.c94
-rw-r--r--cmd-switch-client.c142
-rw-r--r--cmd-unbind-key.c104
-rw-r--r--cmd-wait-for.c264
-rw-r--r--cmd.c872
-rw-r--r--colour.c1073
-rw-r--r--compat.h455
-rw-r--r--compat/asprintf.c64
-rw-r--r--compat/base64.c315
-rw-r--r--compat/bitstring.h128
-rw-r--r--compat/cfmakeraw.c33
-rw-r--r--compat/clock_gettime.c37
-rw-r--r--compat/closefrom.c155
-rw-r--r--compat/daemon-darwin.c85
-rw-r--r--compat/daemon.c75
-rw-r--r--compat/err.c93
-rw-r--r--compat/explicit_bzero.c15
-rw-r--r--compat/fdforkpty.c34
-rw-r--r--compat/fgetln.c61
-rw-r--r--compat/forkpty-aix.c119
-rw-r--r--compat/forkpty-haiku.c82
-rw-r--r--compat/forkpty-hpux.c90
-rw-r--r--compat/forkpty-sunos.c92
-rw-r--r--compat/freezero.c31
-rw-r--r--compat/getdtablecount.c48
-rw-r--r--compat/getdtablesize.c29
-rw-r--r--compat/getline.c93
-rw-r--r--compat/getopt.c115
-rw-r--r--compat/getpeereid.c54
-rw-r--r--compat/getprogname.c43
-rw-r--r--compat/imsg-buffer.c309
-rw-r--r--compat/imsg.c302
-rw-r--r--compat/imsg.h113
-rw-r--r--compat/memmem.c65
-rw-r--r--compat/queue.h533
-rw-r--r--compat/reallocarray.c40
-rw-r--r--compat/recallocarray.c82
-rw-r--r--compat/setenv.c49
-rw-r--r--compat/setproctitle.c52
-rw-r--r--compat/strcasestr.c62
-rw-r--r--compat/strlcat.c57
-rw-r--r--compat/strlcpy.c53
-rw-r--r--compat/strndup.c41
-rw-r--r--compat/strnlen.c34
-rw-r--r--compat/strsep.c73
-rw-r--r--compat/strtonum.c67
-rw-r--r--compat/systemd.c58
-rw-r--r--compat/tree.h748
-rw-r--r--compat/unvis.c281
-rw-r--r--compat/utf8proc.c66
-rw-r--r--compat/vis.c242
-rw-r--r--compat/vis.h85
-rwxr-xr-xconfigure9280
-rw-r--r--configure.ac929
-rw-r--r--control-notify.c236
-rw-r--r--control.c1107
-rw-r--r--environ.c272
-rwxr-xr-xetc/compile348
-rwxr-xr-xetc/config.guess1473
-rwxr-xr-xetc/config.sub1836
-rwxr-xr-xetc/depcomp791
-rwxr-xr-xetc/install-sh501
-rwxr-xr-xetc/missing215
-rwxr-xr-xetc/ylwrap247
-rw-r--r--example_tmux.conf71
-rw-r--r--file.c819
-rw-r--r--format-draw.c1205
-rw-r--r--format.c5059
-rw-r--r--fuzz/input-fuzzer.c96
-rw-r--r--grid-reader.c429
-rw-r--r--grid-view.c235
-rw-r--r--grid.c1461
-rw-r--r--input-keys.c664
-rw-r--r--input.c2796
-rw-r--r--job.c415
-rw-r--r--key-bindings.c682
-rw-r--r--key-string.c466
-rw-r--r--layout-custom.c368
-rw-r--r--layout-set.c487
-rw-r--r--layout.c1120
-rw-r--r--log.c166
-rw-r--r--mdoc2man.awk370
-rw-r--r--menu.c449
-rw-r--r--mode-tree.c1208
-rw-r--r--names.c172
-rw-r--r--notify.c300
-rw-r--r--options-table.c1281
-rw-r--r--options.c1193
-rw-r--r--osdep-aix.c95
-rw-r--r--osdep-cygwin.c87
-rw-r--r--osdep-darwin.c108
-rw-r--r--osdep-dragonfly.c132
-rw-r--r--osdep-freebsd.c208
-rw-r--r--osdep-haiku.c52
-rw-r--r--osdep-hpux.c39
-rw-r--r--osdep-linux.c102
-rw-r--r--osdep-netbsd.c169
-rw-r--r--osdep-openbsd.c158
-rw-r--r--osdep-sunos.c112
-rw-r--r--osdep-unknown.c39
-rw-r--r--paste.c326
-rw-r--r--popup.c815
-rw-r--r--proc.c392
-rw-r--r--regsub.c120
-rw-r--r--resize.c467
-rw-r--r--screen-redraw.c859
-rw-r--r--screen-write.c2146
-rw-r--r--screen.c705
-rw-r--r--server-acl.c186
-rw-r--r--server-client.c3212
-rw-r--r--server-fn.c474
-rw-r--r--server.c554
-rw-r--r--session.c749
-rw-r--r--spawn.c484
-rw-r--r--status.c2004
-rw-r--r--style.c318
-rw-r--r--tmux-protocol.h114
-rw-r--r--tmux.16830
-rw-r--r--tmux.c520
-rw-r--r--tmux.h3291
-rw-r--r--tty-acs.c269
-rw-r--r--tty-features.c400
-rw-r--r--tty-keys.c1404
-rw-r--r--tty-term.c844
-rw-r--r--tty.c2961
-rw-r--r--utf8.c586
-rw-r--r--window-buffer.c543
-rw-r--r--window-client.c418
-rw-r--r--window-clock.c286
-rw-r--r--window-copy.c5575
-rw-r--r--window-customize.c1512
-rw-r--r--window-tree.c1340
-rw-r--r--window.c1620
-rw-r--r--xmalloc.c161
-rw-r--r--xmalloc.h48
208 files changed, 115209 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..b72d1ec
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,3415 @@
+CHANGES FROM 3.3 TO 3.3a
+
+* Do not crash when run-shell produces output from a config file.
+
+* Do not unintentionally turn off all mouse mode when button mode is also
+ present.
+
+CHANGES FROM 3.2a TO 3.3
+
+* Add an ACL list for users connecting to the tmux socket. Users may be
+ forbidden from attaching, forced to attach read-only, or allowed to attach
+ read-write. A new command, server-access, configures the list. File system
+ permissions must still be configured manually.
+
+* Emit window-layout-changed on swap-pane.
+
+* Better error reporting when applying custom layouts.
+
+* Handle ANSI escape sequences in run-shell output.
+
+* Add pane_start_path to match start_command.
+
+* Set PWD so shells have a hint about the real path.
+
+* Do not allow pipe-pane on dead panes.
+
+* Do not report mouse positions (incorrectly) above the maximum of 223 in
+ normal mouse mode.
+
+* Add an option (default off) to control the passthrough escape sequence.
+
+* Support more mouse buttons when the terminal sends them.
+
+* Add a window-resized hook which is fired when the window is actually resized
+ which may be later than the client resize.
+
+* Add next_session_id format with the next session ID.
+
+* Add formats for client and server UID and user.
+
+* Add argument to refresh-client -l to forward clipboard to a pane.
+
+* Add remain-on-exit-format to set text shown when pane is dead.
+
+* With split-window -f use percentages of window size not pane size.
+
+* Add an option (fill-character) to set the character used for unused areas of
+ a client.
+
+* Add an option (scroll-on-clear) to control if tmux scrolls into history on
+ clear.
+
+* Add a capability for OSC 7 and use it similarly to how the title is set (and
+ controlled by the same set-titles option).
+
+* Add support for systemd socket activation (where systemd creates the Unix
+ domain socket for tmux rather than tmux creating it). Build with
+ --enable-systemd.
+
+* Add an option (pane-border-indicators) to select how the active pane is shown
+ on the pane border (colour, arrows or both).
+
+* Support underscore styles with capture-pane -e.
+
+* Make pane-border-format a pane option rather than window.
+
+* Respond to OSC 4 queries
+
+* Fix g/G keys in modes to do the same thing as copy mode (and vi).
+
+* Bump the time terminals have to respond to device attributes queries to three
+ seconds.
+
+* If automatic-rename is off, allow the rename escape sequence to set an empty
+ name.
+
+* Trim menu item text more intelligently.
+
+* Add cursor-style and cursor-colour options to set the default cursor style
+ and colour.
+
+* Accept some useful and non-conflicting emacs keys in vi normal mode at the
+ command prompt.
+
+* Add a format modifier (c) to force a colour to RGB.
+
+* Add -s and -S to display-popup to set styles, -b to set lines and -T to set
+ popup title. New popup-border-lines, popup-border-style and popup-style
+ options set the defaults.
+
+* Add -e flag to set an environment variable for a popup.
+
+* Make send-keys without arguments send the key it is bound to (if bound to a
+ key).
+
+* Try to leave terminal cursor at the right position even when tmux is drawing
+ its own cursor or selection (such as at the command prompt and in choose
+ mode) for people using screen readers and similar which can make use of it.
+
+* Change so that {} is converted to tmux commands immediately when parsed. This
+ means it must contain valid tmux commands. For commands which expand %% and
+ %%%, this now only happens within string arguments. Use of nested aliases
+ inside {} is now forbidden. Processing of commands given in quotes remains
+ the same.
+
+* Disable evports on SunOS since they are broken.
+
+* Do not expand the file given with tmux -f so it can contain :s.
+
+* Bump FORMAT_LOOP_LIMIT and add a log message when hit.
+
+* Add a terminal feature for the mouse (since FreeBSD termcap does not have kmous).
+
+* Forbid empty session names.
+
+* Improve error reporting when the tmux /tmp directory cannot be created or
+ used.
+
+* Give #() commands a one second grace period where the output is empty before
+ telling the user they aren't doing anything ("not ready").
+
+* When building, pick default-terminal from the first of tmux-256color, tmux,
+ screen-256color, screen that is available on the build system (--with-TERM
+ can override).
+
+* Do not close popups on resize, instead adjust them to fit.
+
+* Add a client-active hook.
+
+* Make window-linked and window-unlinked window options.
+
+* Do not configure on macOS without the user making a choice about utf8proc
+ (either --enable-utf8proc or --disable-utf8proc).
+
+* Do not freeze output in panes when a popup is open, let them continue to
+ redraw.
+
+* Add pipe variants of the line copy commands.
+
+* Change copy-line and copy-end-of-line not to cancel and add -and-cancel
+ variants, like the other copy commands.
+
+* Support the OSC palette-setting sequences in popups.
+
+* Add a pane-colours array option to specify the defaults palette.
+
+* Add support for Unicode zero-width joiner.
+
+* Make newline a style delimiter as well so they can cross multiple lines for
+ readability in configuration files.
+
+* Change focus to be driven by events rather than scanning panes so the
+ ordering of in and out is consistent.
+
+* Add display-popup -B to open a popup without a border.
+
+* Add a menu for popups that can be opened with button three outside the popup
+ or on the left or top border. Resizing now only works on the right and bottom
+ borders or when using Meta. The menu allows a popup to be closed, expanded to
+ the full size of the client, centered in the client or changed into a pane.
+
+* Make command-prompt and confirm-before block by default (like run-shell). A
+ new -b flags runs them in the background as before. Also set return code for
+ confirm-before.
+
+* Change cursor style handling so tmux understands which sequences contain
+ blinking and sets the flag appropriately, means that it works whether cnorm
+ disables blinking or not. This now matches xterm's behaviour.
+
+* More accurate vi(1) word navigation in copy mode and on the status line. This
+ changes the meaning of the word-separators option: setting it to the empty
+ string is equivalent to the previous behavior.
+
+* Add -F for command-prompt and use it to fix "Rename" on the window menu.
+
+* Add different command histories for different types of prompts ("command",
+ "search" etc).
+
+CHANGES FROM 3.2 TO 3.2a
+
+* Add an "always" value for the "extended-keys" option; if set then tmux will
+ forward extended keys to applications even if they do not request them.
+
+* Add a "mouse" terminal feature so tmux can enable the mouse on terminals
+ where it is known to be supported even if terminfo(5) says otherwise.
+
+* Do not expand the filename given to -f so it can contain colons.
+
+* Fixes for problems with extended keys and modifiers, scroll region,
+ source-file, crosscompiling, format modifiers and other minor issues.
+
+CHANGES FROM 3.1c TO 3.2
+
+* Add a flag to disable keys to close a message.
+
+* Permit shortcut keys in buffer, client, tree modes to be configured with a
+ format (-K flag to choose-buffer, choose-client, choose-tree).
+
+* Add a current_file format for the config file being parsed.
+
+* When display-message used in config file, show the message after the config
+ file finishes.
+
+* Add client-detached notification in control mode.
+
+* Improve performance of format evaluation.
+
+* Make jump command support UTF-8 in copy mode.
+
+* Support X11 colour names and other colour formats for OSC 10 and 11.
+
+* Add "pipe" variants of "copy-pipe" commands which do not copy.
+
+* Include "focused" in client flags.
+
+* Send Unicode directional isolate characters around horizontal pane borders if
+ the terminal supports UTF-8 and an extension terminfo(5) capability "Bidi" is
+ present.
+
+* Add a -S flag to new-window to make it select the existing window if one
+ with the given name already exists rather than failing with an error.
+
+* Add a format modifier to check if a window or session name exists (N/w or
+ N/s).
+
+* Add compat clock_gettime for older macOS.
+
+* Add a no-detached choice to detach-on-destroy which detaches only if there
+ are no other detached sessions to switch to.
+
+* Add rectangle-on and rectangle-off copy mode commands.
+
+* Change so that window_flags escapes # automatically. A new format
+ window_raw_flags contains the old unescaped version.
+
+* Add -N flag to never start server even if command would normally do so.
+
+* With incremental search, start empty and only repeat the previous search if
+ the user tries to search again with an empty prompt.
+
+* Add a value for remain-on-exit that only keeps the pane if the program
+ failed.
+
+* Add a -C flag to run-shell to use a tmux command rather than a shell command.
+
+* Do not list user options with show-hooks.
+
+* Remove current match indicator in copy mode which can't work anymore since we
+ only search the visible region.
+
+* Make synchronize-panes a pane option and add -U flag to set-option to unset
+ an option on all panes.
+
+* Make replacement of ##s consistent when drawing formats, whether followed by
+ [ or not. Add a flag (e) to the q: format modifier to double up #s.
+
+* Add -N flag to display-panes to ignore keys.
+
+* Change how escaping is processed for formats so that ## and # can be used in
+ styles.
+
+* Add a 'w' format modifier for string width.
+
+* Add support for Haiku.
+
+* Expand menu and popup -x and -y as formats.
+
+* Add numeric comparisons for formats.
+
+* Fire focus events even when the pane is in a mode.
+
+* Add -O flag to display-menu to not automatically close when all mouse buttons
+ are released.
+
+* Allow fnmatch(3) wildcards in update-environment.
+
+* Disable nested job expansion so that the result of #() is not expanded again.
+
+* Use the setal capability as well as (tmux's) Setulc.
+
+* Add -q flag to unbind-key to hide errors.
+
+* Allow -N without a command to change or add a note to an existing key.
+
+* Add a -w flag to set- and load-buffer to send to clipboard using OSC 52.
+
+* Add -F to set-environment and source-file.
+
+* Allow colour to be spelt as color in various places.
+
+* Add n: modifier to get length of a format.
+
+* Respond to OSC colour requests if a colour is available.
+
+* Add a -d option to display-message to set delay.
+
+* Add a way for control mode clients to subscribe to a format and be notified
+ of changes rather than having to poll.
+
+* Add some formats for search in copy mode (search_present, search_match).
+
+* Do not wait on shutdown for commands started with run -b.
+
+* Add -b flags to insert a window before (like the existing -a for after) to
+ break-pane, move-window, new-window.
+
+* Make paste -p the default for ].
+
+* Add support for pausing a pane when the output buffered for a control mode
+ client gets too far behind. The pause-after flag with a time is set on the
+ pane with refresh-client -f and a paused pane may be resumed with
+ refresh-client -A.
+
+* Allow strings in configuration files to span multiple lines - newlines and
+ any leading whitespace are removed, as well as any following comments that
+ couldn't be part of a format. This allows long formats or other strings to be
+ annotated and indented.
+
+* Instead of using a custom parse function to process {} in configuration
+ files, treat as a set of statements the same as outside {} and convert back
+ to a string as the last step. This means the rules are consistent inside and
+ outside {}, %if and friends work at the right time, and the final result
+ isn't littered with unnecessary newlines.
+
+* Add support for extended keys - both xterm(1)'s CSI 27 ~ sequence and the
+ libtickit CSI u sequence are accepted; only the latter is output. tmux will
+ only attempt to use these if the extended-keys option is on and it can detect
+ that the terminal outside supports them (or is told it does with the
+ "extkeys" terminal feature).
+
+* Add an option to set the pane border lines style from a choice of single
+ lines (ACS or UTF-8), double or heavy (UTF-8), simple (plain ASCII) or number
+ (the pane numbers). Lines that won't work on a non-UTF-8 terminal are
+ translated back into ACS when they are output.
+
+* Make focus events update the latest client (like a key press).
+
+* Store UTF-8 characters differently to reduce memory use.
+
+* Fix break-pane -n when only one pane in the window.
+
+* Instead of sending all data to control mode clients as fast as possible, add
+ a limit of how much data will be sent to the client and try to use it for
+ panes with some degree of fairness.
+
+* Add an active-pane client flag (set with attach-session -f, new-session -f
+ or refresh-client -f). This allows a client to have an independent active
+ pane for interactive use (the window client pane is still used for many
+ things however).
+
+* Add a mark to copy mode, this is set with the set-mark command (bound to X)
+ and appears with the entire line shown using copy-mode-mark-style and the
+ marked character in reverse. The jump-to-mark command (bound to M-x) swaps
+ the mark and the cursor positions.
+
+* Add a -D flag to make the tmux server run in the foreground and not as a
+ daemon.
+
+* Do not loop forever in copy mode when search finds an empty match.
+
+* Fix the next-matching-bracket logic when using vi(1) keys.
+
+* Add a customize mode where options may be browsed and changed, includes
+ adding a brief description of each option. Bound to C-b C by default.
+
+* Change message log (C-b ~) so there is one for the server rather than one per
+ client and it remains after detach, and make it useful by logging every
+ command.
+
+* Add M-+ and M-- to tree mode to expand and collapse all.
+
+* Change the existing client flags for control mode to apply for any client,
+ use the same mechanism for the read-only flag and add an ignore-size flag.
+
+ refresh-client -F has become -f (-F stays for backwards compatibility) and
+ attach-session and switch-client now have -f flags also. A new format
+ client_flags lists the flags and is shown by list-clients by default.
+
+ This separates the read-only flag from "ignore size" behaviour (new
+ ignore-size) flag - both behaviours are useful in different circumstances.
+
+ attach -r and switchc -r remain and set or toggle both flags together.
+
+* Store and restore cursor position when copy mode is resized.
+
+* Export TERM_PROGRAM and TERM_PROGRAM_VERSION like various other terminals.
+
+* Add formats for after hook command arguments: hook_arguments with all the
+ arguments together; hook_argument_0, hook_argument_1 and so on with
+ individual arguments; hook_flag_X if flag -X is present; hook_flag_X_0,
+ hook_flag_X_1 and so on if -X appears multiple times.
+
+* Try to search the entire history first for up to 200 ms so a search count can
+ be shown. If it takes too long, search the visible text only.
+
+* Use VIS_CSTYLE for paste buffers also (show \012 as \n).
+
+* Change default formats for tree mode, client mode and buffer mode to be more
+ compact and remove some clutter.
+
+* Add a key (e) in buffer mode to open the buffer in an editor. The buffer
+ contents is updated when the editor exits.
+
+* Add -e flag for new-session to set environment variables, like the same flag
+ for new-window.
+
+* Improve search match marking in copy mode. Two new options
+ copy-mode-match-style and copy-mode-current-match-style to set the style for
+ matches and for the current match respectively. Also a change so that if a
+ copy key is pressed with no selection, the current match (if any) is copied.
+
+* Sanitize session names like window names instead of forbidding invalid ones.
+
+* Check if the clear terminfo(5) capability starts with CSI and if so then
+ assume the terminal is VT100-like, rather than relying on the XT capability.
+
+* Improve command prompt tab completion and add menus both for strings and -t
+ and -s (when used without a trailing space). command-prompt has additional
+ flags for only completing a window (-W) and a target (-T), allowing C-b ' to
+ only show windows and C-b . only targets.
+
+* Change all the style options to string options so they can support formats.
+ Change pane-active-border-style to use this to change the border colour when
+ in a mode or with synchronize-panes on. This also implies a few minor changes
+ to existing behaviour:
+
+ - set-option -a with a style option automatically inserts a comma between the
+ old value and appended text.
+
+ - OSC 10 and 11 no longer set the window-style option, instead they store the
+ colour internally in the pane data and it is used as the default when the
+ option is evaluated.
+
+ - status-fg and -bg now override status-style instead of the option values
+ being changed.
+
+* Add extension terminfo(5) capabilities for margins and focus reporting.
+
+* Try $XDG_CONFIG_HOME/tmux/tmux.conf as well as ~/.config/tmux/tmux.conf for
+ configuration file (the search paths are in TMUX_CONF in Makefile.am).
+
+* Remove the DSR 1337 iTerm2 extension and replace by the extended device
+ attributes sequence (CSI > q) supported by more terminals.
+
+* Add a -s flag to copy-mode to specify a different pane for the source
+ content. This means it is possible to view two places in a pane's history at
+ the same time in different panes, or view the history while still using the
+ pane. Pressing r refreshes the content from the source pane.
+
+* Add an argument to list-commands to show only a single command.
+
+* Change copy mode to make copy of the pane history so it does not need to
+ freeze the pane.
+
+* Restore pane_current_path format from portable tmux on OpenBSD.
+
+* Wait until the initial command sequence is done before sending a device
+ attributes request and other bits that prompt a reply from the terminal. This
+ means that stray replies are not left on the terminal if the command has
+ attached and then immediately detached and tmux will not be around to receive
+ them.
+
+* Add a -f filter argument to the list commands like choose-tree.
+
+* Move specific hooks for panes to pane options and windows for window options
+ rather than all hooks being session options. These hooks are now window options:
+
+ window-layout-changed
+ window-linked
+ window-pane-changed
+ window-renamed
+ window-unlinked
+
+ And these are now pane options:
+
+ pane-died
+ pane-exited
+ pane-focus-in
+ pane-focus-out
+ pane-mode-changed
+ pane-set-clipboard
+
+ Any existing configurations using these hooks on a session rather than
+ globally (that is, set-hook or set-option without -g) may need to be changed.
+
+* Show signal names when a process exits with remain-on-exit on platforms which
+ have a way to get them.
+
+* Start menu with top item selected if no mouse and use mode-style for the
+ selected item.
+
+* Add a copy-command option and change copy-pipe and friends to pipe to it if
+ used without arguments, allows all the default copy key bindings to be
+ changed to pipe with one option rather than needing to change each key
+ binding individually.
+
+* Tidy up the terminal detection and feature code and add named sets of
+ terminal features, each of which are defined in one place and map to a
+ builtin set of terminfo(5) capabilities. Features can be specified based on
+ TERM with a new terminal-features option or with the -T flag when running
+ tmux. tmux will also detect a few common terminals from the DA and DSR
+ responses.
+
+ This is intended to make it easier to configure tmux's use of terminfo(5)
+ even in the presence of outdated ncurses(3) or terminfo(5) databases or for
+ features which do not yet have a terminfo(5) entry. Instead of having to grok
+ terminfo(5) capability names and what they should be set to in the
+ terminal-overrides option, the user can hopefully just give tmux a feature
+ name and let it do the right thing.
+
+ The terminal-overrides option remains both for backwards compatibility and to
+ allow tweaks of individual capabilities.
+
+* Support mintty's application escape sequence (means tmux doesn't have to
+ delay to wait for Escape, so no need to reduce escape-time when using
+ mintty).
+
+* Change so main-pane-width and height can be given as a percentage.
+
+* Support for the iTerm2 synchronized updates feature (allows the terminal to
+ avoid unnecessary drawing while output is still in progress).
+
+* Make the mouse_word and mouse_line formats work in copy mode and enable the
+ default pane menu in copy mode.
+
+* Add a -T flag to resize-pane to trim lines below the cursor, moving lines out
+ of the history.
+
+* Add a way to mark environment variables as "hidden" so they can be used by
+ tmux (for example in formats) but are not set in the environment for new
+ panes. set-environment and show-environment have a new -h flag and there is a
+ new %hidden statement for the configuration file.
+
+* Change default position for display-menu -x and -y to centre rather than top
+ left.
+
+* Add support for per-client transient popups, similar to menus but which are
+ connected to an external command (like a pane). These are created with new
+ command display-popup.
+
+* Change double and triple click bindings so that only one is fired (previously
+ double click was fired on the way to triple click). Also add default double
+ and triple click bindings to copy the word or line under the cursor and
+ change the existing bindings in copy mode to do the same.
+
+* Add a default binding for button 2 to paste.
+
+* Add -d flag to run-shell to delay before running the command and allow it to
+ be used without a command so it just delays.
+
+* Add C-g to cancel command prompt with vi keys as well as emacs, and q in
+ command mode.
+
+* When the server socket is given with -S, create it with umask 177 instead of
+ 117 (because it may not be in a safe directory like the default directory in
+ /tmp).
+
+* Add a copy-mode -H flag to hide the position marker in the top right.
+
+* Add number operators for formats (+, -, *, / and m),
+
+CHANGED FROM 3.1b TO 3.1c
+
+* Do not write after the end of the array and overwrite the stack when
+ colon-separated SGR sequences contain empty arguments.
+
+CHANGES FROM 3.1a TO 3.1b
+
+* Fix build on systems without sys/queue.h.
+
+* Fix crash when allow-rename is on and an empty name is set.
+
+CHANGES FROM 3.1 TO 3.1a
+
+* Do not close stdout prematurely in control mode since it is needed to print
+ exit messages. Prevents hanging when detaching with iTerm2.
+
+CHANGES FROM 3.0a TO 3.1
+
+* Only search the visible part of the history when marking (highlighting)
+ search terms. This is much faster than searching the whole history and solves
+ problems with large histories. The count of matches shown is now the visible
+ matches rather than all matches.
+
+* Search using regular expressions in copy mode. search-forward and
+ search-backward use regular expressions by default; the incremental versions
+ do not.
+
+* Turn off mouse mode 1003 as well as the rest when exiting.
+
+* Add selection_active format for when the selection is present but not moving
+ with the cursor.
+
+* Fix dragging with modifier keys, so binding keys such as C-MouseDrag1Pane and
+ C-MouseDragEnd1Pane now work.
+
+* Add -a to list-keys to also list keys without notes with -N.
+
+* Do not jump to next word end if already on a word end when selecting a word;
+ fixes select-word with single character words and vi(1) keys.
+
+* Fix top and bottom pane calculation with pane border status enabled.
+
+* Add support for adding a note to a key binding (with bind-key -N) and use
+ this to add descriptions to the default key bindings. A new -N flag to
+ list-keys shows key bindings with notes. Change the default ? binding to use
+ this to show a readable summary of keys. Also extend command-prompt to return
+ the name of the key pressed and add a default binding (/) to show the note
+ for the next key pressed.
+
+* Add support for the iTerm2 DSR 1337 sequence to get the terminal version.
+
+* Treat plausible but invalid keys (like C-BSpace) as literal like any other
+ unrecognised string passed to send-keys.
+
+* Detect iTerm2 and enable use of DECSLRM (much faster with horizontally split
+ windows).
+
+* Add -Z to default switch-client command in tree mode.
+
+* Add ~ to quoted characters for %%%.
+
+* Document client exit messages in the manual page.
+
+* Do not let read-only clients limit the size, unless all clients are
+ read-only.
+
+* Add a number of new formats to inspect what sessions and clients a window is
+ present or active in.
+
+* Change file reading and writing to go through the client if necessary. This
+ fixes commands like "tmux loadb /dev/fd/X". Also modify source-file to
+ support "-" for standard input, like load-buffer and save-buffer.
+
+* Add ~/.config/tmux/tmux.conf to the default search path for configuration
+ files.
+
+* Bump the escape sequence timeout to five seconds to allow for longer
+ legitimate sequences.
+
+* Make a best effort to set xpixel and ypixel for each pane and add formats for
+ them.
+
+* Add push-default to status-left and status-right in status-format[0].
+
+* Do not clear search marks on cursor movement with vi(1) keys.
+
+* Add p format modifier for padding to width and allow multiple substitutions
+ in a single format.
+
+* Add -f for full size to join-pane (like split-window).
+
+* Do not use bright when emulating 256 colours on an 8 colour terminal because
+ it is also bold on some terminals.
+
+* Make select-pane -P set window-active-style also to match previous behaviour.
+
+* Do not truncate list-keys output.
+
+* Turn automatic-rename back on if the \033k rename escape sequence is used
+ with an empty name.
+
+* Add support for percentage sizes for resize-pane ("-x 10%"). Also change
+ split-window and join-pane -l to accept similar percentages and deprecate the
+ -p flag.
+
+* Add -F flag to send-keys to expand formats in search-backward and forward
+ copy mode commands and copy_cursor_word and copy_cursor_line formats for word
+ and line at cursor in copy mode. Use for default # and * binding with vi(1)
+ keys.
+
+* Add formats for word and line at cursor position in copy mode.
+
+* Add formats for cursor and selection position in copy mode.
+
+* Support all the forms of RGB colour strings in OSC sequences rather than
+ requiring two digits.
+
+* Limit lazy resize to panes in attached sessions only.
+
+* Add an option to set the key sent by backspace for those whose system uses ^H
+ rather than ^?.
+
+* Change new-session -A without a session name (that is, no -s option also) to
+ attach to the best existing session like attach-session rather than a new
+ one.
+
+* Add a "latest" window-size option which tries to size windows based on the
+ most recently used client. This is now the default.
+
+* Add simple support for OSC 7 (result is available in the pane_path format).
+
+* Add push-default and pop-default for styles which change the colours and
+ attributes used for #[default]. These are used in status-format to restore
+ the behaviour of window-status-style being the default for
+ window-status-format.
+
+* Add window_marked_flag.
+
+* Add cursor-down-and-cancel in copy mode.
+
+* Default to previous search string for search-forward and search-backward.
+
+* Add -Z flag to rotate-window, select-pane, swap-pane, switch-client to
+ preserve zoomed state.
+
+* Add -N to capture-pane to preserve trailing spaces.
+
+* Add reverse sorting in tree, client and buffer modes.
+
+CHANGES FROM 3.0 TO 3.0a
+
+* Do not require REG_STARTEND.
+
+* Respawn panes or windows correctly if default-command is set.
+
+* Add missing option for after-kill-pane hook.
+
+* Fix for crash with a format variable that doesn't exist.
+
+* Do not truncate list-keys output on some platforms.
+
+* Do not crash when restoring a layout with only one pane.
+
+CHANGES FROM 2.9 TO 3.0
+
+* Workaround invalid layout strings generated by older tmux versions and add
+ some additional sanity checks
+
+* xterm 348 now disables margins when resized, so send DECLRMM again after
+ resize.
+
+* Add support for the SD (scroll down) escape sequence.
+
+* Expand arguments to C and s format modifiers to match the m modifier.
+
+* Add support for underscore colours (Setulc capability must be added with
+ terminal-overrides as described in tmux(1)).
+
+* Add a "fill" style attribute for the fill colour of the drawing area (where
+ appropriate).
+
+* New -H flag to send-keys to send literal keys.
+
+* Format variables for pane mouse modes (mouse_utf8_flag and mouse_sgr_flag)
+ and for origin mode (origin_flag).
+
+* Add -F to refresh-client for flags for control mode clients, only one flag
+ (no-output) supported at the moment.
+
+* Add a few vi(1) keys for menus.
+
+* Add pane options, set with set-option -p and displayed with show-options -p.
+ Pane options inherit from window options (so every pane option is also
+ a window option). The pane style is now configured by setting window-style
+ and window-active-style in the pane options; select-pane -P and -g now change
+ the option but are no longer documented.
+
+* Do not document set-window-option and show-window-options. set-option -w and
+ show-options -w should be used instead.
+
+* Add a -A flag to show-options to show parent options as well (they are marked
+ with a *).
+
+* Resize panes lazily - do not resize unless they are in an attached, active
+ window.
+
+* Add regular expression support for the format search, match and substitute
+ modifiers and make them able to ignore case. find-window now accepts -r to
+ use regular expressions.
+
+* Do not use $TMUX to find the session because for windows in multiple sessions
+ it is wrong as often as it is right, and for windows in one session it is
+ pointless. Instead use TMUX_PANE if it is present.
+
+* Do not always resize the window back to its original size after applying a
+ layout, keep it at the layout size until it must be resized (for example when
+ attached and window-size is not manual).
+
+* Add new-session -X and attach-session -x to send SIGHUP to parent when
+ detaching (like detach-client -P).
+
+* Support for octal escapes in strings (such as \007) and improve list-keys
+ output so it parses correctly if copied into a configuration file.
+
+* INCOMPATIBLE: Add a new {} syntax to the configuration file. This is a string
+ similar to single quotes but also includes newlines and allows commands that
+ take other commands as string arguments to be expressed more clearly and
+ without additional escaping.
+
+ A literal { and } or a string containing { or } must now be escaped or
+ quoted, for example '{' and '}' instead of { or }, or 'X#{foo}' instead of
+ X#{foo}.
+
+* New <, >, <= and >= comparison operators for formats.
+
+* Improve escaping of special characters in list-keys output.
+
+* INCOMPATIBLE: tmux's configuration parsing has changed to use yacc(1). There
+ is one incompatible change: a \ on its own must be escaped or quoted as
+ either \\ or '\' (the latter works on older tmux versions).
+
+ Entirely the same parser is now used for parsing the configuration file
+ and for string commands. This means that constructs previously only
+ available in .tmux.conf, such as %if, can now be used in string commands
+ (for example, those given to if-shell - not commands invoked from the
+ shell, they are still parsed by the shell itself).
+
+* Add support for the overline attribute (SGR 53). The Smol capability is
+ needed in terminal-overrides.
+
+* Add the ability to create simple menus. Introduces new command
+ display-menu. Default menus are bound to MouseDown3 on the status line;
+ MouseDown3 or M-MouseDown3 on panes; MouseDown3 in tree, client and
+ buffer modes; and C-b < and >.
+
+* Allow panes to be empty (no command). They can be created either by piping to
+ split-window -I, or by passing an empty command ('') to split-window. Output
+ can be sent to an existing empty window with display-message -I.
+
+* Add keys to jump between matching brackets (emacs C-M-f and C-M-b, vi %).
+
+* Add a -e flag to new-window, split-window, respawn-window, respawn-pane to
+ pass environment variables into the newly created process.
+
+* Hooks are now stored in the options tree as array options, allowing them to
+ have multiple separate commands. set-hook and show-hooks remain but
+ set-option and show-options can now also be used (show-options will only show
+ hooks if given the -H flag). Hooks with multiple commands are run in index
+ order.
+
+* Automatically scroll if dragging to create a selection with the mouse and the
+ cursor reaches the top or bottom line.
+
+* Add -no-clear variants of copy-selection and copy-pipe which do not clear the
+ selection after copying. Make copy-pipe clear the selection by default to be
+ consistent with copy-selection.
+
+* Add an argument to copy commands to set the prefix for the buffer name, this
+ (for example) allows buffers for different sessions to be named separately.
+
+* Update session activity on focus event.
+
+* Pass target from source-file into the config file parser so formats in %if
+ and %endif have access to more useful variables.
+
+* Add the ability to infer an option type (server, session, window) from its
+ name to show-options (it was already present in set-option).
+
+CHANGES FROM 2.9 TO 2.9a
+
+* Fix bugs in select-pane and the main-horizontal and main-vertical layouts.
+
+CHANGES FROM 2.8 TO 2.9
+
+* Attempt to preserve horizontal cursor position as well as vertical with
+ reflow.
+
+* Rewrite main-vertical and horizontal and change layouts to better handle the
+ case where all panes won't fit into the window size, reduce problems with
+ pane border status lines and fix other bugs mostly found by Thomas Sattler.
+
+* Add format variables for the default formats in the various modes
+ (tree_mode_format and so on) and add a -a flag to display-message to list
+ variables with values.
+
+* Add a -v flag to display-message to show verbose messages as the format is
+ parsed, this allows formats to be debugged
+
+* Add support for HPA (\033[`).
+
+* Add support for origin mode (\033[?6h).
+
+* No longer clear history on RIS.
+
+* Extend the #[] style syntax and use that together with previous format
+ changes to allow the status line to be entirely configured with a single
+ option.
+
+ Now that it is possible to configure their content, enable the existing code
+ that lets the status line be multiple lines in height. The status option can
+ now take a value of 2, 3, 4 or 5 (as well as the previous on or off) to
+ configure more than one line. The new status-format array option configures
+ the format of each line, the default just references the existing status-*
+ options, although some of the more obscure status options may be eliminated
+ in time.
+
+ Additions to the #[] syntax are: "align" to specify alignment (left, centre,
+ right), "list" for the window list and "range" to configure ranges of text
+ for the mouse bindings.
+
+ The "align" keyword can also be used to specify alignment of entries in tree
+ mode and the pane status lines.
+
+* Add E: and T: format modifiers to expand a format twice (useful to expand the
+ value of an option).
+
+* The individual -fg, -bg and -attr options have been removed; they
+ were superseded by -style options in tmux 1.9.
+
+* Allow more than one mode to be opened in a pane. Modes are kept on a stack
+ and retrieved if the same mode is entered again. Exiting the active mode goes
+ back to the previous one.
+
+* When showing command output in copy mode, call it view mode instead (affects
+ pane_mode format).
+
+* Add -b to display-panes like run-shell.
+
+* Handle UTF-8 in word-separators option.
+
+* New "terminal" colour allowing options to use the terminal default colour
+ rather than inheriting the default from a parent option.
+
+* Do not move the cursor in copy mode when the mouse wheel is used.
+
+* Use the same working directory rules for jobs as new windows rather than
+ always starting in the user's home.
+
+* Allow panes to be one line or column in size.
+
+* Go to last line when goto-line number is out of range in copy mode.
+
+* Yank previously cut text if any with C-y in the command prompt, only use the
+ buffer if no text has been cut.
+
+* Add q: format modifier to quote shell special characters.
+
+* Add StatusLeft and StatusRight mouse locations (keys such as
+ MouseDown1StatusLeft) for the status-left and status-right areas of the
+ status line.
+
+* Add -Z to find-window.
+
+* Support for windows larger than the client. This adds two new options,
+ window-size and default-size, and a new command, resize-window. The
+ force-width and force-height options and the session_width and session_height
+ formats have been removed.
+
+ The new window-size option tells tmux how to work out the size of windows:
+ largest means it picks the size of the largest session, smallest the smallest
+ session (similar to the old behaviour) and manual means that it does not
+ automatically resize windows. aggressive-resize modifies the choice of
+ session for largest and smallest as it did before.
+
+ If a window is in a session attached to a client that is too small, only part
+ of the window is shown. tmux attempts to keep the cursor visible, so the part
+ of the window displayed is changed as the cursor moves (with a small delay,
+ to try and avoid excess redrawing when applications redraw status lines or
+ similar that are not currently visible).
+
+ Drawing windows which are larger than the client is not as efficient as those
+ which fit, particularly when the cursor moves, so it is recommended to avoid
+ using this on slow machines or networks (set window-size to smallest or
+ manual).
+
+ The resize-window command can be used to resize a window manually. If it is
+ used, the window-size option is automatically set to manual for the window
+ (undo this with "setw -u window-size"). resize-window works in a similar way
+ to resize-pane (-U -D -L -R -x -y flags) but also has -a and -A flags. -a
+ sets the window to the size of the smallest client (what it would be if
+ window-size was smallest) and -A the largest.
+
+ For the same behaviour as force-width or force-height, use resize-window -x
+ or -y.
+
+ If the global window-size option is set to manual, the default-size option is
+ used for new windows. If -x or -y is used with new-session, that sets the
+ default-size option for the new session.
+
+ The maximum size of a window is 10000x10000. But expect applications to
+ complain and higher memory use if making a window that big. The minimum size
+ is the size required for the current layout including borders.
+
+ The refresh-client command can be used to pan around a window, -U -D -L -R
+ moves up, down, left or right and -c returns to automatic cursor
+ tracking. The position is reset when the current window is changed.
+
+CHANGES FROM 2.7 TO 2.8
+
+* Make display-panes block the client until a pane is chosen or it
+ times out.
+
+* Clear history on RIS like most other terminals do.
+
+* Add an "Any" key to run a command if a key is pressed that is not
+ bound in the current key table.
+
+* Expand formats in load-buffer and save-buffer.
+
+* Add a rectangle_toggle format.
+
+* Add set-hook -R to run a hook immediately.
+
+* Add README.ja.
+
+* Add pane focus hooks.
+
+* Allow any punctuation as separator for s/x/y not only /.
+
+* Improve resizing with the mouse (fix resizing the wrong pane in some
+ layouts, and allow resizing multiple panes at the same time).
+
+* Allow , and } to be escaped in formats as #, and #}.
+
+* Add KRB5CCNAME to update-environment.
+
+* Change meaning of -c to display-message so the client is used if it
+ matches the session given to -t.
+
+* Fixes to : form of SGR.
+
+* Add x and X to choose-tree to kill sessions, windows or panes.
+
+CHANGES FROM 2.6 TO 2.7
+
+* Remove EVENT_* variables from environment on platforms where tmux uses them
+ so they do not pass on to panes.
+
+* Fixes for hooks at server exit.
+
+* Remove SGR 10 (was equivalent to SGR 0 but no other terminal seems to do
+ this).
+
+* Expand formats in window and session names.
+
+* Add -Z flag to choose-tree, choose-client, choose-buffer to automatically
+ zoom the pane when the mode is entered and unzoom when it exits, assuming the
+ pane is not already zoomed. This is now part of the default key bindings.
+
+* Add C-g to exit modes with emacs keys.
+
+* Add exit-empty option to exit server if no sessions (defaults to on).
+
+* Show if a filter is present in choose modes.
+
+* Add pipe-pane -I to to connect stdin of the child process.
+
+* Performance improvements for reflow.
+
+* Use RGB terminfo(5) capability to detect RGB colour terminals (the existing
+ Tc extension remains unchanged).
+
+* Support for ISO colon-separated SGR sequences.
+
+* Add select-layout -E to spread panes out evenly (bound to E key).
+
+* Support wide characters properly when reflowing.
+
+* Pass PWD to new panes as a hint to shells, as well as calling chdir().
+
+* Performance improvements for the various choose modes.
+
+* Only show first member of session groups in tree mode (-G flag to choose-tree
+ to show all).
+
+* Support %else in config files to match %if; from Brad Town in GitHub issue
+ 1071.
+
+* Fix "kind" terminfo(5) capability to be S-Down not S-Up.
+
+* Add a box around the preview label in tree mode.
+
+* Show exit status and time in the remain-on-exit pane text; from Timo
+ Boettcher in GitHub issue 1103.
+
+* Correctly use pane-base-index in tree mode.
+
+* Change the allow-rename option default to off.
+
+* Support for xterm(1) title stack escape sequences (GitHub issue 1075 from
+ Brad Town).
+
+* Correctly remove padding cells to fix a UTF-8 display problem (GitHub issue
+ 1090).
+
+CHANGES FROM 2.5 TO 2.6, 05 October 2017
+
+* Add select-pane -T to set pane title.
+
+* Fix memory leak when lines with BCE are removed from history.
+
+* Fix (again) the "prefer unattached" behaviour of attach-session.
+
+* Reorder how keys are checked to allow keys to be specified that have a
+ leading escape. GitHub issue 1048.
+
+* Support REP escape sequence (\033[b).
+
+* Run alert hooks based on options rather than always, and allow further bells
+ even if there is an existing bell.
+
+* Add -d flag to display-panes to override display-panes-time.
+
+* Add selection_present format when in copy mode (allows key bindings that do
+ something different if there is a selection).
+
+* Add pane_at_left, pane_at_right, pane_at_top and pane_at_bottom formats.
+
+* Make bell, activity and silence alerting more consistent by: removing the
+ bell-on-alert option; adding activity-action and silence-action options with
+ the same possible values as the existing bell-action; adding a "both" value
+ for the visual-bell, visual-activity and visual-silence options to trigger
+ both a bell and a message.
+
+* Add a pane_pipe format to show if pipe-pane is active.
+
+* Block signals between forking and resetting signal handlers so that the
+ libevent signal handler doesn't get called in the child and incorrectly write
+ into the signal pipe that it still shares with the parent. GitHub issue 1001.
+
+* Allow punctuation in pane_current_command.
+
+* Add -c for respawn-pane and respawn-window.
+
+* Wait for any remaining data to flush when a pane is closed while pipe-pane is
+ in use.
+
+* Fix working out current client with no target. GitHub issue 995.
+
+* Try to fallback to C.UTF-8 as well as en_US.UTF-8 when looking for a UTF-8
+ locale.
+
+* Add user-keys option for user-defined key escape sequences (mapped to User0
+ to User999 keys).
+
+* Add pane-set-clipboard hook.
+
+* FAQ file has moved out of repository to online.
+
+* Fix problem with high CPU usage when a client dies unexpectedly. GitHub issue
+ 941.
+
+* Do a dance on OS X 10.10 and above to return tmux to the user namespace,
+ allowing access to the clipboard.
+
+* Do not allow escape sequences which expect a specific terminator (APC, DSC,
+ OSC) to wait for forever - use a small timeout. This reduces the chance of
+ the pane locking up completely when sent garbage (cat /dev/random or
+ similar).
+
+* Support SIGUSR2 to toggle logging on a running server, also generate the
+ "out" log file with -vv not -vvvv.
+
+* Make set-clipboard a three state option: on (tmux both sends to outside
+ terminal and accepts from applications inside); external (tmux sends outside
+ but does not accept inside); and off.
+
+* Fix OSC 4 palette setting for bright foreground colours. GitHub issue 954.
+
+* Use setrgbf and setrgbb terminfo(5) capabilities to set RGB colours, if they
+ are available. (Tc is still supported as well.)
+
+* Fix redrawing panes when they are resized several times but end up with the
+ size unchanged (for example, splitw/resizep -Z/breakp).
+
+* Major rewrite of choose mode. Now includes preview, sorting, searching and
+ tagging; commands that can be executed directly from the mode (for example,
+ to delete one or more buffers); and filtering in tree mode.
+
+* choose-window and choose-session are now aliases of choose-tree (in the
+ command-alias option).
+
+* Support OSC 10 and OSC 11 to set foreground and background colours.
+
+* Check the U8 capability to determine whether to use UTF-8 line drawing
+ characters for ACS.
+
+* Some missing notifications for layout changes.
+
+* Control mode clients now do not affect session sizes until they issue
+ refresh-client -C. new-session -x and -y works with control clients even if
+ the session is not detached.
+
+* All new sessions that are unattached (whether with -d or started with no
+ terminal) are now created with size 80 x 24. Whether the status line is on or
+ off does not affect the size of new sessions until they are attached.
+
+* Expand formats in option names and add -F flag to expand them in option values.
+
+* Remember the search string for a pane even if copy mode is exited and entered
+ again.
+
+* Some further BCE fixes (scroll up, reverse index).
+
+* Improvements to how terminals are cleared (entirely or partially).
+
+CHANGES FROM 2.4 TO 2.5, 09 May 2017
+
+* Reset updated flag when restarting #() command so that new output is properly
+ recognised. GitHub issue 922.
+
+* Fix ECH with a background colour.
+
+* Do not rely on the terminal not moving the cursor after DL or EL.
+
+* Fix send-keys and send-prefix in copy-mode (so C-b C-b works). GitHub issue
+ 905.
+
+* Set the current pane for rotate-window so it works in command sequences.
+
+* Add pane_mode format.
+
+* Differentiate M-Up from Escape+Up when possible (that is, in terminals with
+ xterm(1) style function keys). GitHub issue 907.
+
+* Add session_stack and window_stack_index formats.
+
+* Some new control mode notifications and corresponding hooks:
+ pane-mode-changed, window-pane-changed, client-session-changed,
+ session-window-changed.
+
+* Format pane_search_string for last search term while in copy mode (useful
+ with command-prompt -I).
+
+* Fix a problem with high CPU usage and multiple clients with #(). GitHub issue
+ 889.
+
+* Fix UTF-8 combining characters in column 0.
+
+* Fix reference counting so that panes are properly destroyed and their
+ processes killed.
+
+* Clamp SU (CSI S) parameter to work around a bug in Konsole.
+
+* Tweak line wrapping in full width panes to play more nicely with terminal
+ copy and paste.
+
+* Fix when we emit SGR 0 in capture-pane -e.
+
+* Do not change TERM until after config file parsing has finished, so that
+ commands run inside the config file can use it to make decisions (typically
+ about default-terminal).
+
+* Make the initial client wait until config file parsing has finished to avoid
+ racing with commands.
+
+* Fix core when if-shell fails.
+
+* Only use ED to clear screen if the pane is at the bottom.
+
+* Fix multibyte UTF-8 output.
+
+* Code improvements around target (-t) resolution.
+
+* Change how the default target (for commands without -t) is managed across
+ command sequences: now it is set up at the start and commands are required
+ to update it if needed. Fixes binding command sequences to mouse keys.
+
+* Make if-shell from the config file work correctly.
+
+* Change to always check the root key table if no binding is found in the
+ current table (prefix table or copy-mode table or whatever). This means that
+ root key bindings will take effect even in copy mode, if not overridden by a
+ copy mode key binding.
+
+* Fix so that the history file works again.
+
+* Run config file without a client rather than using the first client, restores
+ previous behaviour.
+
+* If a #() command doesn't exit, continue to read from it and use its last full
+ line of output.
+
+* Handle slow terminals and fast output better: when the amount of data
+ outstanding gets too large, discard output until it is drained and we are
+ able to do a full redraw. Prevents tmux sitting on a huge buffer that the
+ terminal will take forever to consume.
+
+* Do not redraw a client unless we realistically think it can accept the data -
+ defer redraws until the client has nothing else waiting to write.
+
+CHANGES FROM 2.3 TO 2.4, 20 April 2017
+
+Incompatible Changes
+====================
+
+* Key tables have undergone major changes. Mode key tables are no longer
+ separate from the main key tables. All mode key tables have been removed,
+ together with the -t flag to bind-key and unbind-key.
+
+ The emacs-edit, vi-edit, emacs-choose and vi-choose tables have been replaced
+ by fixed key bindings in the command prompt and choose modes. The mode-keys
+ and status-keys options remain.
+
+ The emacs-copy and vi-copy tables have been replaced by the copy-mode and
+ copy-mode-vi tables. Commands are sent using the -X and -N flags to
+ send-keys. So the following:
+
+ bind -temacs-copy C-Up scroll-up
+ bind -temacs-copy -R5 WheelUpPane scroll-up
+
+ Becomes:
+
+ bind -Tcopy-mode C-Up send -X scroll-up
+ bind -Tcopy-mode WheelUpPane send -N5 -X scroll-up
+
+ These changes allows the full command parser (including command sequences) and
+ command set to be used - for example, the normal command prompt with editing
+ and history is now used for searching, jumping, and so on instead of a custom
+ one. The default C-r binding is now:
+
+ 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
+ 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
+ have after hooks (they are no longer automatic). Additional hooks have been
+ added.
+* The xterm-keys option now defaults to on.
+
+Normal Changes
+==============
+
+* Support for mouse double and triple clicks.
+* BCE (Background Colour Erase) is now supported.
+* All occurrences of a search string in copy mode are now highlighted;
+ additionally, the number of search results is displayed. The highlighting
+ updates interactively with the default emacs key bindings (incremental
+ search).
+* source-file now understands glob patterns.
+* Formats now have simple comparisons:
+
+ #{==:a,b}
+ #{!=:a,b}
+
+* There are the following new formats:
+
+ - #{version} -- the tmux server version;
+ - #{client_termtype} -- the terminal type of the client;
+ - #{client_name} -- the name of a client;
+ - #{client_written} -- the number of bytes written to the client.
+
+* The configuration file now accepts %if/%endif conditional blocks which are
+ processed when it is parsed; the argument is a format string (useful with the
+ new format comparison options).
+* detach-client now has -E to execute a command replacing the client instead of
+ exiting.
+* Add support for custom command aliases, this is an array option which
+ contains items of the form "alias=command". This is consulted when an
+ unknown command is parsed.
+* break-pane now has -n to specify the new window name.
+* OSC 52 support has been added for programs inside tmux to set a tmux buffer.
+* The mouse "all event" mode (1003) is now supported.
+* Palette setting is now possible (OSC 4 and 104).
+* Strikethrough support (a recent terminfo is required).
+* Grouped sessions can now be named (new -t).
+* terminal-overrides and update-environment are now array options (the previous
+ set -ag syntax should work without change).
+* There have been substantial performance improvements.
+
+CHANGES FROM 2.2 TO 2.3, 29 September 2016
+
+Incompatible Changes
+====================
+
+None.
+
+Normal Changes
+==============
+
+* New option 'pane-border-status' to add text in the pane borders.
+* Support for hooks on commands: 'after' and 'before' hooks.
+* 'source-file' understands '-q' to suppress errors for nonexistent files.
+* Lots of UTF8 improvements, especially on MacOS.
+* 'window-status-separator' understands #[] expansions.
+* 'split-window' understands '-f' for performing a full-width split.
+* Allow report count to be specified when using 'bind-key -R'.
+* 'set -a' for appending to user options (@foo) is now supported.
+* 'display-panes' can now accept a command to run, rather than always
+ selecting the pane.
+
+CHANGES FROM 2.1 TO 2.2, 10 April 2016
+
+Incompatible Changes
+====================
+
+* The format strings which referenced time have been removed. Instead:
+
+ #{t:window_activity}
+
+can be used.
+
+* Support for TMPDIR has been removed. Use TMUX_TMPDIR instead.
+* UTF8 detection now happens automatically if the client supports it, hence
+ the:
+
+ mouse-utf8
+ utf8
+
+ options has been removed.
+* The:
+
+ mouse_utf8_flag
+
+ format string has been removed.
+* The -I option to show-messages has been removed. See:
+
+ #{t:start_time}
+
+ format option instead.
+
+Normal Changes
+==============
+
+* Panes are unzoomed with selectp -LRUD
+* New formats added:
+
+ #{scroll_position}
+ #{socket_path}
+ #{=10:...} -- limit to N characters (from the start)
+ #{=-10:...} -- limit to N characters (from the end)
+ #{t:...} -- used to format time-based formats
+ #{b:...} -- used to ascertain basename from string
+ #{d:...} -- used to ascertain dirname from string
+ #{s:...} -- used to perform substitutions on a string
+
+* Job output is run via the format system, so formats work again
+* If display-time is set to 0, then the indicators wait for a key to be
+ pressed.
+* list-keys and list-commands can be run without starting the tmux server.
+* kill-session learns -C to clear all alerts in all windows of the session.
+* Support for hooks (internal for now), but hooks for the following have been
+ implemented:
+
+ alert-bell
+ alert-silence
+ alert-activity
+ client-attached
+ client-detached
+ client-resized
+ pane-died
+ pane-exited
+
+* RGB (24bit) colour support. The 'Tc' flag must be set in the external TERM
+ entry (using terminal-overrides or a custom terminfo entry).
+
+CHANGES FROM 2.0 TO 2.1, 18 October 2015
+
+Incompatible Changes
+====================
+
+* Mouse-mode has been rewritten. There's now no longer options for:
+ - mouse-resize-pane
+ - mouse-select-pane
+ - mouse-select-window
+ - mode-mouse
+
+ Instead there is just one option: 'mouse' which turns on mouse support
+ entirely.
+* 'default-terminal' is now a session option. Furthermore, if this is set
+ to 'screen-*' then emulate what screen does. If italics are wanted, this
+ can be set to 'tmux' but this is still new and not necessarily supported
+ on all platforms with older ncurses installs.
+* The c0-* options for rate-limiting have been removed. Instead, a backoff
+ approach is used.
+
+Normal Changes
+==============
+
+* New formats:
+ - session_activity
+ - window_linked
+ - window_activity_format
+ - session_alerts
+ - session_last_attached
+ - client_pid
+ - pid
+* 'copy-selection', 'append-selection', 'start-named-buffer' now understand
+ an '-x' flag to prevent it exiting copying mode.
+* 'select-pane' now understands '-P' to set window/pane background colours.
+* 'renumber-windows' now understands windows which are unlinked.
+* 'bind' now understands multiple key tables. Allows for key-chaining.
+* 'select-layout' understands '-o' to undo the last layout change.
+* The environment is updated when switching sessions as well as attaching.
+* 'select-pane' now understands '-M' for marking a pane. This marked pane
+ can then be used with commands which understand src-pane specifiers
+ automatically.
+* If a session/window target is prefixed with '=' then only an exact match
+ is considered.
+* 'move-window' understands '-a'.
+* 'update-environment' understands '-E' when attach-session is used on an
+ already attached client.
+* 'show-environment' understands '-s' to output Bourne-compatible commands.
+* New option: 'history-file' to save/restore command prompt history.
+* Copy mode is exited if the history is cleared whilst in copy-mode.
+* 'copy-mode' learned '-e' to exit copy-mode when scrolling to end.
+
+CHANGES FROM 1.9a TO 2.0, 06 March 2015
+
+Incompatible Changes
+====================
+
+* The choose-list command has been removed.
+* 'terminal-overrides' is now a server option, not a session option.
+* 'message-limit' is now a server option, not a session option.
+* 'monitor-content' option has been removed.
+* 'pane_start_path' option has been removed.
+* The "info" mechanism which used to (for some commands) provide feedback
+ has been removed, and like other commands, they now produce nothing on
+ success.
+
+Normal Changes
+==============
+
+* tmux can now write an entry to utmp if the library 'utempter' is present
+ at compile time.
+* set-buffer learned append mode (-a), and a corresponding
+ 'append-selection' command has been added to copy-mode.
+* choose-mode now has the following commands which can be bound:
+ - start-of-list
+ - end-of-list
+ - top-line
+ - bottom-line
+
+* choose-buffer now understands UTF-8.
+* Pane navigation has changed:
+ - The old way of always using the top or left if the choice is ambiguous.
+ - The new way of remembering the last used pane is annoying if the
+ layout is balanced and the leftmost is obvious to the user (because
+ clearly if we go right from the top-left in a tiled set of four we want
+ to end up in top-right, even if we were last using the bottom-right).
+
+ So instead, use a combination of both: if there is only one possible
+ pane alongside the current pane, move to it, otherwise choose the most
+ recently used of the choice.
+* 'set-buffer' can now be told to give names to buffers.
+* The 'new-session', 'new-window', 'split-window', and 'respawn-pane' commands
+ now understand multiple arguments and handle quoting problems correctly.
+* 'capture-pane' understands '-S-' to mean the start of the pane, and '-E-' to
+ mean the end of the pane.
+* Support for function keys beyond F12 has changed. The following explains:
+ - F13-F24 are S-F1 to S-F12
+ - F25-F36 are C-F1 to C-F12
+ - F37-F48 are C-S-F1 to C-S-F12
+ - F49-F60 are M-F1 to M-F12
+ - F61-F63 are M-S-F1 to M-S-F3
+
+ Therefore, F13 becomes a binding of S-F1, etc.
+* Support using pane id as part of session or window specifier (so % means
+ session-of-%1 or window-of-%1) and window id as part of session
+ (so @1 means session-of-@1).
+* 'copy-pipe' command now understands formats via -F
+* 'if-shell' command now understands formats via -F
+* 'split-window' and 'join-window' understand -b to create the pane to the left
+ or above the target pane.
+
+CHANGES FROM 1.9 TO 1.9a, 22 February 2014
+
+NOTE: This is a bug-fix release to address some important bugs which just
+missed the 1.9 deadline, but were found afterwards.
+
+Normal Changes
+==============
+
+* Fix crash due to uninitialized lastwp member of layout_cell
+* Fix -fg/-bg/-style with 256 colour terminals.
+
+CHANGES FROM 1.8 TO 1.9, 20 February 2014
+
+NOTE: This release has bumped the tmux protocol version. It is therefore
+advised that the prior tmux server is restarted when this version of tmux is
+installed, to avoid protocol mismatch errors for newer clients trying to
+talk to an older running tmux server.
+
+Incompatible Changes
+====================
+
+* 88 colour support has been removed.
+* 'default-path' has been removed. The new-window command accepts '-c' to
+ cater for this. The previous value of "." can be replaced with: 'neww -c
+ $PWD', the previous value of '' which meant current path of the pane can
+ be specified as: 'neww -c "#{pane_current_path}"'
+
+Deprecated Changes
+==================
+
+* The single format specifiers: #A -> #Z (where defined) have been
+ deprecated and replaced with longer-named equivalents, as listed in the
+ FORMATS section of the tmux manpage.
+* The various foo-{fg,bg,attr} commands have been deprecated and replaced
+ with equivalent foo-style option instead. Currently this is still
+ backwards-compatible, but will be removed over time.
+
+Normal Changes
+==============
+
+* A new environment variable TMUX_TMPDIR is now honoured, allowing the
+ socket directory to be set outside of TMPDIR (/tmp/ if not set).
+* If -s not given to swap-pane the current pane is assumed.
+* A #{pane_synchronized} format specifier has been added to be a conditional
+ format if a pane is in a synchronised mode (c.f. synchronize-panes)
+* Tmux now runs under Cygwin natively.
+* Formats can now be nested within each other and expanded accordingly.
+* Added 'automatic-rename-format' option to allow the automatic rename
+ mechanism to use something other than the default of
+ #{pane_current_command}.
+* new-session learnt '-c' to specify the starting directory for that session
+ and all subsequent windows therein.
+* The session name is now shown in the message printed to the terminal when
+ a session is detached.
+* Lots more format specifiers have been added.
+* Server race conditions have been fixed; in particular commands are not run
+ until after the configuration file is read completely.
+* Case insensitive searching in tmux's copy-mode is now possible.
+* attach-session and switch-client learnt the '-t' option to accept a window
+ and/or a pane to use.
+* Copy-mode is only exited if no selection is in progress.
+* Paste key in copy-mode is now possible to enter text from the clipboard.
+* status-interval set to '0' now works as intended.
+* tmux now supports 256 colours running under fbterm.
+* Many bug fixes!
+
+CHANGES FROM 1.7 TO 1.8, 26 March 2013
+
+Incompatible Changes
+====================
+
+* layout redo/undo has been removed.
+
+Normal Changes
+==============
+
+* Add halfpage up/down bindings to copy mode.
+* Session choosing fixed to work with unattached sessions.
+* New window options window-status-last-{attr,bg,fg} to denote the last
+ window which was active.
+* Scrolling in copy-mode now scrolls the region without moving the mouse
+ cursor.
+* run-shell learnt '-t' to specify the pane to use when displaying output.
+* Support for middle-click pasting.
+* choose-tree learns '-u' to start uncollapsed.
+* select-window learnt '-T' to toggle to the last window if it's already
+ current.
+* New session option 'assume-paste-time' for pasting text versus key-binding
+ actions.
+* choose-* commands now work outside of an attached client.
+* Aliases are now shown for list-commands command.
+* Status learns about formats.
+* Free-form options can be set with set-option if prepended with an '@'
+ sign.
+* capture-pane learnt '-p' to send to stdout, and '-e' for capturing escape
+ sequences, and '-a' to capture the alternate screen, and '-P' to dump
+ pending output.
+* Many new formats added (client_session, client_last_session, etc.)
+* Control mode, which is a way for a client to send tmux commands.
+ Currently more useful to users of iterm2.
+* resize-pane learnt '-x' and '-y' for absolute pane sizing.
+* Config file loading now reports errors from all files which are loaded via
+ the 'source-file' command.
+* 'copy-pipe' mode command to copy selection and pipe the selection to a
+ command.
+* Panes can now emit focus notifications for certain applications
+ which use those.
+* run-shell and if-shell now accept formats.
+* resize-pane learnt '-Z' for zooming a pane temporarily.
+* new-session learnt '-A' to make it behave like attach-session.
+* set-option learnt '-o' to prevent setting an option which is already set.
+* capture-pane and show-options learns '-q' to silence errors.
+* New command 'wait-for' which blocks a client until woken up again.
+* Resizing panes will now reflow the text inside them.
+* Lots and lots of bug fixes, fixing memory-leaks, etc.
+* Various manpage improvements.
+
+CHANGES FROM 1.6 TO 1.7, 13 October 2012
+
+* tmux configuration files now support line-continuation with a "\" at the
+ end of a line.
+* New option status-position to move the status line to the top or bottom of
+ the screen.
+* Enforce history-limit option when clearing the screen.
+* Give each window a unique id, like panes but prefixed with @.
+* Add pane id to each pane in layout description (while still accepting
+ the old form).
+* Provide defined ways to set the various default-path possibilities: ~
+ for home directory, . for server start directory, - for session start
+ directory and empty for the pane's working directory (the default). All
+ can also be used as part of a relative path (eg -/foo). Also provide -c
+ flags to neww and splitw to override default-path setting.
+* Add -l flag to send-keys to send input literally (without translating
+ key names).
+* Allow a single option to be specified to show-options to show just that
+ option.
+* New command "move-pane" (like join-pane but allows the same window).
+* join-pane and move-pane commands learn "-b" option to place the pane to
+ the left or above.
+* Support for bracketed-paste mode.
+* Allow send-keys command to accept hex values.
+* Add locking around "start-server" to avoid race-conditions.
+* break-pane learns -P/-F arguments for display formatting.
+* set-option learns "-q" to make it quiet, and not print out anything.
+* copy mode learns "wrap-search" option.
+* Add a simple form of output rate limiting by counting the number of
+ certain C0 sequences (linefeeds, backspaces, carriage returns) and if it
+ exceeds a threshold (current default 250/millisecond), start to redraw
+ the pane every 100 milliseconds instead of making each change as it
+ comes. Two configuration options - c0-change-trigger and
+ c0-change-interval.
+* find-window learns new flags: "-C", "-N", "-T" to match against either or
+ all of a window's content, name, or title. Defaults to all three options
+ if none specified.
+* find-window automatically selects the appropriate pane for the found
+ matches.
+* show-environment can now accept one option to show that environment value.
+* Exit mouse mode when end-of-screen reached when scrolling with the mouse
+ wheel.
+* select-layout learns -u and -U for layout history stacks.
+* kill-window, detach-client, kill-session all learn "-a" option for
+ killing all but the current thing specified.
+* move-window learns "-r" option to renumber window sequentially in a
+ session.
+* New session option "renumber-windows" to automatically renumber windows in
+ a session when a window is closed. (see "move-window -r").
+* Only enter copy-mode on scroll up.
+* choose-* and list-* commands all use "-F" for format specifiers.
+* When spawning external commands, the value from the "default-shell" option
+ is now used, rather than assuming /bin/sh.
+* New choose-tree command to render window/sessions as a tree for selection.
+* display-message learns new format options.
+* For linked-windows across sessions, all flags for that window are now
+ cleared across sessions.
+* Lots and lots of bug fixes, fixing memory-leaks, etc.
+* Various manpage improvements.
+
+CHANGES FROM 1.5 TO 1.6, 23 January 2012
+
+* Extend the mode-mouse option to add a third choice which means the mouse
+ does not enter copy mode.
+* Add a -r flag to switch-client to toggle the client read-only flag.
+* Add pane-base-index option.
+* Support \ for line continuation in the configuration file.
+* Framework for more powerful formatting of command output and use it for
+ list-{panes,windows,sessions}. This allows more descriptive replacements
+ (such as #{session_name}) and conditionals.
+* Mark dead panes with some text saying they are dead.
+* Reject $SHELL if it is not a full path.
+* Add -S option to refresh-client to redraw status line.
+* Add an else clause for if-shell.
+* Try to resolve relative paths for loadb and saveb (first, using client
+ working directory, if any, then default-path or session working directory).
+* Support for \e[3J to clear the history and send the corresponding
+ terminfo code (E3) before locking.
+* When in copy mode, make repeat count indicate buffer to replace, if used.
+* Add screen*:XT to terminal-overrides for tmux-in-tmux.
+* Status-line message attributes added.
+* Move word-separators to be a session rather than window option.
+* Change the way the working directory for new processes is discovered. If
+ default-path isn't empty, it is used. Otherwise, if a new window is created
+ from the command-line, the working directory of the client is used. If not,
+ platform specific code is used to retrieve the current working directory
+ of the process in the active pane. If that fails, the directory where the
+ session was created is used, instead.
+* Do not change the current pane if both mouse-select-{pane,window} are
+ enabled.
+* Add \033[s and \033[u to save and restore cursor position.
+* Allow $HOME to be used as default-path.
+* Add CNL and CPL escape sequences.
+* Calculate last position correctly for UTF-8 wide characters.
+* Add an option allow-rename to disable the window rename escape sequence.
+* Attributes for each type of status-line alert (ie bell, content and
+ activity) added. Therefore, remove the superfluous options
+ window-status-alert-{attr,bg,fg}.
+* Add a -R flag to send-keys to reset the terminal.
+* Add strings to allow the aixterm bright colours to be used when
+ configuring colours.
+* Drop the ability to have a list of keys in the prefix in favour of two
+ separate options, prefix and prefix2.
+* Flag -2 added to send-prefix to send the secondary prefix key.
+* Show pane size in top right of display panes mode.
+* Some memory leaks plugged.
+* More command-prompt editing improvements.
+* Various manpage improvements.
+* More Vi mode improvements.
+
+CHANGES FROM 1.4 TO 1.5, 09 July 2011
+
+* Support xterm mouse modes 1002 and 1003.
+* Change from a per-session stack of buffers to one global stack. This renders
+ copy-buffer useless and makes buffer-limit now a server option.
+* Fix most-recently-used choice by avoiding reset the activity timer for
+ unattached sessions every second.
+* Add a -P option to new-window and split-window to print the new window or
+ pane index in target form (useful to pass it into other commands).
+* Handle a # at the end of a replacement string (such as status-left)
+ correctly.
+* Support for UTF-8 mouse input (\033[1005h) which was added in xterm 262.
+ If the new mouse-utf8 option is on, UTF-8 mouse input is enabled for all
+ UTF-8 terminals. The option defaults to on if LANG etc are set in the same
+ manner as the utf8 option.
+* Support for HP-UX.
+* Accept colours of the hex form #ffffff and translate to the nearest from the
+ xterm(1) 256-colour set.
+* Clear the non-blocking IO flag (O_NONBLOCK) on the stdio file descriptors
+ before closing them (fixes things like "tmux ls && cat").
+* Use TMPDIR if set.
+* Fix next and previous session functions to actually work.
+* Support -x and -y for new-session to specify the initial size of the window
+ if created detached with -d.
+* Make bind-key accept characters with the top-bit-set and print them as octal.
+* Set $TMUX without the session when background jobs are run.
+* Simplify the way jobs work and drop the persist type, so all jobs are
+ fire-and-forget.
+* Accept tcgetattr/tcsetattr(3) failure, fixes problems with fatal() if the
+ terminal disappears while locked.
+* Add a -P option to detach to HUP the client's parent process (usually causing
+ it to exit as well).
+* Support passing through escape sequences to the underlying terminal by using
+ DCS with a "tmux;" prefix.
+* Prevent tiled producing a corrupt layout when only one column is needed.
+* Give each pane created in a tmux server a unique id (starting from 0), put it
+ in the TMUX_PANE environment variable and accept it as a target.
+* Allow a start and end line to be specified for capture-pane which may be
+ negative to capture part of the history.
+* Add -a and -s options to lsp to list all panes in the server or session
+ respectively. Likewise add -s to lsw.
+* Change -t on display-message to be target-pane for the #[A-Z] replacements
+ and add -c as target-client.
+* The attach-session command now prefers the most recently used unattached
+ session.
+* Add -s option to detach-client to detach all clients attached to a session.
+* Add -t to list-clients.
+* Change window with mouse wheel over status line if mouse-select-window is on.
+* When mode-mouse is on, automatically enter copy mode when the mouse is
+ dragged or the mouse wheel is used. Also exit copy mode when the mouse wheel
+ is scrolled off the bottom.
+* Provide #h character pair for short hostname (no domain).
+* Don't use strnvis(3) for the title as it breaks UTF-8.
+* Use the tsl and fsl terminfo(5) capabilities to update terminal title and
+ automatically fill them in on terminals with the XT capability (which means
+ their title setting is xterm-compatible).
+* Add a new option, mouse-resize-pane. When on, panes may be resized by
+ dragging their borders.
+* Fix crash by resetting last pane on {break,swap}-pane across windows.
+* Add three new copy-mode commands - select-line, copy-line, copy-end-of-line.
+* Support setting the xterm clipboard when copying from copy mode using the
+ xterm escape sequence for the purpose (if xterm is configured to allow it).
+* Support xterm(1) cursor colour change sequences through terminfo(5) Cc
+ (set) and Cr (reset) extensions.
+* Support DECSCUSR sequence to set the cursor style with two new terminfo(5)
+ extensions, Cs and Csr.
+* Make the command-prompt custom prompts recognize the status-left option
+ character pairs.
+* Add a respawn-pane command.
+* Add a couple of extra xterm-style keys that gnome terminal provides.
+* Allow the initial context on prompts to be set with the new -I option to
+ command-prompt. Include the current window and session name in the prompt
+ when renaming and add a new key binding ($) for rename session.
+* Option bell-on-alert added to trigger the terminal bell when there is an
+ alert.
+* Change the list-keys format so that it shows the keys using actual tmux
+ commands which should be able to be directly copied into the config file.
+* Show full targets for lsp/lsw -a.
+* Make confirm-before prompt customizable with -p option like command-prompt
+ and add the character pairs #W and #P to the default kill-{pane,window}
+ prompts.
+* Avoid sending data to suspended/locked clients.
+* Small memory leaks in error paths plugged.
+* Vi mode improvements.
+
+CHANGES FROM 1.3 TO 1.4, 27 December 2010
+
+* Window bell reporting fixed.
+* Show which pane is active in the list-panes output.
+* Backoff reworked.
+* Prevent the server from dying when switching into copy mode when already
+ in a different mode.
+* Reset running jobs when the status line is enabled or disabled.
+* Simplify xterm modifier detection.
+* Avoid crashing in copy mode if the screen size is too small for the
+ indicator.
+* Flags -n and -p added to switch-client.
+* Use UTF-8 line drawing characters on UTF-8 terminals, thus fixing some
+ terminals (eg putty) which disable the vt100 ACS mode switching sequences
+ in UTF-8 mode. On terminals without ACS, use ASCII equivalents.
+* New server option exit-unattached added.
+* New session option destroy-unattached added.
+* Fall back on normal session choice method if $TMUX exists but is invalid
+ rather than rejecting.
+* Mark repeating keys with "(repeat)" in the key list.
+* When removing a pane, don't change the active pane unless the active pane
+ is actually the one being removed.
+* New command last-pane added.
+* AIX fixes.
+* Flag -a added to unbind-key.
+* Add XAUTHORITY to update-environment.
+* More info regarding window and pane flags is now shown in list-*.
+* If VISUAL or EDITOR contains "vi" configure mode-keys and status-key to vi.
+* New window option monitor-silence and session option visual-silence added.
+* In the built-in layouts distribute the panes more evenly.
+* Set the default value of main-pane-width to 80 instead of 81.
+* Command-line flag -V added.
+* Instead of keeping a per-client prompt history make it global.
+* Fix rectangle copy to behave like emacs (the cursor is not part of the
+ selection on the right edge but on the left it is).
+* Flag -l added to switch-client.
+* Retrieve environment variables from the global environment rather than
+ getenv(3), thus allowing them to be updated during the configuration file.
+* New window options other-pane-{height,width} added.
+* More minor bugs fixed and manpage improvements.
+
+CHANGES FROM 1.2 TO 1.3, 18 July 2010
+
+* New input parser.
+* Flags to move through panes -UDLR added to select-pane.
+* Commands up-pane, and down-pane removed, since equivalent behaviour is now
+ available through the target flag (-t:+ and -t:-).
+* Jump-forward/backward in copy move (based on vi's F, and f commands).
+* Make paste-buffer accept a pane as a target.
+* Flag -a added to new-window to insert a window after an existing one, moving
+ windows up if necessary.
+* Merge more mode into copy mode.
+* Run job commands explicitly in the global environment (which can be modified
+ with setenv -g), rather than with the environment tmux started with.
+* Use the machine's hostname as the default title, instead of an empty string.
+* Prevent double free if the window option remain-on-exit is set.
+* Key string conversions rewritten.
+* Mark zombie windows as dead in the choose-window list.
+* Tiled layout added.
+* Signal handling reworked.
+* Reset SIGCHLD after fork to fix problems with some shells.
+* Select-prompt command removed. Therefore, bound ' to command-prompt -p index
+ "select-window -t:%%" by default.
+* Catch SIGHUP and terminate if running as a client, thus avoiding clients from
+ being left hanging around when, for instance, a SSH session is disconnected.
+* Solaris 9 fixes (such as adding compat {get,set}env(3) code).
+* Accept none instead of default for attributes.
+* Window options window-status-alert-{alert,bg,fg} added.
+* Flag -s added to the paste-buffer command to specify a custom separator.
+* Allow dragging to make a selection in copy mode if the mode-mouse option is
+ set.
+* Support the mouse scroll wheel.
+* Make pipe-pane accept special character sequences (eg #I).
+* Fix problems with window sizing when starting tmux from .xinitrc.
+* Give tmux sockets (but not the containing folder) group permissions.
+* Extend the target flags (ie -t) to accept an offset (for example -t:+2), and
+ make it wrap windows, and panes.
+* New command choose-buffer added.
+* New server option detach-on-destroy to set what happens to a client when the
+ session it is attached to is destroyed. If on (default), the client is
+ detached. Otherwise, the client is switched to the most recently active of
+ the remaining sessions.
+* The commands load-buffer, and save-buffer now accept a dash (-) as the file
+ to read from stdin, or write to stdout.
+* Custom layouts added.
+* Additional code reduction, bug fixes, and manpage enhancements.
+
+CHANGES FROM 1.1 TO 1.2, 10 March 2010
+
+* Switch to libevent.
+* Emulate the ri (reverse index) capability, ergo allowing tmux to at least
+ start on Sun consoles (TERM=sun, or sun-color).
+* Assign each entry a number, or lowercase letter in choose mode, and accept
+ that as a shortcut key.
+* Permit top-bit-set characters to be entered in the status line.
+* Mark no-prefix keys with (no prefix), rather than [] in list-keys.
+* New command show-messages (alias showmsgs), and new session option
+ message-limit, to show a per-client log of status lines messages up to the
+ number defined by message-limit.
+* Do not interpret #() for display-message to avoid leaking commands.
+* New window options window-status-format, and window-status-current-format to
+ control the format of each window in the status line.
+* Add a -p flag to display-message to print the output, instead of displaying
+ it in the status line.
+* Emulate il1, dl1, ich1 to run with vt100 feature set.
+* New command capture-pane (alias capturep) to copy the entire pane contents
+ to a paste buffer.
+* Avoid duplicating code by adding a -w flag to set-option, and show-options to
+ set, and show window options. The commands set-window-option, and
+ show-window-options are now aliases.
+* Panes can now be referred to as top, bottom, top-left, etc.
+* Add server-wide options, which can be set with set-option -s, and shown with
+ show-options -s.
+* New server option quiet (like -q from the command line).
+* New server option escape-time to set the timeout used to detect if escapes
+ are alone, part of a function key, or meta sequence.
+* New session options pane-active-border-bg, pane-active-border-fg,
+ pane-border-bg, and pane-border-fg to set pane colours.
+* Make split-window accept a pane target, instead of a window.
+* New command join-pane (alias joinp) to split, and move an existing pane into
+ the space (the opposite of break-pane), thus simplifying calls to
+ split-window, followed by move-window.
+* Permit S- prefix on keys for shift when the terminal/terminfo supports them.
+* Window targets (-t flag) can now refer to the last window (!), next (+), and
+ previous (-) window by number.
+* Mode keys to jump to the bottom/top of history, end of the next word, scroll
+ up/down, and reverse search in copy mode.
+* New session option display-panes-active-colour to display the active pane in
+ a different colour with the display-panes command.
+* Read the socket path from $TMUX if it's present, and -L, and -S are not
+ given.
+* Vi-style mode keys B, W, and E to navigate between words in copy mode.
+* Start in more mode when configuration file errors are detected.
+* Rectangle copy support added.
+* If attach-session was specified with the -r flag, make the client read-only.
+* Per-window alternate-screen option.
+* Make load-buffer work with FIFOs.
+* New window option word-separators to set the characters considered as word
+ separators in copy mode.
+* Permit keys in copy mode to be prefixed by a repeat count, entered with [1-9]
+ in vi mode, or M-[1-9] in emacs mode.
+* utf8 improvements.
+* As usual, additional code reduction, bug fixes, and manpage enhancements.
+
+CHANGES FROM 1.0 TO 1.1, 05 November 2009
+
+* New run-shell (alias run) command to run an external command without a
+ window, capture it's stdout, and send it to output mode.
+* Ability to define multiple prefix keys.
+* Internal locking mechanism removed. Instead, detach each client and run the
+ external command specified in the new session option lock-command (by default
+ lock -np), thus allowing the system password to be used.
+* set-password command, and -U command line flag removed per the above change.
+* Add support for -c command line flag to execute a shell command.
+* New lock-client (alias lockc), and lock-session (alias locks) commands to
+ lock a particular client, or all clients attached to a session.
+* Support C-n/C-p/C-v/M-v with emacs keys in choice mode.
+* Use : for goto line rather than g in vi mode.
+* Try to guess which client to use when no target client was specified. Finds
+ the current session, and if only one client is present, use it. Otherwise,
+ return the most recently used client.
+* Make C-Down/C-Up in copy mode scroll the screen down/up one line without
+ moving the cursor.
+* Scroll mode superseded by copy mode.
+* New synchronize-panes window option to send all input to all other panes in
+ the same window.
+* New lock-server session option to lock, when off (on by default), each
+ session when it has been idle for the lock-after-time setting. When on, the
+ entire server locks when all sessions have been idle for their individual
+ lock-after-time setting.
+* Add support for grouped sessions which have independent name, options,
+ current window, but where the linked windows are synchronized (ie creating,
+ killing windows are mirrored between the sessions). A grouped session may be
+ created by passing -t to new-session.
+* New mouse-select-pane session option to select the current pane with the
+ mouse.
+* Queue, and run commands in the background for if-shell, status-left,
+ status-right, and #() by starting each once every status-interval. Adds the
+ capability to call some programs which would previously cause the server to
+ hang (eg sleep/tmux). It also avoids running commands excessively (ie if used
+ multiple times, it will be run only once).
+* When a window is zombified and automatic-rename is on, append [dead] to the
+ name.
+* Split list-panes (alias lsp) off from list-windows.
+* New pipe-pane (alias pipep) to redirect a pane output to an external command.
+* Support for automatic-renames for Solaris.
+* Permit attributes to be turned off in #[] by prefixing with no (eg nobright).
+* Add H/M/L in vi mode, and M-R/M-r in emacs to move the cursor to the top,
+ middle, and bottom of the screen.
+* -a option added to kill-pane to kill all except current pane.
+* The -d command line flag is now gone (can be replaced by terminal-overrides).
+ Just use op/AX to detect default colours.
+* input/tty/utf8 improvements.
+* xterm-keys rewrite.
+* Additional code reduction, and bug fixes.
+
+CHANGES FROM 0.9 TO 1.0, 20 September 2009
+
+* Option to alter the format of the window title set by tmux.
+* Backoff for a while after multiple incorrect password attempts.
+* Quick display of pane numbers (C-b q).
+* Better choose-window, choose-session commands and a new choose-client command.
+* Option to request multiple responses when using command-prompt.
+* Improved environment handling.
+* Combine wrapped lines when pasting.
+* Option to override terminal settings (terminal-overrides).
+* Use the full range of ACS characters for drawing pane separator lines.
+* Customisable mode keys.
+* Status line colour options, with embedded colours in status-left/right, and
+ an option to centre the window list.
+* Much improved layouts, including both horizontal and vertical splitting.
+* Optional visual bell, activity and content indications.
+* Set the utf8 and status-utf8 options when the server is started with -u.
+* display-message command to show a message in the status line, by default some
+ information about the current window.
+* Improved current process detection on NetBSD.
+* unlink-window -k is now the same as kill-window.
+* attach-session now works from inside tmux.
+* A system-wide configuration file, /etc/tmux.conf.
+* A number of new commands in copy mode, including searching.
+* Panes are now specified using the target (-t) notation.
+* -t now accepts fnmatch(3) patterns and looks for prefixes.
+* Translate \r into \n when pasting.
+* Support for binding commands to keys without the prefix key
+* Support for alternate screen (terminfo smcup/rmcup).
+* Maintain data that goes off screen after reducing the window size, so it can
+ be restored when the size is increased again.
+* New if-shell command to test a shell command before running a tmux command.
+* tmux now works as the shell.
+* Man page reorganisation.
+* Many minor additions, much code tidying and several bug fixes.
+
+CHANGES FROM 0.8 TO 0.9, 01 July 2009
+
+* Major changes to build infrastructure: cleanup of makefiles and addition
+ of a configure script.
+* monitor-content window option to monitor a window for a specific fnmatch(3)
+ pattern. The find-window command also now accepts fnmatch(3) patterns.
+* previous-layout and select-layout commands, and a main-horizontal layout.
+* Recreate the server socket on SIGUSR1.
+* clear-history command.
+* Use ACS line drawing characters for pane separator lines.
+* UTF-8 improvements, and code to detect UTF-8 support by looking at
+ environment variables.
+* The resize-pane-up and resize-pane-down commands are now merged together
+ into a new resize-pane command with -U and -D flags.
+* confirm-before command to request a yes/no answer before executing dangerous
+ commands.
+* Status line bug fixes, support for UTF-8 (status-utf8 option), and a key to
+ paste from the paste buffer.
+* Support for some additional escape sequences and terminal features, including
+ better support for insert mode and tab stops.
+* Improved window resizing behaviour, modelled after xterm.
+* Some code reduction and a number of miscellaneous bug fixes.
+
+================================================================================
+
+On 01 June 2009, tmux was imported into the OpenBSD base system. From this date
+onward changes are logged as part of the normal CVS commit message to either
+OpenBSD or SourceForge CVS. This file will be updated to contain a summary of
+major changes with each release, and to mention important configuration or
+command syntax changes during development.
+
+The list of older changes is below.
+
+================================================================================
+
+21 May 2009
+
+* stat(2) files before trying to load them to avoid problems, for example
+ with "source-file /dev/zero".
+
+19 May 2009
+
+* Try to guess if the window is UTF-8 by outputting a three-byte UTF-8 wide
+ character and seeing how much the cursor moves. Currently tries to figure out
+ if this works by some stupid checks on the terminal, these need to be
+ rethought. Also might be better using a width 1 character rather than width 2.
+* If LANG contains "UTF-8", assume the terminal supports UTF-8, on the grounds
+ that anyone who configures it probably wants UTF-8. Not certain if this is
+ a perfect idea but let's see if it causes any problems.
+* New window option: monitor-content. Searches for a string in a window and if
+ it matches, highlight the status line.
+
+18 May 2009
+
+* main-horizontal layout and main-pane-height option to match vertical.
+* New window option main-pane-width to set the width of the large left pane with
+ main-vertical (was left-vertical) layout.
+* Lots of layout cleanup. manual layout is now manual-vertical.
+
+16 May 2009
+
+* select-layout command and a few default key bindings (M-0, M-1, M-2, M-9) to
+ select layouts.
+* Recreate server socket on SIGUSR1, per SF feature request 2792533.
+
+14 May 2009
+
+* Keys in status line (p in vi mode, M-y in emacs) to paste the first line
+ of the upper paste buffer. Suggested by Dan Colish.
+* clear-history command to clear a pane's history.
+* Don't force wrapping with \n when asked, let the cursor code figure it out.
+ Should fix terminals which use this to detect line breaks.
+* Major cleanup and restructuring of build infrastructure. Still separate files
+ for GNU and BSD make, but they are now hugely simplified at the expense of
+ adding a configure script which must be run before make. Now build and
+ install with:
+
+ $ ./configure && make && sudo make install
+
+04 May 2009
+
+* Use ACS line drawing characters for pane separator lines.
+
+30 April 2009
+
+* Support command sequences without a space before the semicolon, for example
+ "neww; neww" now works as well as "neww ; neww". "neww;neww" is still an
+ error.
+* previous-layout command.
+* Display the layout name in window lists.
+* Merge resize-pane-up and resize-pane-down into resize-pane with -U and -D
+ flags.
+
+29 April 2009
+
+* Get rid of compat/vis.* - only one function was used which is easily
+ replaced,and less compat code == good.
+
+27 April 2009
+
+* Avoid using the prompt history when the server is locked, and prevent any
+ input entered from being added to the client's prompt history.
+* New command, confirm-before (alias confirm), which asks for confirmation
+ before executing a command. Bound "&" and "x" by default to confirm-before
+ "kill-window" and confirm-before "kill-pane", respectively.
+
+23 April 2009
+
+* Support NEL, yet another way of making newline. Fixes the output from some
+ Gentoo packaging thing. Reported by someone on SF then logs that allowed a
+ fix sent by tcunha.
+* Use the xenl terminfo flag to detect early-wrap terminals like the FreeBSD
+ console. Many thanks for a very informative email from Christian Weisgerber.
+
+21 April 2009
+
+* tmux 0.8 released.
+
+17 April 2009
+
+* Remove the right number of characters from the buffer when escape then
+ a cursor key (or other key prefixed by \033) is pressed. Reported by
+ Stuart Henderson.
+
+03 April 2009
+
+* rotate-window command. -U flag (default) for up, -D flag for down.
+
+02 April 2009
+
+* Change scroll/pane redraws to only redraw the single pane affected rather
+ than the entire window.
+* If redrawing the region would mean redrawing > half the pane, just schedule
+ to redraw the entire window. Also add a flag to skip updating the window any
+ further if it is scheduled to be redrawn. This has the effect of batching
+ multiple redraws together.
+
+01 April 2009
+
+* Basic horizontal splitting and layout management. Still some redraw and other
+ issues - particularly, don't mix with manual pane resizing, be careful when
+ viewing from multiple clients and don't expect shell windows to redraw very
+ well after the layout is changed; generally cycling the layout a few times
+ will fix most problems. Getting this in for testing while I think about how
+ to deal with manual mode.
+
+ Split window as normal and cycle the layouts with C-b space. Some of the
+ layouts will work better when swap-pane comes along.
+
+31 March 2009
+
+* AIX port, thanks to cmihai for access to a box. Only tested on 6.1 with xlc
+ 10.1 (make sure CC is set). Needs GNU make and probably ncurses (didn't try
+ plain curses). Also won't build with DEBUG, so comment the FDEBUG=1 line in
+ GNUmakefile.
+* Draw a vertical line on the right when the window size is less than the
+ terminal size. This is partly to shake out any horizontal limit bugs on the
+ way to horizontal splitting/pane tiling. Currently a bit slow since it has to
+ do a lot of redrawing but hopefully that will improve as I get some better
+ ideas for how to do it.
+* Fix remaining problems with copy and paste and UTF-8.
+
+28 March 2009
+
+* Better UTF-8 support, including combined characters. Unicode data is now
+ stored as UTF-8 in a separate array, the code does a lookup into this every
+ time it gets to a UTF-8 cell. Zero width characters are just appended onto
+ the UTF-8 data for the previous cell. This also means that almost no bytes
+ extra are wasted non-Unicode data (yay).
+
+ Still some oddities, such as copy mode skips over wide characters in a
+ strange way, and the code could do with some tidying.
+* Key repeating is now a property of the key binding not of the command.
+ Repeat is turned on when the key is bound with the -r flag to bind-key.
+ next/previous-window no longer repeat by default as it turned out to annoy
+ me.
+
+27 March 2009
+
+* Clear using ED when redrawing the screen. I foolishly assumed using spaces
+ would be equivalent and terminals would pick up on this, but apparently not.
+ This fixes copy and paste in xterm/rxvt.
+* Sockets in /tmp are now created in a subdirectory named, tmux-UID, eg
+ tmux-1000. The default socket is thus /tmp/tmux-UID/default. To start a
+ separate server, the new -L command line option should be used: this creates
+ a socket in the same directory with a different name ("-L main" will create
+ socket called "main"). -S should only be used to place the socket outside
+ /tmp. This makes sockets a little more secure and a bit more convenient to
+ use multiple servers.
+
+21 March 2009
+
+* New session flag "set-remain-on-exit" to set remain-on-exit flag for new
+ windows created in that session (like "remain-by-default" used to do). Not
+ perfectly happy about this, but until I can think of a good way to introduce
+ it generically (maybe a set of options in the session) this will do. Fixes
+ SF request 2527847.
+
+07 March 2009
+
+* Support for 88 colour terminals.
+* break-pane command to create a new window using an existing pane.
+
+02 March 2009
+
+* Make escape key timer work properly so escape+key can be used without
+ lightning fast key presses.
+
+13 February 2009
+
+* Redo mode keys slightly more cleanly and apply them to command prompt
+ editing. vi or emacs mode is controlled by the session option status-keys.
+
+12 February 2009
+
+* Looking up argv[0] is expensive, so just use p_comm for the window name which
+ is good enough. Also increase name update time to 500 ms.
+
+11 February 2009
+
+* Only use ri when actually at the top of the screen; just move the cursor up
+ otherwise.
+* FreeBSD's console wraps lines at $COLUMNS - 1 rather than $COLUMNS (the
+ cursor can never be beyond $COLUMNS - 1) and does not appear to support
+ changing this behaviour, or any of the obvious possibilities (turning off
+ right margin wrapping, insert mode). This is irritating, most notably because
+ it impossible to write to the very bottom-right of the screen without
+ scrolling. To work around this, if built on FreeBSD and run with a "cons"
+ $TERM, the bottom-right cell on the screen is omitted.
+* Emulate scroll regions (slowly) to support the few terminals which don't have
+ it (some of which don't really have any excuse).
+
+10 February 2009
+
+* No longer redraw the status line every status-interval unless it has actually
+ changed.
+
+08 February 2009
+
+* Don't treat empty arguments ("") differently when parsing configuration
+ file/command prompt rather than command line.
+* tmux 0.7 released.
+
+03 February 2009
+
+* New command, copy-buffer (alias copyb), to copy a session paste buffer to
+ another session.
+
+01 February 2009
+
+* The character pair #(command) may now contain (escaped) right parenthesis.
+
+30 January 2009
+
+* . now bound to "command-prompt 'move-window %%'" by default, from joshe.
+
+29 January 2009
+
+* Window options to set status line fg, bg and attributes for a single
+ window. Options are: window-status-fg, window-status-bg,
+ window-status-attr. Set to "default" to use the session status colours.
+
+ This allows quite neat things like:
+
+ $ cat ~/bin/xssh
+ #!/bin/sh
+
+ if [ ! -z "$TMUX" ]; then
+ case "$1" in
+ natalya)
+ tmux setw window-status-fg red >/dev/null
+ ;;
+ natasha)
+ tmux setw window-status-fg yellow >/dev/null
+ ;;
+ esac
+ fi
+ ssh "$@"
+ [ ! -z "$TMUX" ] && tmux setw -u window-status-fg >/dev/null
+ $ alias ssh="~/bin/xssh"
+
+* Support #(command) in status-left, and status-right, which is displayed as
+ the first line of command's output (e.g. set -g status-right
+ "#(whoami)@#(hostname -s)"). Commands with )s aren't supported.
+
+28 January 2009
+
+* Support mouse in copy mode to move cursor. Can't do anything else at the
+ moment until other mouse modes are handled.
+* Better support for at least the most common variant of mouse input: parse it
+ and adjust for different panes. Also support mouse in window/session choice
+ mode.
+
+27 January 2009
+
+* Bring back the fancy window titles with session/window names: it is easy to
+ work around problems with elinks (see FAQ).
+* -u flag to scroll-mode and copy-mode to start scrolled one page
+ up. scroll-mode -u is bound to prefix,page-up (ppage) by default.
+* Allow status, mode and message attributes to be changed by three new options:
+ status-attr, mode-attr, message-attr. A comma-separated list is accepted
+ containing: bright, dim, underscore, blink, reverse, hidden, italics, for
+ example:
+
+ set -g status-attr bright,blink
+
+ From Josh Elsasser, thanks!
+
+26 January 2009
+
+* Be more clever about picking the right process to create the window name.
+* Don't balls up the terminal on UTF-8 combined characters. Don't support them
+ properly either - they are just discarded for the moment.
+
+25 January 2009
+
+* load-buffer command
+
+23 January 2009
+
+* Use reverse colours rather than swapping fg and bg for message, mode and
+ status line. This makes these usable on black and white terminals.
+* Better error messages when creating a session or window fails.
+* Oops. Return non-zero on error. Reported by Will Maier.
+
+21 January 2009
+
+* Handle SIGTERM (and kill-server which uses it), a bit more neatly - tidy
+ up properly and print a nicer message. Same effect though :-).
+* new-window now supports -k to kill target window if it exists.
+* Bring back split-window -p and -l options to specify the height a percentage
+ or as a number of lines.
+* Make window and session choice modes allow you to choose items in vi keys
+ mode (doh!). As a side-effect, this makes enter copy selection (as well
+ as C-w/M-w) when using emacs keys in copy mode. Reported by merdely.
+
+20 January 2009
+
+* Darwin support for automatic-rename from joshe; Darwin doesn't seem to have
+ a sane method of getting argv[0] and searching for the precise insane way
+ is too frustrating, so this just uses the executable name.
+* Try to change the window title to match the command running it in. This is
+ done by reading argv[0] from the process group leader of the group that owns
+ the tty (tcgetpgrp()). This can't be done portably so some OS-dependent code
+ is introduced (ugh); OpenBSD, FreeBSD and Linux are supported at the moment.
+
+ A new window flag, automatic-rename, is available: if this is set to off, the
+ window name is not changed. Specifying a name with the new-window,
+ new-session or rename-window commands will automatically set this flag to off
+ for the window in question. To disable it entirely set the option to off
+ globally (setw -g automatic-rename off).
+
+19 January 2009
+
+* Fix various stupid issues when the status line is turned off. Grr.
+* Use reverse attributes for clock and cursor, otherwise they do not
+ appear on black and white terminals.
+* An error in a command sequence now stops execution of that sequence.
+ Internally, each command code now passes a return code back rather than
+ talking to the calling client (if any) directly.
+* attach-session now tries to start the server if it isn't already started - if
+ no sessions are created in .tmux.conf this will cause an error.
+* Clean up starting server by making initial client get a special socketpair.
+
+18 January 2009
+
+* Unbreak UTF-8.
+* -a flag to next-window and previous-window to select the next or previous
+ window with activity or bell. Bound to M-n and M-p.
+* find-window command to search window names, titles and visible content (but
+ not history) for a string. If only one is found, the window is selected
+ otherwise a choice list is shown. This (as with the other choice commands)
+ only works from a key. Bound to "f" by default.
+* Cleaned up command printing code, also enclose arguments with spaces in "s.
+* Added command sequences. These are entered by separating each argument by a ;
+ argument (spaces on both sides), for example:
+
+ lsk ; lsc
+
+ To use a literal ; as the argument prefix it with \, for example:
+
+ bind x lsk \; lsc
+
+ Commands are executed from left to right. Also note that command sequences do
+ not support repeat-time repetition unless all commands making up the sequence
+ support it.
+* suspend-client command to suspend a client. Don't try to background it
+ though...
+* Mark attached sessions in sessions lists. Suggested by Simon Kuhnle.
+
+17 January 2009
+
+* tmux 0.6 released.
+
+15 January 2009
+
+* Support #H for hostname and #S for session name in status-left/right.
+* Two new commands, choose-window and choose-session which work only when bound
+ to a key and allow the window or session to be selected from a list. These
+ are now bound to "w" and "s" instead of the list commands.
+
+14 January 2009
+
+* Rework the prefix-time stuff. The option is now called repeat-time and
+ defaults to 500 ms. It only applies to a small subset of commands, currently:
+ up-pane, down-pane, next-window, previous-window, resize-pane-up,
+ resize-pane-down. These are the commands for which it is obviously useful,
+ having it for everything else was just bloody annoying.
+* The alt-up and alt-down keys now resize a pane by five lines at a time.
+* switch-pane is now select-pane and requires -p to select a pane. The
+ "o" key binding is changed to down-pane.
+* up-pane and down-pane commands, bound to arrow up and down by default.
+* Multiple vertical window splitting. Minimum pane size is four lines, an
+ (unhelpful) error will be shown if attempting to split a window with less
+ that eight lines. If the window is resized, as many panes are shown as can
+ fit without reducing them below four lines. There is (currently!) not a way
+ to show a hidden pane without making the window larger.
+
+ Note the -p and -l options to split-window are now gone, these may reappear
+ once I think them through again.
+* Server locking on inactivity (lock-after-time) is now disabled by default.
+
+13 January 2009
+
+* kill-pane command.
+
+12 January 2009
+
+* command-prompt now accepts a single argument, a template string. Any
+ occurrences of %% in this string are replaced by whatever is entered at the
+ prompt and the result is executed as a command. This allows things like (now
+ bound by default):
+
+ bind , command-prompt "rename-window %%"
+
+ Or my favourite:
+
+ bind x command-prompt "split-window 'man %%'"
+
+* Option to set prefix time, allowing multiple commands to be entered without
+ pressing the prefix key again, so long as they each typed within this time of
+ each other.
+* Yet more hacks for key handling. Think it is just about working now.
+* Two commands, resize-pane-up and resize-pane-down to resize a pane.
+* Make the window pane code handle panes of different sizes, and add a -l
+ and -p arguments to split-window to specify the new window size in lines
+ or as a percentage.
+
+11 January 2009
+
+* Vertical window splitting. Currently can only split a window into two panes.
+ New split-window command splits (bound to ") and switch-pane command (bound to
+ o) switches between panes.
+
+ close-pane, swap-pane commands are to follow. Also to come are pane resizing,
+ >2 panes, the ability to break a pane out to a full window and vice versa and
+ possibly horizontal splitting.
+
+ Panes are subelements of windows rather than being windows in their own
+ right. I tried to make them windows (so the splitting was at the session or
+ client level) but this rapidly became very complex and invasive. So in the
+ interests of having something working, I just made it so each window can have
+ two child processes instead of one (and it still took me 12 hours straight
+ coding). Now the concept is proven and much of the support code is there,
+ this may change in future if more flexibility is needed.
+* save-buffer command, from Tiago Cunha.
+
+10 January 2009
+
+* New option, lock-after-time. If there is no activity in the period specified
+ by this option (in seconds), tmux will lock the server. Default is 1800 (30
+ minutes), set to 0 to disable.
+* Server locking. Two new commands: set-password to set a password (a
+ preencrypted password may be specified with -c); and lock-server to lock the
+ server until the password is entered. Also an additional command line flag,
+ -U, to unlock from the shell. The default password is blank (any password
+ accepted). If specifying an encrypted password from encrypt(1) in .tmux.conf
+ with -c, don't forget to enclose it in single-quotes (') to prevent shell
+ variable expansion.
+* If a window is created from the command line, tmux will now use the same
+ current working directory for the new process. A new default-path option to
+ sets the working directory for processes created from keys or interactively
+ from the prompt.
+* New mode to display a large clock. Entered with clock-mode command (bound to
+ C-b t by default); two window options: clock-mode-colour and clock-mode-style
+ (12 or 24). This will probably be used as the basis for window locking.
+* New command, server-info, to show some server information and terminal
+ details.
+
+09 January 2009
+
+* Stop using ncurses variables and instead build a table of the codes we want
+ into an array for each terminal type. This makes the code a little more
+ untidy in places but gets rid of the awful global variables and calling
+ setterm all the time, and shoves all the ncurses-dependent mess into a single
+ file, tty-term.c. It also allows overriding single terminal codes, this is
+ used to fix rxvt on some platforms (where it is missing dch) and in future
+ may allow user customisation a la vim.
+* Update key handling code. Simplify, support ctrl properly and add a new
+ window option (xterm-keys) to output xterm key codes including ctrl and,
+ if available, alt and shift.
+
+08 January 2009
+
+* If built without DEBUG (the release versions), don't cause a fatal error if
+ the grid functions notice an input error, just log and ignore the
+ request. This might mean me getting shouted at less often when bugs kill
+ long-running sessions, at least in release versions.
+* Hopefully fix cursor out-of-bounds checking when writing to grid. When I
+ wrote the code I must have forgotten that the cursor can be one cell off the
+ right of the screen (yes, I know), so there were number of out-of-bounds/
+ overflow problems.
+
+07 January 2009
+
+* New flag to set and setw, -u, to unset an option (allowing it to inherit from)
+ the global options again.
+* Added more info messages for options changes.
+* A bit of tidying and reorganisation of options code.
+
+06 January 2009
+
+* Don't crash when backspacing if cursor is off the right of the screen,
+ reported by David Chisnall.
+* Complete words at any point inside command in prompt, also use option name
+ as well as command names.
+* Per-client prompt history of up to 100 items.
+* Use a splay tree for key bindings instead of an array. As a side-effect this
+ sorts them when listed.
+
+22 December 2008
+
+* Use the right keys for home and end.
+
+20 December 2008
+
+* Add vim mode for tmux configuration file to examples/, from Tiago Cunha.
+
+15 December 2008
+
+* New command, source-file (alias source), to load a configuration
+ file. Written by Tiago Cunha, many thanks.
+
+13 December 2008
+
+* Work around lack of dch. On Linux, the rxvt termcap doesn't have it (it is
+ lying, but we can't really start disbelieving termcaps...). This is a bit
+ horrible - I can see no way to do it without pretty much redrawing the whole
+ line, but it works...
+
+10 December 2008
+
+* glibc's getopt(3) is useless: it is not POSIX compliant without jumping
+ through non-portable hoops, and the method of resetting it is unclear (the
+ man page on my system says set optind to 1, but other sources say 0). So,
+ import OpenBSD's getopt_long.c into compat/ for use on Linux and use the
+ clearly documented optreset = optind = 1 method. This fixes some strange
+ issues with command parsing (getting the syntax wrong would prevent any
+ further commands being parsed).
+
+06 December 2008
+
+* Bring set/setw/show/showw into line with other commands. This means that by
+ default they now affect the current window (if any); the new -g flag must be
+ passed to set the global options. This changes the behaviour of set/show and
+ WILL BREAK CURRENT CONFIGURATIONS.
+
+ In summary, whether in the configuration file, the command prompt, or a key
+ binding, use -g to set a global option, use -t to specify a particular window
+ or session, or omit both to try and use the current window or session.
+
+ This makes set/show a bit of a pain but is the correct behaviour for
+ setw/showw and is the same as every other command, so we can put up with a
+ bit of pain for consistency.
+* Redo window options. They now work in the same way to session options with a
+ global options set. showw/setw commands now have similar syntax to show/set
+ (including the ability to use abbreviations).
+
+ PLEASE NOTE this includes the following configuration-breaking changes:
+
+ - remain-by-default is now GONE, use "setw -g remain-on-exit" to apply the
+ global window option instead;
+ - mode-keys is now a window option rather than session - use "setw [-g]
+ mode-keys" instead of set.
+
+ There are also some additions:
+
+ - message-fg and message-bg session options to control status line message
+ colours;
+ - mode-fg and mode-bg window options to set colours in window modes such as
+ copy mode.
+
+ The options code still a mess and now there is twice as much of it :-(.
+
+02 December 2008
+
+* Add support for including the window title in status-left or status-right
+ strings by including the character pair "#T". This may be prefixed with
+ a number to specify a maximum length, for example "#24T" to use at most
+ 24 characters of the title.
+* Introduce two new options, status-left-length and status-right-length,
+ control the maximum length of left and right components of the status bar.
+* elinks (and possibly others) bypass the terminal and talk directly to X to
+ restore the window title when exiting. tmux can't know about this particular
+ bit of stupidity so the title ends up strange - the prefix isn't terribly
+ important and elinks is quite useful so just get rid of it.
+
+27 November 2008
+
+* Tweaks to support Dragonfly.
+
+17 November 2008
+
+* tmux 0.5 released.
+
+16 November 2008
+
+* New window option: "utf8"; this must be on (it is off by default) for UTF-8
+ to be parsed. The global/session option "utf8-default" controls the setting
+ for new windows.
+
+ This means that by default tmux does not handle UTF-8. To use UTF-8 by
+ default it is necessary to a) "set utf8-default on" in .tmux.conf b) start
+ tmux with -u on any terminal which support UTF-8.
+
+ It seems a bit unnecessary for this to be a per-window option but that is
+ the easiest way to do it, and it can't do any harm...
+* Enable default colours if op contains \033[39;49m, based on a report from
+ fulvio ciriaco.
+
+12 November 2008
+
+* Keep stack of last windows rather than just most recent; based on a diff from
+ joshe.
+
+04 November 2008
+
+* Don't try to redraw status line when showing a prompt or message; if it does,
+ the status timer is never reset so it redraws on every loop. Spotted by
+ joshe.
+
+09 October 2008
+
+* Translate 256 colours into 16 if 256 is not available, same as screen does.
+* Better support for OSC command (only to set window title now), and also
+ support using APC for the same purpose (some Linux default shell profiles do
+ this).
+
+25 September 2008
+
+* Large internal rewrite to better support 256 colours and UTF-8. Screen data
+ is now stored as single two-way array of structures rather than as multiple
+ separate arrays. Also simplified a lot of code.
+
+ Only external changes are three new flags, -2, -d and -u, which force tmux to
+ assume the terminal supports 256 colours, default colours (useful for
+ xterm-256color which lacks the AX flag), or UTF-8 respectively.
+
+10 September 2008
+
+* Split off colour conversion code from screen code.
+
+09 September 2008
+
+* Initial UTF-8 support. A bit ugly and with a limit of 4096 UTF-8
+ characters per window.
+
+08 September 2008
+
+* 256 colour support. tmux attempts to autodetect the terminal by looking
+ both at what ncurses reports (usually wrong for xterm) and checking if
+ the TERM contains "256col". For xterm TERM=xterm-256color is needed (as
+ well as a build that support 256 colours); this seems to work for rxvt
+ as well. On non-256 colour terminals, high colours are translated to white
+ foreground and black background.
+
+28 August 2008
+
+* Support OS X/Darwin thanks to bsd-poll.c from OpenSSH. Also convert
+ from clock_gettime(2) to gettimeofday(2) as OS X doesn't support the
+ former; microsecond accuracy will have to be sufficient ;-).
+
+07 August 2008
+
+* Lose some unused/useless wrapper functions.
+
+25 July 2008
+
+* Shell variables may now be defined and used in configuration file. Define
+ variables with:
+
+ VAR=1
+
+ And use with:
+
+ renamew ${VAR}
+ renamew "x${VAR}x"
+
+ Also some other fixes to make, for example, "abc""abc" work similarly to
+ the shell.
+
+24 July 2008
+
+* Finally lose inconsistently-used SCREEN_DEF* defines.
+* If cursor mode is on, switch the arrow keys from \033[A to \033OA.
+* Support the numeric keypad in both application and numbers mode. This is
+ different from screen which always keeps it in application mode.
+
+19 July 2008
+
+* Unbreak "set status" - tmux thought it was ambiguous, reported by rivo nurges.
+
+02 July 2008
+
+* Split vi and emacs mode keys into two tables and add an option (mode-keys)
+ to select between them. Default is emacs, use,
+
+ tmux set mode-keys vi
+
+ to change to vi.
+
+ vi mode uses space to start selection, enter to copy selection and escape
+ to clear selection.
+
+01 July 2008
+
+* Protocol versioning. Clients which identify as a different version from the
+ server will be rejected.
+* tmux 0.4 released.
+
+29 June 2008
+
+* Zombie windows. These are not closed when the child process dies. May be
+ set for a window with the new "remain-on-exit" option; the default setting
+ of this flag for new windows may be set with the "remain-by-default" session
+ option.
+
+ A window may be restarted with the respawn-window command:
+
+ respawn-window [-k] [command]
+
+ If -k is given, any existing process running in the window is killed;
+ if command is omitted, the same command as when the window was first
+ created is used.
+
+27 June 2008
+
+* Handle nonexistent session or client to -t properly.
+
+25 June 2008
+
+* select-prompt command to allow a window to be selected at a prompt. Only
+ windows in the current session may be selected. Bound to ' by default.
+ Suggested by merdely.
+* move-window command. Requested by merdely.
+* Support binding alt keys (prefixed with M-). Change default to use
+ C- for ctrl keys (^ is still accepted as an alternative).
+* Slim down default key bindings: support lowercase only.
+* Handle escaped keys properly (parse eg \033b into a single key code) and
+ use this to change copy mode next/previous work to M-f and M-b to match
+ emacs.
+
+24 June 2008
+
+* Next word (C-n/w) and previous word (C-b/b) in copy mode.
+
+23 June 2008
+
+* list-commands command (alias lscm).
+* Split information about options into a table and use it to parse options
+ on input (allowing abbreviations) and to print them with show-options
+ (meaning that bell-action gets a proper string). This turned out a bit ugly
+ though :-/.
+
+22 June 2008
+
+* Do not translate black and white into default if the terminal supports
+ default colours. This was nice to force programs which didn't use default
+ colours to be properly transparent in rxvt/aterm windows with a background
+ image, but it causes trouble if someone redefines the default foreground and
+ background (to have black on white or something).
+
+21 June 2008
+
+* Naive tab completion in the command prompt. This only completes command
+ names if a) they are at the start of the text b) the cursor is at
+ the end of the text c) the text contains no spaces.
+* Only attempt to set the title where TERM looks like an xterm (contains
+ "xterm", "rxvt" or is "screen"). I hate this but I don't see a better way:
+ setting the title actually kills some other terminals pretty much dead.
+* Strip padding out of terminfo(5) strings. Currently the padding is just
+ ignored, this may need to be altered if there are any software terminals
+ out there that actually need it.
+
+20 June 2008
+
+* buffer-limit option to set maximum size of buffer stack. Default is 9.
+* Initial buffer improvements. Each session has a stack of buffers and each
+ buffer command takes a -b option to manipulate items on the stack. If -b
+ is omitted, the top entry is used. The following commands are currently
+ available:
+
+ set-buffer [-b index] [-t target-session] string
+ paste-buffer [-d] [-b index] [-t target-window]
+ delete-buffer [-b index] [-t target-session]
+ show-buffers [-t target-session]
+ show-buffer [-b index] [-t target-session]
+
+ -d to paste-buffer deletes the buffer after pasting it.
+* New option, display-time, sets the time status line messages stay on screen
+ (unless a key is pressed). Set in milliseconds, default is 750 (0.75 seconds).
+ The timer is only checked every 100 ms or so.
+
+19 June 2008
+
+* Use "status" consistently for status line option, and prefix for "prefix" key
+ option.
+* Allow commands to be entered at a prompt. This is triggered with the
+ command-prompt command, bound to : by default.
+* Show status messages properly, without blocking the server.
+
+18 June 2008
+
+* New option, set-titles. On by default, this attempts to set the window title
+ using the \e]2;...\007 xterm code.
+
+ Note that elinks requires the STY environment variable (used by screen) to be
+ set before it will set the window title. So, if you want window titles set by
+ elinks, set STY before running it (any value will do). I can't do this for all
+ windows since setting it to an invalid value breaks screen.
+* Show arrows at either end of status line when scrolled if more windows
+ exist. Highlight the arrow if a hidden window has activity or bell.
+* Scroll the status line to show the current window if necessary. Also handle
+ windows smaller than needed better (show a blank status line instead of
+ hanging or crashing).
+
+17 June 2008
+
+* tmux 0.3 released.
+
+16 June 2008
+
+* Add some information messages when window options are changed, suggested by
+ Mike Erdely. Also add a -q command-line option to suppress them.
+* show-window-options (showw) command.
+
+15 June 2008
+
+* show-options (show) command to show one or all options.
+
+14 June 2008
+
+* New window options: force-width and force-height. This will force a window
+ to an arbitrary width and height (0 for the default unlimited). This is
+ neat for emacs which doesn't have a sensible way to force hard wrapping at 80
+ columns. Also, don't try to be clever and use clr_eol when redrawing the
+ whole screen, it causes trouble since the redraw functions are used to draw
+ the blank areas too.
+* Clear the blank area below windows properly when they are smaller than client,
+ also add an indicator line to show the vertical limit.
+* Don't die on empty strings in config file, reported by Will Maier.
+
+08 June 2008
+
+* Set socket mode +x if any sessions are attached and -x if not.
+
+07 June 2008
+
+* Make status-interval actually changeable.
+
+06 June 2008
+
+* New window option: aggressive-resize. Normally, windows are resized to the
+ size of the smallest attached session to which they are linked. This means a
+ window only changes size when sessions are detached or attached, or they are
+ linked or unlinked from a session. This flag changes a window to be the size
+ of the smallest attached session for which it is the current window - it is
+ resized every time a session changes to it or away from it. This is nice for
+ things that handle SIGWINCH well (like irssi) and bad for things like shells.
+* The server now exits when no sessions remain.
+* Fix bug with inserting characters with TERM=xterm-color.
+
+05 June 2008
+
+* Completely reorganise command parsing. Much more common code in cmd-generic.c
+ and a new way of specifying windows, clients or sessions. Now, most commands
+ take a -t argument, which specifies a client, a session, or a window target.
+ Clients and sessions are given alone (sessions are fnmatch(3)d and
+ clients currently not), windows are give by (client|session):index. For
+ example, if a user is in session "1" window 0 on /dev/ttypi, these should all
+ be equivalent:
+
+ tmux renamew newname (current session and window)
+ tmux renamew -t: newname (current session and window)
+ tmux renamew -t:0 newname (current session, window 0)
+ tmux renamew -t0 newname (current session, window 0)
+ tmux renamew -t1:0 newname (session 1, window 0)
+ tmux renamew -t1: newname (session 1's current window)
+ tmux renamew -t/dev/ttypi newname (client /dev/ttypi's current
+ session and window)
+ tmux renamew -t/dev/ttypi: newname (client /dev/ttypi's current
+ session and window)
+ tmux renamew -t/dev/ttypi:0 newname (client /dev/ttypi's current
+ session, window 0)
+
+ This does have some downsides, for example, having to use -t on selectw,
+
+ tmux selectw -t7
+
+ is annoying. But then using non-flagged arguments would mean renaming the
+ current window would need to be something like:
+
+ tmux renamew : newname
+
+ It might be better not to try and be so consistent; comments to the usual
+ address ;-).
+* Infrastructure for printing arguments in list-keys output. Easy ones only for
+ now.
+
+04 June 2008
+
+* Add some vi(1) key bindings in copy mode, and support binding ^[, ^\, ^]
+ ^^ and ^_. Both from/prompted by Will Maier.
+* setw monitor-activity and set status without arguments now toggle the current
+ value; suggested by merdely.
+* New command set-window-option (alias setw) to set the single current window
+ option: monitor-activity to determine whether window activity is shown in
+ the status bar for that window (default off).
+* Change so active/bell windows are inverted in status line.
+* Activity monitoring - window with activity are marked in status line. No
+ way to disable this/filter windows yet.
+* Brought select-window command into line with everything else; it now uses
+ -i for the window index.
+* Strings to display on the left and right of the status bar may now be set
+ with the status-left and status-right options. These are passed through
+ strftime(3) before being displayed. The status bar is automatically updated
+ at an interval set by the status-interval option. The default is to display
+ nothing on the left and the date and time on the left; the default update
+ interval is 15 seconds.
+
+03 June 2008
+
+* Per session options. Setting options without specifying a session sets the
+ global options as normal (global options are inherited by all sessions);
+ passing -c or -s will set the option only for that session.
+* Because a client has a session attached, any command needing a session can
+ take a client and use its session. So, anything that used to accept -s now
+ accepts -c as well.
+* -s to specify session name now supports fnmatch(3) wildcards; if multiple
+ sessions are found, or if no -s is specified, the most newly created is used.
+* If no command is specified, assume new-session. As a byproduct, clean up
+ command default values into separate init functions.
+* kill-server command.
+
+02 June 2008
+
+* New command, start-server (alias "start"), to start the tmux server and do
+ nothing else. This is good if you have a configuration file which creates
+ windows or sessions (like me): in that case, starting the server the first
+ time tmux new is run is bad since it creates a new session and window (as
+ it is supposed to - starting the server is a side-effect).
+
+ Instead, I have a little script which does the equivalent of:
+
+ tmux has -s0 2>/dev/null || tmux start
+ tmux attach -d -s0
+
+ And I use it to start the server if necessary and attach to my primary
+ session.
+* Basic configuration file in ~/.tmux.conf or specified with -f. This is file
+ contains a set of tmux commands that are run the first time the server is
+ started. The configuration commands are executed before any others, so
+ if you have a configuration file that contains:
+
+ new -d
+ neww -s0
+
+ And you do the following without an existing server running:
+
+ tmux new
+
+ You will end up with two sessions, session 0 with two windows (created by
+ the configuration file) and your client attached to session 1 with one
+ window (created by the command-line command). I'm not completely happy with
+ this, it seems a little non-obvious, but I haven't yet decided what to do
+ about it.
+
+ There is no environment variable handling or other special stuff yet.
+
+ In the future, it might be nice to be able to have per-session configuration
+ settings, probably by having conditionals in the file (so you could, for
+ example, have commands to define a particular window layout that would only
+ be invoked if you called tmux new -smysession and mysession did not already
+ exist).
+* BIG CHANGE: -s and -c to specify session name and client name are now passed
+ after the command rather than before it. So, for example:
+
+ tmux -s0 neww
+
+ Becomes:
+
+ tmux neww -s0
+
+ This is to allow them to be used in the (forthcoming) configuration file
+ THIS WILL BREAK ANY CURRENT SCRIPTS OR ALIASES USING -s OR -c.
+
+01 June 2008
+
+* Bug fix: don't die if -k passed to link-window and the destination doesn't
+ exist.
+* New command, send-keys, will send a set of keys to a window.
+
+31 May 2008
+
+* Fix so tmux doesn't hang if the initial window fails for some reason. This
+ was highlighted by problems on Darwin, thanks to Elias Pipping for the report
+ and access to a test account. (tmux still won't work on Darwin since its
+ poll(2) is broken.)
+
+02 January 2008
+
+* Don't attempt to reset the tty on exit if it has been closed externally.
+
+06 December 2007
+
+* Restore checks for required termcap entries and add a few more obvious
+ emulations.
+* Another major reorganisation, this time of screen handling. A new set of
+ functions, screen_write_*, are now used to write to a screen and a tty
+ simultaneously. These are used by the input parser to update the base
+ window screen and also by the different modes which now interpose their own
+ screen.
+
+30 November 2007
+
+* Support \ek...\e\ to set window name.
+
+27 November 2007
+
+* Enable/disable mouse when asked, if terminal claims to support it. Mouse
+ sequences are just passed through unaltered for the moment.
+* Big internal reorganisation. Rather than leaving control of the tty solely in
+ the client and piping all data through a socket to it, change so that the
+ server opens the tty again and reads and writes to it directly. This avoids
+ a lot of buffering and copying. Also reorganise the redrawing stuff so that
+ everything goes through screen_draw_* - this makes the code simpler, but
+ still needs broken up more, and all the ways of writing to screens should be
+ more consistent.
+
+26 November 2007
+
+* Rather than shifting up one line at a time once the history is full,
+ shift by 10% of the history each time. This is faster.
+* Add ^A and ^E to copy mode to move to start-of-line/end-of-line.
+
+24 November 2007
+
+* Support for alt charset mode (VT100 graphics characters).
+
+23 November 2007
+
+* Mostly complete copy & paste. Copy mode entered with C-b [ (copy-mode
+ command). In copy mode, arrow keys/page up/page down/hjkl/C-u/C-f navigate,
+ space or C-space starts selection, and enter or C-w copies and (important!)
+ exits copy mode. C-b ] (paste-buffer) pastes into current window. No
+ extra utility keys (bol/eol/clear selection/etc), only one single buffer,
+ and no buffer manipulation commands (clear/view/etc) yet. The code is also
+ fugly :-(.
+* history-limit option to set maximum history. Does not apply retroactively to
+ existing windows! Lines take up a variable amount of space, but a reasonable
+ guess for an 80-column terminal is 250 KB per 1000 lines (of history used,
+ an empty history takes no space).
+
+21 November 2007
+
+* Create every line as zero length and only expand it as data is written,
+ rather than creating at full size immediately.
+* Make command output (eg list-keys) go to a scrollable window similar to
+ scroll mode.
+* Redo screen redrawing so it is a) readable b) split into utility functions
+ that can be used outside screen.c. Use these to make scroll mode only
+ redraw what it has to which gets rid of irritating flickering status box and
+ makes it much faster.
+* Full line width memory and horizontal scrolling in history.
+* Initial support for scroll history. = to enter scrolling mode, and then
+ vi keys or up/down/pgup/pgdown to navigate. Q to exit. No horizontal history
+ yet (need per-line sizes) and a few kinks to be worked out (resizing while in
+ history mode will probably cause trouble).
+
+20 November 2007
+
+* Fix format string error with "must specify a client" message. Also
+ sprinkle some printflike tags.
+* tmux 0.1 released.
+
+17 November 2007
+
+* (nicm) Add -k option to link-window to kill target window if it exists.
+
+16 November 2007
+
+* (nicm) Split in-client display into two columns. This is a hack but not a lot
+ more so than that bit is already and it helps with lots of keys.
+* (nicm) switch-client command to switch client between different sessions. This
+ is pretty cool:
+
+ $ tmux bind q switch 0
+ $ tmux bind w switch 1
+
+ Then you can switch between sessions 0 and 1 with a key :-).
+* (nicm) Accept "-c client-tty" on command line to allow client manipulation
+ commands, and change detach-/refresh-session to detach-/refresh-client (this
+ loses the -a behaviour, but at some point -session versions may return, and
+ -c will allow fnmatch(3)).
+* (nicm) List available commands on ambiguous command.
+
+12 November 2007
+
+* (nicm) If the terminal supports default colours (AX present), force black
+ background and white foreground to default. This is useful on transparent
+ *terms for programs which don't do it themselves (like most(1)).
+* (nicm) Fill in the rest of the man page.
+* (nicm) kill-session command.
+
+09 November 2007
+
+* (nicm) C-space is now "^ " not "^@".
+* (nicm) Support tab (\011).
+* (nicm) Initial man page outline.
+* (nicm) -V to show version.
+* (nicm) rename-session command.
+
+08 November 2007
+
+* (nicm) Check for required terminal capabilities on start.
+
+31 October 2007
+
+* (nicm) Linux port.
+
+30 October 2007
+
+* (nicm) swap-window command. Same as link-window but swaps windows.
+
+26 October 2007
+
+* (nicm) Saving scroll region on \e7 causes problems with ncmpc so I guess
+ it is not required.
+* (nicm) unlink-window command.
+* (nicm) link-window command to link an existing window into another session
+ (or another index in the same session). Syntax:
+
+ tmux -s dstname link-window [-i dstidx] srcname srcidx
+
+* (nicm) Redo window data structures. The global array remains, but each per-
+ session list is now a RB tree of winlink structures. This disassociates the
+ window index from the array size (allowing arbitrary indexes) which still
+ allowing windows to have multiple indexes.
+
+25 October 2007
+
+* (nicm) has-session command: checks if session exists.
+
+24 October 2007
+
+* (nicm) Support for \e6n to request cursor position. resize(1) now works.
+* (nicm) Support for \e7, \e8 save/restore cursor and attribute sequences.
+ Currently don't save mode (probably should). Also change some cases where
+ out-of-bound values are ignored to limit them to within range (there are
+ others than need to be checked too).
+
+23 October 2007
+
+* (nicm) Lift limit on session name passed with -s.
+* (nicm) Show size in session/window lists.
+* (nicm) Pass tty up to server when client identifies and add a list-clients
+ command to list connected clients.
+
+20 October 2007
+
+* (nicm) Add default-command option and change default to be $SHELL rather than
+ $SHELL -l. Also try to read shell from passwd db if $SHELL isn't present.
+
+19 October 2007
+
+* (nicm) -n on new-session is now -s, and -n is now the initial window name.
+ This was documented but not implemented :-/.
+* (nicm) kill-window command, bound to & by default (because it should be hard
+ to hit accidentally).
+* (nicm) bell-style option with three choices: "none" completely ignore bell;
+ "any" pass through a bell in any window to current; "current" ignore bells
+ except in current window. This applies only to the bell terminal signal,
+ the status bar always reflects any bells.
+* (nicm) Refresh session command.
+
+12 October 2007
+
+* (nicm) Add a warning if $TMUX exists on new/attach.
+* (nicm) send-prefix command. Bound to C-b by default.
+* (nicm) set status, status-fg, status-bg commands. fg and bg are as a number
+ from 0 to 8 or a string ("red", "blue", etc). status may be 1/0, on/off,
+ yes/no.
+* (nicm) Make status line mark window in yellow on bell.
+
+04 October 2007
+
+* (nicm) -d option to attach to detach all other clients on the same session.
+* (nicm) Partial resizing support. Still buggy. A C-b S and back sometimes fixes
+ it when it goes wonky.
+* (mxey) Added my tmux start script as an example (examples/start-tmux.sh).
+* (mxey) New sessions can now be given a command for their first window.
+* (mxey) Fixed usage statement for new-window.
+* (nicm) attach-session (can't believe I forgot it until now!) and list-windows
+ commands.
+* (nicm) rename-window and select-window commands.
+* (nicm) set-option command (alias set): "tmux set-option prefix ^A".
+* (nicm) Key binding and unbinding is back.
+
+03 October 2007
+
+* (nicm) {new,next,last,previous}-window.
+* (nicm) Rewrite command handling so commands are much more generic and the
+ same commands are used for command line and keys (although most will probably
+ need to check how they are called). Currently incomplete (only new/detach/ls
+ implemented). Change: -s is now passed before command again!
+* (nicm) String number arguments. So you can do: tmux bind ^Q create "blah".
+* (nicm) Key binding. tmux bind key command [argument] and tmux unbind key.
+ Key names are in a table in key-string.c, plus A is A, ^A is ctrl-A.
+ Possible commands are in cmd.c (look at cmd_bind_table).
+* (nicm) Move command parsing into the client. Also rename some messages and
+ tidy up a few bits. Lots more tidying up needed :-/.
+
+02 October 2007
+
+* (nicm) Redraw client status lines on rename.
+* (nicm) Error on ambiguous command.
+
+01 October 2007
+
+* (nicm) Restore window title handling.
+* (nicm) Simple uncustomisable status line with window list.
+
+30 September 2007
+
+* (nicm) Window info command for debugging, C-b I.
+
+29 September 2007
+
+* (nicm) Deleting/inserting lines should follow scrolling region. Fix.
+* (nicm) Allow creation of detached sessions: "tmux new-session -d".
+* (nicm) Permit error messages to be passed back for transient clients like
+ rename. Also make rename -i work.
+* (nicm) Pass through bell in any window to current.
+
+28 September 2007
+
+* (nicm) Major rewrite of input parser:
+ - Lose the old weirdness in favour of a state machine.
+ - Merge in parsing from screen.c.
+ - Split key parsing off into a separate file.
+ This is step one towards hopefully allowing a status line. It requires
+ that we output data as if the terminal had one line less than it really does -
+ a serious problem when it comes to things like scrolling. This change
+ consolidates all the range checking and limiting together which should make
+ it easier.
+* (mxey) Added window renaming, like "tmux rename [-s session] [-i index] name"
+
+27 September 2007
+
+* Split "tmux list" into "tmux list-sessions" (ls) and "list-windows" (lsw).
+* New command session selection:
+ - if name is specified, look for it and use it if it exists, otherwise
+ error
+ - if no name specified, try the current session from $TMUX
+ - if $TMUX doesn't exist, and there is only one session, use it,
+ otherwise error
+
+26 September 2007
+
+* Add command aliases, so "ls" is an alias for "list".
+* Rename some commands and alter syntax to take options after a la CVS. Also
+ change some flags. So:
+
+ tmux -s/socket -nabc new
+
+ Becomes:
+
+ tmux -S/socket new -sabc
+
+* Major tidy and split of client/server code.
+
+22 September 2007
+
+* Window list command (C-b W). Started by Maximilian Gass, finished by me.
+
+20 September 2007
+
+* Specify meta via environment variable (META).
+* Record last window and ^L key to switch to it. Largely from Maximilian Gass.
+* Reset ignored signals in child after forkpty, makes ^C work.
+* Wrap on next/previous. From Maximilian Gass.
+
+19 September 2007
+
+* Don't renumber windows on close.
+
+28 August 2007
+
+* Scrolling region (\e[r) support.
+
+27 August 2007
+
+* Change screen.c to work more logically and hopefully fix heap corruption.
+
+09 July 2007
+
+* Initial import to CVS. Basic functions are working, albeit with a couple of
+ showstopper memory bugs and many missing features. Detaching, reattaching,
+ creating new sessions, listing sessions work acceptably for using with shells.
+ Simple curses programs (top, systat, tetris) and more complicated ones (mutt,
+ emacs) that don't require scrolling regions (ESC[r) mostly work fine
+ (including mutt, emacs). No status bar yet and no key remapping or other
+ customisation.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..fd02a99
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,18 @@
+THIS IS FOR INFORMATION ONLY, CODE IS UNDER THE LICENCE AT THE TOP OF ITS FILE.
+
+The README, CHANGES, FAQ and TODO files are licensed under the ISC license. All
+other files have a license and copyright notice at their start, typically:
+
+Copyright (c) <author>
+
+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.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..fb94e82
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,236 @@
+# Makefile.am
+
+# Obvious program stuff.
+bin_PROGRAMS = tmux
+CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
+
+# Distribution tarball options.
+EXTRA_DIST = \
+ CHANGES README README.ja COPYING example_tmux.conf \
+ osdep-*.c mdoc2man.awk tmux.1
+dist_EXTRA_tmux_SOURCES = compat/*.[ch]
+
+# Preprocessor flags.
+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_TERM='"@DEFAULT_TERM@"'
+
+# Additional object files.
+LDADD = $(LIBOBJS)
+
+# Set flags for gcc.
+if IS_GCC
+AM_CFLAGS += -std=gnu99 -O2
+if IS_DEBUG
+AM_CFLAGS += -g
+AM_CFLAGS += -Wno-long-long -Wall -W -Wformat=2
+AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations
+AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare
+AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wcast-align
+AM_CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes
+AM_CFLAGS += -Wno-unused-result -Wno-format-y2k
+if IS_DARWIN
+AM_CFLAGS += -Wno-deprecated-declarations -Wno-cast-align
+endif
+AM_CPPFLAGS += -DDEBUG
+endif
+AM_CPPFLAGS += -iquote.
+endif
+
+# Set flags for Solaris.
+if IS_SUNOS
+if IS_GCC
+AM_CPPFLAGS += -D_XPG6
+else
+AM_CPPFLAGS += -D_XPG4_2
+endif
+endif
+
+# Set flags for Sun CC.
+if IS_SUNCC
+AM_CFLAGS += -erroff=E_EMPTY_DECLARATION
+endif
+
+# Set _LINUX_SOURCE_COMPAT for AIX for malloc(0).
+if IS_AIX
+AM_CPPFLAGS += -D_LINUX_SOURCE_COMPAT=1
+endif
+
+# Set flags for NetBSD.
+if IS_NETBSD
+AM_CPPFLAGS += -D_OPENBSD_SOURCE
+endif
+
+# Set flags for Haiku.
+if IS_HAIKU
+AM_CPPFLAGS += -D_BSD_SOURCE
+endif
+
+# 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
+nodist_tmux_SOURCES = osdep-@PLATFORM@.c
+
+# Add compat file for forkpty.
+if NEED_FORKPTY
+nodist_tmux_SOURCES += compat/forkpty-@PLATFORM@.c
+endif
+
+# Add compat file for systemd.
+if HAVE_SYSTEMD
+nodist_tmux_SOURCES += compat/systemd.c
+endif
+
+# Add compat file for utf8proc.
+if HAVE_UTF8PROC
+nodist_tmux_SOURCES += compat/utf8proc.c
+endif
+
+if NEED_FUZZING
+check_PROGRAMS = fuzz/input-fuzzer
+fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS)
+fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
+endif
+
+# Install tmux.1 in the right format.
+install-exec-hook:
+ if test x@MANFORMAT@ = xmdoc; then \
+ sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1 \
+ >$(srcdir)/tmux.1.mdoc; \
+ else \
+ sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1| \
+ $(AWK) -f $(srcdir)/mdoc2man.awk >$(srcdir)/tmux.1.man; \
+ fi
+ $(mkdir_p) $(DESTDIR)$(mandir)/man1
+ $(INSTALL_DATA) $(srcdir)/tmux.1.@MANFORMAT@ \
+ $(DESTDIR)$(mandir)/man1/tmux.1
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..979172b
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,1296 @@
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile.am
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = tmux$(EXEEXT)
+
+# Set flags for gcc.
+@IS_GCC_TRUE@am__append_1 = -std=gnu99 -O2
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@am__append_2 = -g -Wno-long-long -Wall -W \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wformat=2 -Wmissing-prototypes \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wstrict-prototypes \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wmissing-declarations \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wwrite-strings -Wshadow \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wpointer-arith -Wsign-compare \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wundef -Wbad-function-cast \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Winline -Wcast-align \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wdeclaration-after-statement \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wno-pointer-sign -Wno-attributes \
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@ -Wno-unused-result -Wno-format-y2k
+@IS_DARWIN_TRUE@@IS_DEBUG_TRUE@@IS_GCC_TRUE@am__append_3 = -Wno-deprecated-declarations -Wno-cast-align
+@IS_DEBUG_TRUE@@IS_GCC_TRUE@am__append_4 = -DDEBUG
+@IS_GCC_TRUE@am__append_5 = -iquote.
+
+# Set flags for Solaris.
+@IS_GCC_TRUE@@IS_SUNOS_TRUE@am__append_6 = -D_XPG6
+@IS_GCC_FALSE@@IS_SUNOS_TRUE@am__append_7 = -D_XPG4_2
+
+# Set flags for Sun CC.
+@IS_SUNCC_TRUE@am__append_8 = -erroff=E_EMPTY_DECLARATION
+
+# Set _LINUX_SOURCE_COMPAT for AIX for malloc(0).
+@IS_AIX_TRUE@am__append_9 = -D_LINUX_SOURCE_COMPAT=1
+
+# Set flags for NetBSD.
+@IS_NETBSD_TRUE@am__append_10 = -D_OPENBSD_SOURCE
+
+# Set flags for Haiku.
+@IS_HAIKU_TRUE@am__append_11 = -D_BSD_SOURCE
+
+# Add compat file for forkpty.
+@NEED_FORKPTY_TRUE@am__append_12 = compat/forkpty-@PLATFORM@.c
+
+# Add compat file for systemd.
+@HAVE_SYSTEMD_TRUE@am__append_13 = compat/systemd.c
+
+# Add compat file for utf8proc.
+@HAVE_UTF8PROC_TRUE@am__append_14 = compat/utf8proc.c
+@NEED_FUZZING_TRUE@check_PROGRAMS = fuzz/input-fuzzer$(EXEEXT)
+subdir = .
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
+ $(am__configure_deps) $(am__DIST_COMMON)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+LIBOBJDIR = compat/
+fuzz_input_fuzzer_SOURCES = fuzz/input-fuzzer.c
+am__dirstamp = $(am__leading_dot)dirstamp
+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 $@
+dist_tmux_OBJECTS = alerts.$(OBJEXT) arguments.$(OBJEXT) \
+ attributes.$(OBJEXT) cfg.$(OBJEXT) client.$(OBJEXT) \
+ cmd-attach-session.$(OBJEXT) cmd-bind-key.$(OBJEXT) \
+ cmd-break-pane.$(OBJEXT) cmd-capture-pane.$(OBJEXT) \
+ cmd-choose-tree.$(OBJEXT) cmd-command-prompt.$(OBJEXT) \
+ cmd-confirm-before.$(OBJEXT) cmd-copy-mode.$(OBJEXT) \
+ cmd-detach-client.$(OBJEXT) cmd-display-menu.$(OBJEXT) \
+ cmd-display-message.$(OBJEXT) cmd-display-panes.$(OBJEXT) \
+ cmd-find-window.$(OBJEXT) cmd-find.$(OBJEXT) \
+ cmd-if-shell.$(OBJEXT) cmd-join-pane.$(OBJEXT) \
+ cmd-kill-pane.$(OBJEXT) cmd-kill-server.$(OBJEXT) \
+ cmd-kill-session.$(OBJEXT) cmd-kill-window.$(OBJEXT) \
+ cmd-list-buffers.$(OBJEXT) cmd-list-clients.$(OBJEXT) \
+ cmd-list-keys.$(OBJEXT) cmd-list-panes.$(OBJEXT) \
+ cmd-list-sessions.$(OBJEXT) cmd-list-windows.$(OBJEXT) \
+ cmd-load-buffer.$(OBJEXT) cmd-lock-server.$(OBJEXT) \
+ cmd-move-window.$(OBJEXT) cmd-new-session.$(OBJEXT) \
+ cmd-new-window.$(OBJEXT) cmd-parse.$(OBJEXT) \
+ cmd-paste-buffer.$(OBJEXT) cmd-pipe-pane.$(OBJEXT) \
+ cmd-queue.$(OBJEXT) cmd-refresh-client.$(OBJEXT) \
+ cmd-rename-session.$(OBJEXT) cmd-rename-window.$(OBJEXT) \
+ cmd-resize-pane.$(OBJEXT) cmd-resize-window.$(OBJEXT) \
+ cmd-respawn-pane.$(OBJEXT) cmd-respawn-window.$(OBJEXT) \
+ cmd-rotate-window.$(OBJEXT) cmd-run-shell.$(OBJEXT) \
+ cmd-save-buffer.$(OBJEXT) cmd-select-layout.$(OBJEXT) \
+ cmd-select-pane.$(OBJEXT) cmd-select-window.$(OBJEXT) \
+ cmd-send-keys.$(OBJEXT) cmd-server-access.$(OBJEXT) \
+ cmd-set-buffer.$(OBJEXT) cmd-set-environment.$(OBJEXT) \
+ cmd-set-option.$(OBJEXT) cmd-show-environment.$(OBJEXT) \
+ cmd-show-messages.$(OBJEXT) cmd-show-options.$(OBJEXT) \
+ cmd-show-prompt-history.$(OBJEXT) cmd-source-file.$(OBJEXT) \
+ cmd-split-window.$(OBJEXT) cmd-swap-pane.$(OBJEXT) \
+ cmd-swap-window.$(OBJEXT) cmd-switch-client.$(OBJEXT) \
+ cmd-unbind-key.$(OBJEXT) cmd-wait-for.$(OBJEXT) cmd.$(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) \
+ 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 = \
+@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)
+tmux_OBJECTS = $(dist_tmux_OBJECTS) $(nodist_tmux_OBJECTS)
+tmux_LDADD = $(LDADD)
+tmux_DEPENDENCIES = $(LIBOBJS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@
+depcomp = $(SHELL) $(top_srcdir)/etc/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+am__yacc_c2h = sed -e s/cc$$/hh/ -e s/cpp$$/hpp/ -e s/cxx$$/hxx/ \
+ -e s/c++$$/h++/ -e s/c$$/h/
+YACCCOMPILE = $(YACC) $(AM_YFLAGS) $(YFLAGS)
+AM_V_YACC = $(am__v_YACC_@AM_V@)
+am__v_YACC_ = $(am__v_YACC_@AM_DEFAULT_V@)
+am__v_YACC_0 = @echo " YACC " $@;
+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_EXTRA_tmux_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+CSCOPE = cscope
+AM_RECURSIVE_TARGETS = cscope
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/compat/asprintf.c $(top_srcdir)/compat/base64.c \
+ $(top_srcdir)/compat/cfmakeraw.c \
+ $(top_srcdir)/compat/clock_gettime.c \
+ $(top_srcdir)/compat/closefrom.c \
+ $(top_srcdir)/compat/daemon-darwin.c \
+ $(top_srcdir)/compat/daemon.c $(top_srcdir)/compat/err.c \
+ $(top_srcdir)/compat/explicit_bzero.c \
+ $(top_srcdir)/compat/fdforkpty.c $(top_srcdir)/compat/fgetln.c \
+ $(top_srcdir)/compat/freezero.c \
+ $(top_srcdir)/compat/getdtablecount.c \
+ $(top_srcdir)/compat/getdtablesize.c \
+ $(top_srcdir)/compat/getline.c $(top_srcdir)/compat/getopt.c \
+ $(top_srcdir)/compat/getpeereid.c \
+ $(top_srcdir)/compat/getprogname.c \
+ $(top_srcdir)/compat/imsg-buffer.c $(top_srcdir)/compat/imsg.c \
+ $(top_srcdir)/compat/memmem.c \
+ $(top_srcdir)/compat/reallocarray.c \
+ $(top_srcdir)/compat/recallocarray.c \
+ $(top_srcdir)/compat/setenv.c \
+ $(top_srcdir)/compat/setproctitle.c \
+ $(top_srcdir)/compat/strcasestr.c \
+ $(top_srcdir)/compat/strlcat.c $(top_srcdir)/compat/strlcpy.c \
+ $(top_srcdir)/compat/strndup.c $(top_srcdir)/compat/strnlen.c \
+ $(top_srcdir)/compat/strsep.c $(top_srcdir)/compat/strtonum.c \
+ $(top_srcdir)/compat/unvis.c $(top_srcdir)/compat/vis.c \
+ $(top_srcdir)/etc/compile $(top_srcdir)/etc/config.guess \
+ $(top_srcdir)/etc/config.sub $(top_srcdir)/etc/depcomp \
+ $(top_srcdir)/etc/install-sh $(top_srcdir)/etc/missing \
+ $(top_srcdir)/etc/ylwrap COPYING README cmd-parse.c \
+ etc/compile etc/config.guess etc/config.sub etc/depcomp \
+ etc/install-sh etc/missing etc/ylwrap
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+ if test -d "$(distdir)"; then \
+ find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+ && rm -rf "$(distdir)" \
+ || { sleep 5 && rm -rf "$(distdir)"; }; \
+ else :; fi
+am__post_remove_distdir = $(am__remove_distdir)
+DIST_ARCHIVES = $(distdir).tar.gz
+GZIP_ENV = --best
+DIST_TARGETS = dist-gzip
+distuninstallcheck_listfiles = find . -type f -print
+am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
+ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CFLAGS = @AM_CFLAGS@ $(am__append_1) $(am__append_2) \
+ $(am__append_3) $(am__append_8)
+
+# Preprocessor flags.
+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_TERM='"@DEFAULT_TERM@"' $(am__append_4) $(am__append_5) \
+ $(am__append_6) $(am__append_7) $(am__append_9) \
+ $(am__append_10) $(am__append_11)
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_LDFLAGS = @AM_LDFLAGS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFAULT_TERM = @DEFAULT_TERM@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FUZZING_LIBS = @FUZZING_LIBS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBEVENT_CFLAGS = @LIBEVENT_CFLAGS@
+LIBEVENT_CORE_CFLAGS = @LIBEVENT_CORE_CFLAGS@
+LIBEVENT_CORE_LIBS = @LIBEVENT_CORE_LIBS@
+LIBEVENT_LIBS = @LIBEVENT_LIBS@
+LIBNCURSESW_CFLAGS = @LIBNCURSESW_CFLAGS@
+LIBNCURSESW_LIBS = @LIBNCURSESW_LIBS@
+LIBNCURSES_CFLAGS = @LIBNCURSES_CFLAGS@
+LIBNCURSES_LIBS = @LIBNCURSES_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTINFO_CFLAGS = @LIBTINFO_CFLAGS@
+LIBTINFO_LIBS = @LIBTINFO_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANFORMAT = @MANFORMAT@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PLATFORM = @PLATFORM@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VERSION = @VERSION@
+XOPEN_DEFINES = @XOPEN_DEFINES@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
+
+# Distribution tarball options.
+EXTRA_DIST = \
+ CHANGES README README.ja COPYING example_tmux.conf \
+ osdep-*.c mdoc2man.awk tmux.1
+
+dist_EXTRA_tmux_SOURCES = compat/*.[ch]
+
+# Additional object files.
+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
+
+nodist_tmux_SOURCES = osdep-@PLATFORM@.c $(am__append_12) \
+ $(am__append_13) $(am__append_14)
+@NEED_FUZZING_TRUE@fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS)
+@NEED_FUZZING_TRUE@fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj .y
+am--refresh: Makefile
+ @:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
+ $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ echo ' $(SHELL) ./config.status'; \
+ $(SHELL) ./config.status;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ $(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ $(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+clean-checkPROGRAMS:
+ -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS)
+fuzz/$(am__dirstamp):
+ @$(MKDIR_P) fuzz
+ @: > fuzz/$(am__dirstamp)
+fuzz/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) fuzz/$(DEPDIR)
+ @: > fuzz/$(DEPDIR)/$(am__dirstamp)
+fuzz/input-fuzzer.$(OBJEXT): fuzz/$(am__dirstamp) \
+ fuzz/$(DEPDIR)/$(am__dirstamp)
+
+fuzz/input-fuzzer$(EXEEXT): $(fuzz_input_fuzzer_OBJECTS) $(fuzz_input_fuzzer_DEPENDENCIES) $(EXTRA_fuzz_input_fuzzer_DEPENDENCIES) fuzz/$(am__dirstamp)
+ @rm -f fuzz/input-fuzzer$(EXEEXT)
+ $(AM_V_CCLD)$(fuzz_input_fuzzer_LINK) $(fuzz_input_fuzzer_OBJECTS) $(fuzz_input_fuzzer_LDADD) $(LIBS)
+compat/$(am__dirstamp):
+ @$(MKDIR_P) compat
+ @: > compat/$(am__dirstamp)
+compat/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) compat/$(DEPDIR)
+ @: > compat/$(DEPDIR)/$(am__dirstamp)
+compat/forkpty-@PLATFORM@.$(OBJEXT): compat/$(am__dirstamp) \
+ compat/$(DEPDIR)/$(am__dirstamp)
+compat/systemd.$(OBJEXT): compat/$(am__dirstamp) \
+ compat/$(DEPDIR)/$(am__dirstamp)
+compat/utf8proc.$(OBJEXT): compat/$(am__dirstamp) \
+ compat/$(DEPDIR)/$(am__dirstamp)
+
+tmux$(EXEEXT): $(tmux_OBJECTS) $(tmux_DEPENDENCIES) $(EXTRA_tmux_DEPENDENCIES)
+ @rm -f tmux$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(tmux_OBJECTS) $(tmux_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f compat/*.$(OBJEXT)
+ -rm -f fuzz/*.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alerts.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/arguments.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/attributes.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cfg.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-attach-session.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-bind-key.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-break-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-capture-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-choose-tree.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-command-prompt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-confirm-before.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-copy-mode.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-detach-client.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-display-menu.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-display-message.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-display-panes.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-find-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-find.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-if-shell.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-join-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-kill-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-kill-server.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-kill-session.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-kill-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list-buffers.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list-clients.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list-keys.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list-panes.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list-sessions.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-list-windows.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-load-buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-lock-server.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-move-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-new-session.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-new-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-parse.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-paste-buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-pipe-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-queue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-refresh-client.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-rename-session.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-rename-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-resize-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-resize-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-respawn-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-respawn-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-rotate-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-run-shell.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-save-buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-select-layout.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-select-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-select-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-send-keys.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-server-access.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-set-buffer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-set-environment.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-set-option.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-show-environment.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-show-messages.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-show-options.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-show-prompt-history.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-source-file.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-split-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-swap-pane.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-swap-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-switch-client.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-unbind-key.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-wait-for.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colour.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/control-notify.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/control.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/environ.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/format-draw.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/format.Po@am__quote@
+@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)/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@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/key-bindings.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/key-string.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/layout-custom.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/layout-set.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/layout.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/menu.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mode-tree.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/names.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options-table.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/options.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osdep-@PLATFORM@.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paste.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/popup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/regsub.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resize.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/screen-redraw.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/screen-write.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/screen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server-acl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server-client.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server-fn.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spawn.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/status.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/style.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tmux.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-acs.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty-features.Po@am__quote@
+@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.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@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-clock.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-copy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-customize.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-tree.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xmalloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/asprintf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/base64.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/cfmakeraw.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/clock_gettime.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/closefrom.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/daemon-darwin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/daemon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/err.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/explicit_bzero.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/fdforkpty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/fgetln.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/forkpty-@PLATFORM@.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/freezero.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/getdtablecount.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/getdtablesize.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/getline.Po@am__quote@
+@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)/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)/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@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/setproctitle.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strcasestr.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strlcat.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strlcpy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strndup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strnlen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strsep.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/strtonum.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/systemd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/unvis.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/utf8proc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@compat/$(DEPDIR)/vis.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@fuzz/$(DEPDIR)/input-fuzzer.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.y.c:
+ $(AM_V_YACC)$(am__skipyacc) $(SHELL) $(YLWRAP) $< y.tab.c $@ y.tab.h `echo $@ | $(am__yacc_c2h)` y.output $*.output -- $(YACCCOMPILE)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscope: cscope.files
+ test ! -s cscope.files \
+ || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS)
+clean-cscope:
+ -rm -f cscope.files
+cscope.files: clean-cscope cscopelist
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+ -rm -f cscope.out cscope.in.out cscope.po.out cscope.files
+
+distdir: $(DISTFILES)
+ $(am__remove_distdir)
+ test -d "$(distdir)" || mkdir "$(distdir)"
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ -test -n "$(am__skip_mode_fix)" \
+ || find "$(distdir)" -type d ! -perm -755 \
+ -exec chmod u+rwx,go+rx {} \; -o \
+ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+ || chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+ tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz
+ $(am__post_remove_distdir)
+
+dist-bzip2: distdir
+ tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
+ $(am__post_remove_distdir)
+
+dist-lzip: distdir
+ tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+ $(am__post_remove_distdir)
+
+dist-xz: distdir
+ tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
+ $(am__post_remove_distdir)
+
+dist-tarZ: distdir
+ @echo WARNING: "Support for distribution archives compressed with" \
+ "legacy program 'compress' is deprecated." >&2
+ @echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+ tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+ $(am__post_remove_distdir)
+
+dist-shar: distdir
+ @echo WARNING: "Support for shar distribution archives is" \
+ "deprecated." >&2
+ @echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+ shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz
+ $(am__post_remove_distdir)
+
+dist-zip: distdir
+ -rm -f $(distdir).zip
+ zip -rq $(distdir).zip $(distdir)
+ $(am__post_remove_distdir)
+
+dist dist-all:
+ $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
+ $(am__post_remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration. Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+ case '$(DIST_ARCHIVES)' in \
+ *.tar.gz*) \
+ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\
+ *.tar.bz2*) \
+ bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+ *.tar.lz*) \
+ lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
+ *.tar.xz*) \
+ xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+ *.tar.Z*) \
+ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+ *.shar.gz*) \
+ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
+ *.zip*) \
+ unzip $(distdir).zip ;;\
+ esac
+ chmod -R a-w $(distdir)
+ chmod u+w $(distdir)
+ mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst
+ chmod a-w $(distdir)
+ test -d $(distdir)/_build || exit 0; \
+ dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+ && am__cwd=`pwd` \
+ && $(am__cd) $(distdir)/_build/sub \
+ && ../../configure \
+ $(AM_DISTCHECK_CONFIGURE_FLAGS) \
+ $(DISTCHECK_CONFIGURE_FLAGS) \
+ --srcdir=../.. --prefix="$$dc_install_base" \
+ && $(MAKE) $(AM_MAKEFLAGS) \
+ && $(MAKE) $(AM_MAKEFLAGS) dvi \
+ && $(MAKE) $(AM_MAKEFLAGS) check \
+ && $(MAKE) $(AM_MAKEFLAGS) install \
+ && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+ && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+ distuninstallcheck \
+ && chmod -R a-w "$$dc_install_base" \
+ && ({ \
+ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+ } || { rm -rf "$$dc_destdir"; exit 1; }) \
+ && rm -rf "$$dc_destdir" \
+ && $(MAKE) $(AM_MAKEFLAGS) dist \
+ && rm -rf $(DIST_ARCHIVES) \
+ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+ && cd "$$am__cwd" \
+ || exit 1
+ $(am__post_remove_distdir)
+ @(echo "$(distdir) archives ready for distribution: "; \
+ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+ @test -n '$(distuninstallcheck_dir)' || { \
+ echo 'ERROR: trying to run $@ with an empty' \
+ '$$(distuninstallcheck_dir)' >&2; \
+ exit 1; \
+ }; \
+ $(am__cd) '$(distuninstallcheck_dir)' || { \
+ echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
+ exit 1; \
+ }; \
+ test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
+ || { echo "ERROR: files left after uninstall:" ; \
+ if test -n "$(DESTDIR)"; then \
+ echo " (check DESTDIR support)"; \
+ fi ; \
+ $(distuninstallcheck_listfiles) ; \
+ exit 1; } >&2
+distcleancheck: distclean
+ @if test '$(srcdir)' = . ; then \
+ echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+ exit 1 ; \
+ fi
+ @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+ || { echo "ERROR: files left in build directory after distclean:" ; \
+ $(distcleancheck_listfiles) ; \
+ exit 1; } >&2
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(LIBOBJS)" || rm -f $(LIBOBJS)
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -rm -f compat/$(DEPDIR)/$(am__dirstamp)
+ -rm -f compat/$(am__dirstamp)
+ -rm -f fuzz/$(DEPDIR)/$(am__dirstamp)
+ -rm -f fuzz/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -rm -f cmd-parse.c
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+ -rm -rf ./$(DEPDIR) compat/$(DEPDIR) fuzz/$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+ @$(NORMAL_INSTALL)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-hook
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+ -rm -rf $(top_srcdir)/autom4te.cache
+ -rm -rf ./$(DEPDIR) compat/$(DEPDIR) fuzz/$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: check-am install-am install-exec-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--refresh check check-am clean \
+ clean-binPROGRAMS clean-checkPROGRAMS clean-cscope \
+ clean-generic cscope cscopelist-am ctags ctags-am dist \
+ dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \
+ dist-xz dist-zip distcheck distclean distclean-compile \
+ distclean-generic distclean-tags distcleancheck distdir \
+ distuninstallcheck dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-exec-hook install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Install tmux.1 in the right format.
+install-exec-hook:
+ if test x@MANFORMAT@ = xmdoc; then \
+ sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1 \
+ >$(srcdir)/tmux.1.mdoc; \
+ else \
+ sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1| \
+ $(AWK) -f $(srcdir)/mdoc2man.awk >$(srcdir)/tmux.1.man; \
+ fi
+ $(mkdir_p) $(DESTDIR)$(mandir)/man1
+ $(INSTALL_DATA) $(srcdir)/tmux.1.@MANFORMAT@ \
+ $(DESTDIR)$(mandir)/man1/tmux.1
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/README b/README
new file mode 100644
index 0000000..5732962
--- /dev/null
+++ b/README
@@ -0,0 +1,86 @@
+Welcome to tmux!
+
+tmux is a terminal multiplexer: it enables a number of terminals to be created,
+accessed, and controlled from a single screen. tmux may be detached from a
+screen and continue running in the background, then later reattached.
+
+This release runs on OpenBSD, FreeBSD, NetBSD, Linux, macOS and Solaris.
+
+* Dependencies
+
+tmux depends on libevent 2.x, available from:
+
+ https://github.com/libevent/libevent/releases/latest
+
+It also depends on ncurses, available from:
+
+ https://invisible-mirror.net/archives/ncurses/
+
+To build tmux, a C compiler (for example gcc or clang), make, pkg-config and a
+suitable yacc (yacc or bison) are needed.
+
+* Installation
+
+To build and install tmux from a release tarball, use:
+
+ $ ./configure && make
+ $ sudo make install
+
+tmux can use the utempter library to update utmp(5), if it is installed - run
+configure with --enable-utempter to enable this.
+
+To get and build the latest from version control - note that this requires
+autoconf, automake and pkg-config:
+
+ $ git clone https://github.com/tmux/tmux.git
+ $ cd tmux
+ $ sh autogen.sh
+ $ ./configure && make
+
+* Contributing
+
+Bug reports, feature suggestions and especially code contributions are most
+welcome. Please send by email to:
+
+ tmux-users@googlegroups.com
+
+Or open a GitHub issue or pull request.
+
+* Documentation
+
+For documentation on using tmux, see the tmux.1 manpage. View it from the
+source tree with:
+
+ $ nroff -mdoc tmux.1|less
+
+A small example configuration is in example_tmux.conf.
+
+Other documentation is available in the wiki:
+
+ https://github.com/tmux/tmux/wiki
+
+Also see the tmux FAQ at:
+
+ https://github.com/tmux/tmux/wiki/FAQ
+
+A bash(1) completion file is at:
+
+ https://github.com/imomaliev/tmux-bash-completion
+
+For debugging, run tmux with -v and -vv to generate server and client log files
+in the current directory.
+
+* Support
+
+The tmux mailing list for general discussion and bug reports is:
+
+ https://groups.google.com/forum/#!forum/tmux-users
+
+Subscribe by sending an email to:
+
+ tmux-users+subscribe@googlegroups.com
+
+* License
+
+This file and the CHANGES files are licensed under the ISC license. All other
+files have a license and copyright notice at their start.
diff --git a/README.ja b/README.ja
new file mode 100644
index 0000000..3c94473
--- /dev/null
+++ b/README.ja
@@ -0,0 +1,62 @@
+tmuxへようこそ!
+
+tmuxはターミナルマルチプレクサーです。複数のターミナルを一つのスクリーン内に作成し、操作することができます。
+バックグラウンドで処理を実行中に一度スクリーンから離れて後から復帰することも可能です。
+
+OpenBSD、FreeBSD、NetBSD、Linux、macOS、Solarisで実行できます。
+
+tmuxはlibevent 2.x.に依存します。 下記からダウンロードしてください。
+
+ http://libevent.org
+
+また、ncursesも必要です。こちらからどうぞ。
+
+ http://invisible-island.net/ncurses/
+
+tarballでのtmuxのビルドとインストール方法。
+
+ $ ./configure && make
+ $ sudo make install
+
+tmuxはutmp(5)をアップデートするためにutempterを使うことができます。もしインストール済みであればオプション「--enable-utempter」をつけて実行してください。
+
+リポジトリから最新バージョンを手に入れるためには下記を実行。
+
+ $ git clone https://github.com/tmux/tmux.git
+ $ cd tmux
+ $ sh autogen.sh
+ $ ./configure && make
+
+(ビルドのためにはlibevent、ncurses libraries、headersに加えて、C compiler、make、autoconf、automake、pkg-configが必要です。)
+
+詳しい情報はhttp://git-scm.comをご覧ください。修正はメール<tmux-users@googlegroups.com>宛、もしくはhttps://github.com/tmux/tmux/issuesにて受け付けています。
+
+tmuxのドキュメントについてはtmux.1マニュアルをご覧ください。こちらのコマンドで参照可能です。
+
+ $ nroff -mdoc tmux.1|less
+
+サンプル設定は本リポジトリのexample_tmux.confに
+また、bash-completionファイルは下記にあります。
+
+ https://github.com/imomaliev/tmux-bash-completion
+
+「-v」や「-vv」を指定することでデバッグモードでの起動が可能です。カレントディレクトリにサーバーやクライアントのログファイルが生成されます。
+
+議論やバグレポート用のメーリングリストにはこちらから参加可能です。
+
+ https://groups.google.com/forum/#!forum/tmux-users
+
+gitコミットについての連絡先
+
+ https://groups.google.com/forum/#!forum/tmux-git
+
+購読は<tmux-users+subscribe@googlegroups.com>までメールをお願いします。
+
+バグレポートや機能追加(特にコードへの貢献)は大歓迎です。こちらにご連絡ください。
+
+ tmux-users@googlegroups.com
+
+本ファイル、CHANGES、 FAQ、SYNCINGそしてTODOはISC licenseで保護されています。
+その他のファイルのライセンスや著作権については、ファイルの上部に明記されています。
+
+-- Nicholas Marriott <nicholas.marriott@gmail.com>
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644
index 0000000..2161831
--- /dev/null
+++ b/aclocal.m4
@@ -0,0 +1,1428 @@
+# generated automatically by aclocal 1.15.1 -*- Autoconf -*-
+
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
+m4_ifndef([AC_AUTOCONF_VERSION],
+ [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],,
+[m4_warning([this file was generated for autoconf 2.69.
+You have another version of autoconf. It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically 'autoreconf'.])])
+
+# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
+# serial 12 (pkg-config-0.29.2)
+
+dnl Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+dnl Copyright © 2012-2015 Dan Nicholson <dbn.lists@gmail.com>
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+dnl General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software
+dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+dnl 02111-1307, USA.
+dnl
+dnl As a special exception to the GNU General Public License, if you
+dnl distribute this file as part of a program that contains a
+dnl configuration script generated by Autoconf, you may include it under
+dnl the same distribution terms that you use for the rest of that
+dnl program.
+
+dnl PKG_PREREQ(MIN-VERSION)
+dnl -----------------------
+dnl Since: 0.29
+dnl
+dnl Verify that the version of the pkg-config macros are at least
+dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's
+dnl installed version of pkg-config, this checks the developer's version
+dnl of pkg.m4 when generating configure.
+dnl
+dnl To ensure that this macro is defined, also add:
+dnl m4_ifndef([PKG_PREREQ],
+dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])])
+dnl
+dnl See the "Since" comment for each macro you use to see what version
+dnl of the macros you require.
+m4_defun([PKG_PREREQ],
+[m4_define([PKG_MACROS_VERSION], [0.29.2])
+m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1,
+ [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])])
+])dnl PKG_PREREQ
+
+dnl PKG_PROG_PKG_CONFIG([MIN-VERSION])
+dnl ----------------------------------
+dnl Since: 0.16
+dnl
+dnl Search for the pkg-config tool and set the PKG_CONFIG variable to
+dnl first found in the path. Checks that the version of pkg-config found
+dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is
+dnl used since that's the first version where most current features of
+dnl pkg-config existed.
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
+m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
+AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
+AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+ AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+ _pkg_min_version=m4_default([$1], [0.9.0])
+ AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+ if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ PKG_CONFIG=""
+ fi
+fi[]dnl
+])dnl PKG_PROG_PKG_CONFIG
+
+dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl -------------------------------------------------------------------
+dnl Since: 0.18
+dnl
+dnl Check to see whether a particular set of modules exists. Similar to
+dnl PKG_CHECK_MODULES(), but does not set variables or print errors.
+dnl
+dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+dnl only at the first occurence in configure.ac, so if the first place
+dnl it's called might be skipped (such as if it is within an "if", you
+dnl have to call PKG_CHECK_EXISTS manually
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+ m4_default([$2], [:])
+m4_ifvaln([$3], [else
+ $3])dnl
+fi])
+
+dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+dnl ---------------------------------------------
+dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting
+dnl pkg_failed based on the result.
+m4_define([_PKG_CONFIG],
+[if test -n "$$1"; then
+ pkg_cv_[]$1="$$1"
+ elif test -n "$PKG_CONFIG"; then
+ PKG_CHECK_EXISTS([$3],
+ [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes ],
+ [pkg_failed=yes])
+ else
+ pkg_failed=untried
+fi[]dnl
+])dnl _PKG_CONFIG
+
+dnl _PKG_SHORT_ERRORS_SUPPORTED
+dnl ---------------------------
+dnl Internal check to see if pkg-config supports short errors.
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi[]dnl
+])dnl _PKG_SHORT_ERRORS_SUPPORTED
+
+
+dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+dnl [ACTION-IF-NOT-FOUND])
+dnl --------------------------------------------------------------
+dnl Since: 0.4.0
+dnl
+dnl Note that if there is a possibility the first call to
+dnl PKG_CHECK_MODULES might not happen, you should be sure to include an
+dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $2])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+ AC_MSG_RESULT([no])
+ _PKG_SHORT_ERRORS_SUPPORTED
+ if test $_pkg_short_errors_supported = yes; then
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1`
+ else
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+ m4_default([$4], [AC_MSG_ERROR(
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT])[]dnl
+ ])
+elif test $pkg_failed = untried; then
+ AC_MSG_RESULT([no])
+ m4_default([$4], [AC_MSG_FAILURE(
+[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.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl
+ ])
+else
+ $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+ $1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+ AC_MSG_RESULT([yes])
+ $3
+fi[]dnl
+])dnl PKG_CHECK_MODULES
+
+
+dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+dnl [ACTION-IF-NOT-FOUND])
+dnl ---------------------------------------------------------------------
+dnl Since: 0.29
+dnl
+dnl Checks for existence of MODULES and gathers its build flags with
+dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags
+dnl and VARIABLE-PREFIX_LIBS from --libs.
+dnl
+dnl Note that if there is a possibility the first call to
+dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to
+dnl include an explicit call to PKG_PROG_PKG_CONFIG in your
+dnl configure.ac.
+AC_DEFUN([PKG_CHECK_MODULES_STATIC],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+_save_PKG_CONFIG=$PKG_CONFIG
+PKG_CONFIG="$PKG_CONFIG --static"
+PKG_CHECK_MODULES($@)
+PKG_CONFIG=$_save_PKG_CONFIG[]dnl
+])dnl PKG_CHECK_MODULES_STATIC
+
+
+dnl PKG_INSTALLDIR([DIRECTORY])
+dnl -------------------------
+dnl Since: 0.27
+dnl
+dnl Substitutes the variable pkgconfigdir as the location where a module
+dnl should install pkg-config .pc files. By default the directory is
+dnl $libdir/pkgconfig, but the default can be changed by passing
+dnl DIRECTORY. The user can override through the --with-pkgconfigdir
+dnl parameter.
+AC_DEFUN([PKG_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+ [pkg-config installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([pkgconfigdir],
+ [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,
+ [with_pkgconfigdir=]pkg_default)
+AC_SUBST([pkgconfigdir], [$with_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+])dnl PKG_INSTALLDIR
+
+
+dnl PKG_NOARCH_INSTALLDIR([DIRECTORY])
+dnl --------------------------------
+dnl Since: 0.27
+dnl
+dnl Substitutes the variable noarch_pkgconfigdir as the location where a
+dnl module should install arch-independent pkg-config .pc files. By
+dnl default the directory is $datadir/pkgconfig, but the default can be
+dnl changed by passing DIRECTORY. The user can override through the
+dnl --with-noarch-pkgconfigdir parameter.
+AC_DEFUN([PKG_NOARCH_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+ [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([noarch-pkgconfigdir],
+ [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,
+ [with_noarch_pkgconfigdir=]pkg_default)
+AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+])dnl PKG_NOARCH_INSTALLDIR
+
+
+dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
+dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl -------------------------------------------
+dnl Since: 0.28
+dnl
+dnl Retrieves the value of the pkg-config variable for the given module.
+AC_DEFUN([PKG_CHECK_VAR],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
+
+_PKG_CONFIG([$1], [variable="][$3]["], [$2])
+AS_VAR_COPY([$1], [pkg_cv_][$1])
+
+AS_VAR_IF([$1], [""], [$5], [$4])dnl
+])dnl PKG_CHECK_VAR
+
+# Copyright (C) 2002-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.15'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version. Point them to the right macro.
+m4_if([$1], [1.15.1], [],
+ [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too. Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.15.1])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+ [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# AM_AUX_DIR_EXPAND -*- Autoconf -*-
+
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to
+# '$srcdir', '$srcdir/..', or '$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory. The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run. This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+# fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+# fails if $ac_aux_dir is absolute,
+# fails when called from a subdirectory in a VPATH build with
+# a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir. In an in-source build this is usually
+# harmless because $srcdir is '.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir. That would be:
+# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+# MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH. The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+])
+
+# AM_CONDITIONAL -*- Autoconf -*-
+
+# Copyright (C) 1997-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ([2.52])dnl
+ m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])],
+ [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+ $1_TRUE=
+ $1_FALSE='#'
+else
+ $1_TRUE='#'
+ $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+ AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery. Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+m4_if([$1], [CC], [depcc="$CC" am_compiler_list=],
+ [$1], [CXX], [depcc="$CXX" am_compiler_list=],
+ [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+ [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'],
+ [$1], [UPC], [depcc="$UPC" am_compiler_list=],
+ [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'],
+ [depcc="$$1" am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+ [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+ # We make a subdir and do the tests there. Otherwise we can end up
+ # making bogus files that we don't know about and never remove. For
+ # instance it was reported that on HP-UX the gcc test will end up
+ # making a dummy file named 'D' -- because '-MD' means "put the output
+ # in D".
+ rm -rf conftest.dir
+ mkdir conftest.dir
+ # Copy depcomp to subdir because otherwise we won't find it if we're
+ # using a relative directory.
+ cp "$am_depcomp" conftest.dir
+ cd conftest.dir
+ # We will build objects and dependencies in a subdirectory because
+ # it helps to detect inapplicable dependency modes. For instance
+ # both Tru64's cc and ICC support -MD to output dependencies as a
+ # side effect of compilation, but ICC will put the dependencies in
+ # the current directory while Tru64 will put them in the object
+ # directory.
+ mkdir sub
+
+ am_cv_$1_dependencies_compiler_type=none
+ if test "$am_compiler_list" = ""; then
+ am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+ fi
+ am__universal=false
+ m4_case([$1], [CC],
+ [case " $depcc " in #(
+ *\ -arch\ *\ -arch\ *) am__universal=true ;;
+ esac],
+ [CXX],
+ [case " $depcc " in #(
+ *\ -arch\ *\ -arch\ *) am__universal=true ;;
+ esac])
+
+ for depmode in $am_compiler_list; do
+ # Setup a source with many dependencies, because some compilers
+ # like to wrap large dependency lists on column 80 (with \), and
+ # we should not choose a depcomp mode which is confused by this.
+ #
+ # We need to recreate these files for each test, as the compiler may
+ # overwrite some of them when testing with obscure command lines.
+ # This happens at least with the AIX C compiler.
+ : > sub/conftest.c
+ for i in 1 2 3 4 5 6; do
+ echo '#include "conftst'$i'.h"' >> sub/conftest.c
+ # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+ # Solaris 10 /bin/sh.
+ echo '/* dummy */' > sub/conftst$i.h
+ done
+ echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+ # We check with '-c' and '-o' for the sake of the "dashmstdout"
+ # mode. It turns out that the SunPro C++ compiler does not properly
+ # handle '-M -o', and we need to detect this. Also, some Intel
+ # versions had trouble with output in subdirs.
+ am__obj=sub/conftest.${OBJEXT-o}
+ am__minus_obj="-o $am__obj"
+ case $depmode in
+ gcc)
+ # This depmode causes a compiler race in universal mode.
+ test "$am__universal" = false || continue
+ ;;
+ nosideeffect)
+ # After this tag, mechanisms are not by side-effect, so they'll
+ # only be used when explicitly requested.
+ if test "x$enable_dependency_tracking" = xyes; then
+ continue
+ else
+ break
+ fi
+ ;;
+ msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+ # This compiler won't grok '-c -o', but also, the minuso test has
+ # not run yet. These depmodes are late enough in the game, and
+ # so weak that their functioning should not be impacted.
+ am__obj=conftest.${OBJEXT-o}
+ am__minus_obj=
+ ;;
+ none) break ;;
+ esac
+ if depmode=$depmode \
+ source=sub/conftest.c object=$am__obj \
+ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+ >/dev/null 2>conftest.err &&
+ grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+ ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+ # icc doesn't choke on unknown options, it will just issue warnings
+ # or remarks (even with -Werror). So we grep stderr for any message
+ # that says an option was ignored or not supported.
+ # When given -MP, icc 7.0 and 7.1 complain thusly:
+ # icc: Command line warning: ignoring option '-M'; no argument required
+ # The diagnosis changed in icc 8.0:
+ # icc: Command line remark: option '-MP' not supported
+ if (grep 'ignoring option' conftest.err ||
+ grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+ am_cv_$1_dependencies_compiler_type=$depmode
+ break
+ fi
+ fi
+ done
+
+ cd ..
+ rm -rf conftest.dir
+else
+ am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+ test "x$enable_dependency_tracking" != xno \
+ && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES.
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE([dependency-tracking], [dnl
+AS_HELP_STRING(
+ [--enable-dependency-tracking],
+ [do not reject slow dependency extractors])
+AS_HELP_STRING(
+ [--disable-dependency-tracking],
+ [speeds up one-time build])])
+if test "x$enable_dependency_tracking" != xno; then
+ am_depcomp="$ac_aux_dir/depcomp"
+ AMDEPBACKSLASH='\'
+ am__nodep='_no'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([am__nodep])dnl
+_AM_SUBST_NOTMAKE([am__nodep])dnl
+])
+
+# Generate code to set up dependency tracking. -*- Autoconf -*-
+
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+ # Older Autoconf quotes --file arguments for eval, but not when files
+ # are listed without --file. Let's play safe and only enable the eval
+ # if we detect the quoting.
+ case $CONFIG_FILES in
+ *\'*) eval set x "$CONFIG_FILES" ;;
+ *) set x $CONFIG_FILES ;;
+ esac
+ shift
+ for mf
+ do
+ # Strip MF so we end up with the name of the file.
+ mf=`echo "$mf" | sed -e 's/:.*$//'`
+ # Check whether this is an Automake generated Makefile or not.
+ # We used to match only the files named 'Makefile.in', but
+ # some people rename them; so instead we look at the file content.
+ # Grep'ing the first line is not enough: some people post-process
+ # each Makefile.in and add a new line on top of each file to say so.
+ # Grep'ing the whole file is not good either: AIX grep has a line
+ # limit of 2048, but all sed's we know have understand at least 4000.
+ if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+ dirpart=`AS_DIRNAME("$mf")`
+ else
+ continue
+ fi
+ # Extract the definition of DEPDIR, am__include, and am__quote
+ # from the Makefile without running 'make'.
+ DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+ test -z "$DEPDIR" && continue
+ am__include=`sed -n 's/^am__include = //p' < "$mf"`
+ test -z "$am__include" && continue
+ am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+ # Find all dependency output files, they are included files with
+ # $(DEPDIR) in their names. We invoke sed twice because it is the
+ # simplest approach to changing $(DEPDIR) to its actual value in the
+ # expansion.
+ for file in `sed -n "
+ s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
+ # Make sure the directory exists.
+ test -f "$dirpart/$file" && continue
+ fdir=`AS_DIRNAME(["$file"])`
+ AS_MKDIR_P([$dirpart/$fdir])
+ # echo "creating $dirpart/$file"
+ echo '# dummy' > "$dirpart/$file"
+ done
+ done
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking
+# is enabled. FIXME. This creates each '.P' file that we will
+# need in order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+ [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+ [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"])
+])
+
+# Do all the work for Automake. -*- Autoconf -*-
+
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This macro actually does too much. Some checks are only needed if
+# your package does certain things. But this isn't really a big deal.
+
+dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O.
+m4_define([AC_PROG_CC],
+m4_defn([AC_PROG_CC])
+[_AM_PROG_CC_C_O
+])
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out. PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition. After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.65])dnl
+dnl Autoconf wants to disallow AM_ names. We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+ # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+ # is not polluted with repeated "-I."
+ AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+ # test to see if srcdir already configured
+ if test -f $srcdir/config.status; then
+ AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+ fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+ if (cygpath --version) >/dev/null 2>/dev/null; then
+ CYGPATH_W='cygpath -w'
+ else
+ CYGPATH_W=echo
+ fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[AC_DIAGNOSE([obsolete],
+ [$0: two- and three-arguments forms are deprecated.])
+m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(
+ m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]),
+ [ok:ok],,
+ [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package])
+ AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}])
+AM_MISSING_PROG([AUTOCONF], [autoconf])
+AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}])
+AM_MISSING_PROG([AUTOHEADER], [autoheader])
+AM_MISSING_PROG([MAKEINFO], [makeinfo])
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+# For better backward compatibility. To be removed once Automake 1.9.x
+# dies out for good. For more background, see:
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+AC_SUBST([mkdir_p], ['$(MKDIR_P)'])
+# We need awk for the "check" target (and possibly the TAP driver). The
+# system "awk" is bad on some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+ [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+ [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+ [_AM_DEPENDENCIES([CC])],
+ [m4_define([AC_PROG_CC],
+ m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+ [_AM_DEPENDENCIES([CXX])],
+ [m4_define([AC_PROG_CXX],
+ m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+ [_AM_DEPENDENCIES([OBJC])],
+ [m4_define([AC_PROG_OBJC],
+ m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJCXX],
+ [_AM_DEPENDENCIES([OBJCXX])],
+ [m4_define([AC_PROG_OBJCXX],
+ m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl
+])
+AC_REQUIRE([AM_SILENT_RULES])dnl
+dnl The testsuite driver may need to know about EXEEXT, so add the
+dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This
+dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+ [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes. So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+ cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present. This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message. This
+can help us improve future automake versions.
+
+END
+ if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+ echo 'Configuration will proceed anyway, since you have set the' >&2
+ echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+ echo >&2
+ else
+ cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <http://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+ AC_MSG_ERROR([Your 'rm' program is bad, sorry.])
+ fi
+fi
+dnl The trailing newline in this macro's definition is deliberate, for
+dnl backward compatibility and to allow trailing 'dnl'-style comments
+dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841.
+])
+
+dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated. The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+ case $_am_header in
+ $_am_arg | $_am_arg:* )
+ break ;;
+ * )
+ _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+ esac
+done
+echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh+set}" != xset; then
+ case $am_aux_dir in
+ *\ * | *\ *)
+ install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+ *)
+ install_sh="\${SHELL} $am_aux_dir/install-sh"
+ esac
+fi
+AC_SUBST([install_sh])])
+
+# Copyright (C) 2003-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot. For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+ am__leading_dot=.
+else
+ am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Check to see how 'make' treats includes. -*- Autoconf -*-
+
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check to see how make treats includes.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+ @echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+AC_MSG_CHECKING([for style of include used by $am_make])
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from 'make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+ am__include=include
+ am__quote=
+ _am_result=GNU
+ ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+ echo '.include "confinc"' > confmf
+ case `$am_make -s -f confmf 2> /dev/null` in #(
+ *the\ am__doit\ target*)
+ am__include=.include
+ am__quote="\""
+ _am_result=BSD
+ ;;
+ esac
+fi
+AC_SUBST([am__include])
+AC_SUBST([am__quote])
+AC_MSG_RESULT([$_am_result])
+rm -f confinc confmf
+])
+
+# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*-
+
+# Copyright (C) 1997-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it is modern enough.
+# If it is, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+ case $am_aux_dir in
+ *\ * | *\ *)
+ MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+ *)
+ MISSING="\${SHELL} $am_aux_dir/missing" ;;
+ esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+ am_missing_run="$MISSING "
+else
+ am_missing_run=
+ AC_MSG_WARN(['missing' script is too old or missing])
+fi
+])
+
+# Helper functions for option handling. -*- Autoconf -*-
+
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# --------------------
+# Set option NAME. Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), [1])])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_CC_C_O
+# ---------------
+# Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC
+# to automatically call this.
+AC_DEFUN([_AM_PROG_CC_C_O],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([compile])dnl
+AC_LANG_PUSH([C])dnl
+AC_CACHE_CHECK(
+ [whether $CC understands -c and -o together],
+ [am_cv_prog_cc_c_o],
+ [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])])
+ # Make sure it works both with $CC and with simple cc.
+ # Following AC_PROG_CC_C_O, we do the test twice because some
+ # compilers refuse to overwrite an existing .o file with -o,
+ # though they will create one.
+ am_cv_prog_cc_c_o=yes
+ for am_i in 1 2; do
+ if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \
+ && test -f conftest2.$ac_objext; then
+ : OK
+ else
+ am_cv_prog_cc_c_o=no
+ break
+ fi
+ done
+ rm -f core conftest*
+ unset am_i])
+if test "$am_cv_prog_cc_c_o" != yes; then
+ # Losing compiler, so override with the script.
+ # FIXME: It is wrong to rewrite CC.
+ # But if we don't then we get into trouble of one sort or another.
+ # A longer-term fix would be to have automake use am__CC in this case,
+ # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+ CC="$am_aux_dir/compile $CC"
+fi
+AC_LANG_POP([C])])
+
+# For backward compatibility.
+AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
+
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_RUN_LOG(COMMAND)
+# -------------------
+# Run COMMAND, save the exit status in ac_status, and log it.
+# (This has been adapted from Autoconf's _AC_RUN_LOG macro.)
+AC_DEFUN([AM_RUN_LOG],
+[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD
+ ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+ (exit $ac_status); }])
+
+# Check to make sure that the build environment is sane. -*- Autoconf -*-
+
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name. Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+ *[[\\\"\#\$\&\'\`$am_lf]]*)
+ AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+ *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*)
+ AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments. Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+ am_has_slept=no
+ for am_try in 1 2; do
+ echo "timestamp, slept: $am_has_slept" > conftest.file
+ set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+ if test "$[*]" = "X"; then
+ # -L didn't work.
+ set X `ls -t "$srcdir/configure" conftest.file`
+ fi
+ if test "$[*]" != "X $srcdir/configure conftest.file" \
+ && test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+ # If neither matched, then we have a broken ls. This can happen
+ # if, for instance, CONFIG_SHELL is bash and it inherits a
+ # broken ls alias from the environment. This has actually
+ # happened. Such a system could not be considered "sane".
+ AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken
+ alias in your environment])
+ fi
+ if test "$[2]" = conftest.file || test $am_try -eq 2; then
+ break
+ fi
+ # Just in case.
+ sleep 1
+ am_has_slept=yes
+ done
+ test "$[2]" = conftest.file
+ )
+then
+ # Ok.
+ :
+else
+ AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT([yes])
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+ ( sleep 1 ) &
+ am_sleep_pid=$!
+fi
+AC_CONFIG_COMMANDS_PRE(
+ [AC_MSG_CHECKING([that generated files are newer than configure])
+ if test -n "$am_sleep_pid"; then
+ # Hide warnings about reused PIDs.
+ wait $am_sleep_pid 2>/dev/null
+ fi
+ AC_MSG_RESULT([done])])
+rm -f conftest.file
+])
+
+# Copyright (C) 2009-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# ("yes" being less verbose, "no" or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules], [dnl
+AS_HELP_STRING(
+ [--enable-silent-rules],
+ [less verbose build output (undo: "make V=1")])
+AS_HELP_STRING(
+ [--disable-silent-rules],
+ [verbose build output (undo: "make V=0")])dnl
+])
+case $enable_silent_rules in @%:@ (((
+ yes) AM_DEFAULT_VERBOSITY=0;;
+ no) AM_DEFAULT_VERBOSITY=1;;
+ *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+dnl
+dnl A few 'make' implementations (e.g., NonStop OS and NextStep)
+dnl do not support nested variable expansions.
+dnl See automake bug#9928 and bug#10237.
+am_make=${MAKE-make}
+AC_CACHE_CHECK([whether $am_make supports nested variables],
+ [am_cv_make_support_nested_variables],
+ [if AS_ECHO([['TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+ @$(TRUE)
+.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then
+ am_cv_make_support_nested_variables=yes
+else
+ am_cv_make_support_nested_variables=no
+fi])
+if test $am_cv_make_support_nested_variables = yes; then
+ dnl Using '$V' instead of '$(V)' breaks IRIX make.
+ AM_V='$(V)'
+ AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+ AM_V=$AM_DEFAULT_VERBOSITY
+ AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AC_SUBST([AM_V])dnl
+AM_SUBST_NOTMAKE([AM_V])dnl
+AC_SUBST([AM_DEFAULT_V])dnl
+AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor 'install' (even GNU) is that you can't
+# specify the program used to strip binaries. This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in "make install-strip", and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip". However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be 'maybe'.
+if test "$cross_compiling" != no; then
+ AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball. -*- Autoconf -*-
+
+# Copyright (C) 2004-2017 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of 'v7', 'ustar', or 'pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+# tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+# $(am__untar) < result.tar
+#
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility. Yes, it's still used
+# in the wild :-( We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+
+m4_if([$1], [v7],
+ [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
+
+ [m4_case([$1],
+ [ustar],
+ [# The POSIX 1988 'ustar' format is defined with fixed-size fields.
+ # There is notably a 21 bits limit for the UID and the GID. In fact,
+ # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343
+ # and bug#13588).
+ am_max_uid=2097151 # 2^21 - 1
+ am_max_gid=$am_max_uid
+ # The $UID and $GID variables are not portable, so we need to resort
+ # to the POSIX-mandated id(1) utility. Errors in the 'id' calls
+ # below are definitely unexpected, so allow the users to see them
+ # (that is, avoid stderr redirection).
+ am_uid=`id -u || echo unknown`
+ am_gid=`id -g || echo unknown`
+ AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format])
+ if test $am_uid -le $am_max_uid; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ _am_tools=none
+ fi
+ AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format])
+ if test $am_gid -le $am_max_gid; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ _am_tools=none
+ fi],
+
+ [pax],
+ [],
+
+ [m4_fatal([Unknown tar format])])
+
+ AC_MSG_CHECKING([how to create a $1 tar archive])
+
+ # Go ahead even if we have the value already cached. We do so because we
+ # need to set the values for the 'am__tar' and 'am__untar' variables.
+ _am_tools=${am_cv_prog_tar_$1-$_am_tools}
+
+ for _am_tool in $_am_tools; do
+ case $_am_tool in
+ gnutar)
+ for _am_tar in tar gnutar gtar; do
+ AM_RUN_LOG([$_am_tar --version]) && break
+ done
+ am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
+ am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
+ am__untar="$_am_tar -xf -"
+ ;;
+ plaintar)
+ # Must skip GNU tar: if it does not support --format= it doesn't create
+ # ustar tarball either.
+ (tar --version) >/dev/null 2>&1 && continue
+ am__tar='tar chf - "$$tardir"'
+ am__tar_='tar chf - "$tardir"'
+ am__untar='tar xf -'
+ ;;
+ pax)
+ am__tar='pax -L -x $1 -w "$$tardir"'
+ am__tar_='pax -L -x $1 -w "$tardir"'
+ am__untar='pax -r'
+ ;;
+ cpio)
+ am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+ am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+ am__untar='cpio -i -H $1 -d'
+ ;;
+ none)
+ am__tar=false
+ am__tar_=false
+ am__untar=false
+ ;;
+ esac
+
+ # If the value was cached, stop now. We just wanted to have am__tar
+ # and am__untar set.
+ test -n "${am_cv_prog_tar_$1}" && break
+
+ # tar/untar a dummy directory, and stop if the command works.
+ rm -rf conftest.dir
+ mkdir conftest.dir
+ echo GrepMe > conftest.dir/file
+ AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+ rm -rf conftest.dir
+ if test -s conftest.tar; then
+ AM_RUN_LOG([$am__untar <conftest.tar])
+ AM_RUN_LOG([cat conftest.dir/file])
+ grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+ fi
+ done
+ rm -rf conftest.dir
+
+ AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+ AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
diff --git a/alerts.c b/alerts.c
new file mode 100644
index 0000000..d3c5df0
--- /dev/null
+++ b/alerts.c
@@ -0,0 +1,325 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 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 "tmux.h"
+
+static int alerts_fired;
+
+static void alerts_timer(int, short, void *);
+static int alerts_enabled(struct window *, int);
+static void alerts_callback(int, short, void *);
+static void alerts_reset(struct window *);
+
+static int alerts_action_applies(struct winlink *, const char *);
+static int alerts_check_all(struct window *);
+static int alerts_check_bell(struct window *);
+static int alerts_check_activity(struct window *);
+static int alerts_check_silence(struct window *);
+static void alerts_set_message(struct winlink *, const char *,
+ const char *);
+
+static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
+
+static void
+alerts_timer(__unused int fd, __unused short events, void *arg)
+{
+ struct window *w = arg;
+
+ log_debug("@%u alerts timer expired", w->id);
+ alerts_queue(w, WINDOW_SILENCE);
+}
+
+static void
+alerts_callback(__unused int fd, __unused short events, __unused void *arg)
+{
+ struct window *w, *w1;
+ int alerts;
+
+ TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) {
+ alerts = alerts_check_all(w);
+ log_debug("@%u alerts check, alerts %#x", w->id, alerts);
+
+ w->alerts_queued = 0;
+ TAILQ_REMOVE(&alerts_list, w, alerts_entry);
+
+ w->flags &= ~WINDOW_ALERTFLAGS;
+ window_remove_ref(w, __func__);
+ }
+ alerts_fired = 0;
+}
+
+static int
+alerts_action_applies(struct winlink *wl, const char *name)
+{
+ int action;
+
+ /*
+ * {bell,activity,silence}-action determines when to alert: none means
+ * nothing happens, current means only do something for the current
+ * window and other means only for windows other than the current.
+ */
+
+ action = options_get_number(wl->session->options, name);
+ if (action == ALERT_ANY)
+ return (1);
+ if (action == ALERT_CURRENT)
+ return (wl == wl->session->curw);
+ if (action == ALERT_OTHER)
+ return (wl != wl->session->curw);
+ return (0);
+}
+
+static int
+alerts_check_all(struct window *w)
+{
+ int alerts;
+
+ alerts = alerts_check_bell(w);
+ alerts |= alerts_check_activity(w);
+ alerts |= alerts_check_silence(w);
+ return (alerts);
+}
+
+void
+alerts_check_session(struct session *s)
+{
+ struct winlink *wl;
+
+ RB_FOREACH(wl, winlinks, &s->windows)
+ alerts_check_all(wl->window);
+}
+
+static int
+alerts_enabled(struct window *w, int flags)
+{
+ if (flags & WINDOW_BELL) {
+ if (options_get_number(w->options, "monitor-bell"))
+ return (1);
+ }
+ if (flags & WINDOW_ACTIVITY) {
+ if (options_get_number(w->options, "monitor-activity"))
+ return (1);
+ }
+ if (flags & WINDOW_SILENCE) {
+ if (options_get_number(w->options, "monitor-silence") != 0)
+ return (1);
+ }
+ return (0);
+}
+
+void
+alerts_reset_all(void)
+{
+ struct window *w;
+
+ RB_FOREACH(w, windows, &windows)
+ alerts_reset(w);
+}
+
+static void
+alerts_reset(struct window *w)
+{
+ struct timeval tv;
+
+ if (!event_initialized(&w->alerts_timer))
+ evtimer_set(&w->alerts_timer, alerts_timer, w);
+
+ w->flags &= ~WINDOW_SILENCE;
+ event_del(&w->alerts_timer);
+
+ timerclear(&tv);
+ tv.tv_sec = options_get_number(w->options, "monitor-silence");
+
+ log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec);
+ if (tv.tv_sec != 0)
+ event_add(&w->alerts_timer, &tv);
+}
+
+void
+alerts_queue(struct window *w, int flags)
+{
+ alerts_reset(w);
+
+ if ((w->flags & flags) != flags) {
+ w->flags |= flags;
+ log_debug("@%u alerts flags added %#x", w->id, flags);
+ }
+
+ if (alerts_enabled(w, flags)) {
+ if (!w->alerts_queued) {
+ w->alerts_queued = 1;
+ TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry);
+ window_add_ref(w, __func__);
+ }
+
+ if (!alerts_fired) {
+ log_debug("alerts check queued (by @%u)", w->id);
+ event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
+ alerts_fired = 1;
+ }
+ }
+}
+
+static int
+alerts_check_bell(struct window *w)
+{
+ struct winlink *wl;
+ struct session *s;
+
+ if (~w->flags & WINDOW_BELL)
+ return (0);
+ if (!options_get_number(w->options, "monitor-bell"))
+ return (0);
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry)
+ wl->session->flags &= ~SESSION_ALERTED;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ /*
+ * Bells are allowed even if there is an existing bell (so do
+ * not check WINLINK_BELL).
+ */
+ s = wl->session;
+ if (s->curw != wl || s->attached == 0) {
+ wl->flags |= WINLINK_BELL;
+ server_status_session(s);
+ }
+ if (!alerts_action_applies(wl, "bell-action"))
+ continue;
+ notify_winlink("alert-bell", wl);
+
+ if (s->flags & SESSION_ALERTED)
+ continue;
+ s->flags |= SESSION_ALERTED;
+
+ alerts_set_message(wl, "Bell", "visual-bell");
+ }
+
+ return (WINDOW_BELL);
+}
+
+static int
+alerts_check_activity(struct window *w)
+{
+ struct winlink *wl;
+ struct session *s;
+
+ if (~w->flags & WINDOW_ACTIVITY)
+ return (0);
+ if (!options_get_number(w->options, "monitor-activity"))
+ return (0);
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry)
+ wl->session->flags &= ~SESSION_ALERTED;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->flags & WINLINK_ACTIVITY)
+ continue;
+ s = wl->session;
+ if (s->curw != wl || s->attached == 0) {
+ wl->flags |= WINLINK_ACTIVITY;
+ server_status_session(s);
+ }
+ if (!alerts_action_applies(wl, "activity-action"))
+ continue;
+ notify_winlink("alert-activity", wl);
+
+ if (s->flags & SESSION_ALERTED)
+ continue;
+ s->flags |= SESSION_ALERTED;
+
+ alerts_set_message(wl, "Activity", "visual-activity");
+ }
+
+ return (WINDOW_ACTIVITY);
+}
+
+static int
+alerts_check_silence(struct window *w)
+{
+ struct winlink *wl;
+ struct session *s;
+
+ if (~w->flags & WINDOW_SILENCE)
+ return (0);
+ if (options_get_number(w->options, "monitor-silence") == 0)
+ return (0);
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry)
+ wl->session->flags &= ~SESSION_ALERTED;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->flags & WINLINK_SILENCE)
+ continue;
+ s = wl->session;
+ if (s->curw != wl || s->attached == 0) {
+ wl->flags |= WINLINK_SILENCE;
+ server_status_session(s);
+ }
+ if (!alerts_action_applies(wl, "silence-action"))
+ continue;
+ notify_winlink("alert-silence", wl);
+
+ if (s->flags & SESSION_ALERTED)
+ continue;
+ s->flags |= SESSION_ALERTED;
+
+ alerts_set_message(wl, "Silence", "visual-silence");
+ }
+
+ return (WINDOW_SILENCE);
+}
+
+static void
+alerts_set_message(struct winlink *wl, const char *type, const char *option)
+{
+ struct client *c;
+ int visual;
+
+ /*
+ * We have found an alert (bell, activity or silence), so we need to
+ * pass it on to the user. For each client attached to this session,
+ * decide whether a bell, message or both is needed.
+ *
+ * If visual-{bell,activity,silence} is on, then a message is
+ * substituted for a bell; if it is off, a bell is sent as normal; both
+ * mean both a bell and message is sent.
+ */
+
+ visual = options_get_number(wl->session->options, option);
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != wl->session || c->flags & CLIENT_CONTROL)
+ continue;
+
+ if (visual == VISUAL_OFF || visual == VISUAL_BOTH)
+ tty_putcode(&c->tty, TTYC_BEL);
+ if (visual == VISUAL_OFF)
+ continue;
+ if (c->session->curw == wl) {
+ status_message_set(c, -1, 1, 0, "%s in current window",
+ type);
+ } else {
+ status_message_set(c, -1, 1, 0, "%s in window %d", type,
+ wl->idx);
+ }
+ }
+}
diff --git a/arguments.c b/arguments.c
new file mode 100644
index 0000000..d0dc2d4
--- /dev/null
+++ b/arguments.c
@@ -0,0 +1,906 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2010 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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Manipulate command arguments.
+ */
+
+/* List of argument values. */
+TAILQ_HEAD(args_values, args_value);
+
+/* Single arguments flag. */
+struct args_entry {
+ u_char flag;
+ struct args_values values;
+ u_int count;
+ RB_ENTRY(args_entry) entry;
+};
+
+/* Parsed argument flags and values. */
+struct args {
+ struct args_tree tree;
+ u_int count;
+ struct args_value *values;
+};
+
+/* Prepared command state. */
+struct args_command_state {
+ struct cmd_list *cmdlist;
+ char *cmd;
+ struct cmd_parse_input pi;
+};
+
+static struct args_entry *args_find(struct args *, u_char);
+
+static int args_cmp(struct args_entry *, struct args_entry *);
+RB_GENERATE_STATIC(args_tree, args_entry, entry, args_cmp);
+
+/* Arguments tree comparison function. */
+static int
+args_cmp(struct args_entry *a1, struct args_entry *a2)
+{
+ return (a1->flag - a2->flag);
+}
+
+/* Find a flag in the arguments tree. */
+static struct args_entry *
+args_find(struct args *args, u_char flag)
+{
+ struct args_entry entry;
+
+ entry.flag = flag;
+ return (RB_FIND(args_tree, &args->tree, &entry));
+}
+
+/* Copy value. */
+static void
+args_copy_value(struct args_value *to, struct args_value *from)
+{
+ to->type = from->type;
+ switch (from->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_COMMANDS:
+ to->cmdlist = from->cmdlist;
+ to->cmdlist->references++;
+ break;
+ case ARGS_STRING:
+ to->string = xstrdup(from->string);
+ break;
+ }
+}
+
+/* Get value as string. */
+static const char *
+args_value_as_string(struct args_value *value)
+{
+ switch (value->type) {
+ case ARGS_NONE:
+ return ("");
+ case ARGS_COMMANDS:
+ if (value->cached == NULL)
+ value->cached = cmd_list_print(value->cmdlist, 0);
+ return (value->cached);
+ case ARGS_STRING:
+ return (value->string);
+ }
+ fatalx("unexpected argument type");
+}
+
+/* Create an empty arguments set. */
+struct args *
+args_create(void)
+{
+ struct args *args;
+
+ args = xcalloc(1, sizeof *args);
+ RB_INIT(&args->tree);
+ return (args);
+}
+
+/* Parse arguments into a new argument set. */
+struct args *
+args_parse(const struct args_parse *parse, struct args_value *values,
+ u_int count, char **cause)
+{
+ struct args *args;
+ u_int i;
+ enum args_parse_type type;
+ struct args_value *value, *new;
+ u_char flag;
+ const char *found, *string, *s;
+ int optional_argument;
+
+ 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;
+ }
+ }
+ log_debug("%s: flags end at %u of %u", __func__, i, count);
+ if (i != count) {
+ for (/* nothing */; i < count; i++) {
+ value = &values[i];
+
+ s = args_value_as_string(value);
+ log_debug("%s: %u = %s (type %d)", __func__, i, s,
+ value->type);
+
+ if (parse->cb != NULL) {
+ type = parse->cb(args, args->count, cause);
+ if (type == ARGS_PARSE_INVALID) {
+ args_free(args);
+ return (NULL);
+ }
+ } else
+ type = ARGS_PARSE_STRING;
+
+ args->values = xrecallocarray(args->values,
+ args->count, args->count + 1, sizeof *args->values);
+ new = &args->values[args->count++];
+
+ switch (type) {
+ case ARGS_PARSE_INVALID:
+ fatalx("unexpected argument type");
+ case ARGS_PARSE_STRING:
+ if (value->type != ARGS_STRING) {
+ xasprintf(cause,
+ "argument %u must be \"string\"",
+ args->count);
+ args_free(args);
+ return (NULL);
+ }
+ args_copy_value(new, value);
+ break;
+ case ARGS_PARSE_COMMANDS_OR_STRING:
+ args_copy_value(new, value);
+ break;
+ case ARGS_PARSE_COMMANDS:
+ if (value->type != ARGS_COMMANDS) {
+ xasprintf(cause,
+ "argument %u must be { commands }",
+ args->count);
+ args_free(args);
+ return (NULL);
+ }
+ args_copy_value(new, value);
+ break;
+ }
+ }
+ }
+
+ if (parse->lower != -1 && args->count < (u_int)parse->lower) {
+ xasprintf(cause,
+ "too few arguments (need at least %u)",
+ parse->lower);
+ args_free(args);
+ return (NULL);
+ }
+ if (parse->upper != -1 && args->count > (u_int)parse->upper) {
+ xasprintf(cause,
+ "too many arguments (need at most %u)",
+ parse->upper);
+ args_free(args);
+ return (NULL);
+ }
+ return (args);
+}
+
+/* Copy and expand a value. */
+static void
+args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
+ char **argv)
+{
+ char *s, *expanded;
+ int i;
+
+ to->type = from->type;
+ switch (from->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_STRING:
+ expanded = xstrdup(from->string);
+ for (i = 0; i < argc; i++) {
+ s = cmd_template_replace(expanded, argv[i], i + 1);
+ free(expanded);
+ expanded = s;
+ }
+ to->string = expanded;
+ break;
+ case ARGS_COMMANDS:
+ to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
+ break;
+ }
+}
+
+/* Copy an arguments set. */
+struct args *
+args_copy(struct args *args, int argc, char **argv)
+{
+ struct args *new_args;
+ struct args_entry *entry;
+ struct args_value *value, *new_value;
+ u_int i;
+
+ cmd_log_argv(argc, argv, "%s", __func__);
+
+ new_args = args_create();
+ 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);
+ 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);
+ }
+ }
+ if (args->count == 0)
+ return (new_args);
+ new_args->count = args->count;
+ new_args->values = xcalloc(args->count, sizeof *new_args->values);
+ for (i = 0; i < args->count; i++) {
+ new_value = &new_args->values[i];
+ args_copy_copy_value(new_value, &args->values[i], argc, argv);
+ }
+ return (new_args);
+}
+
+/* Free a value. */
+void
+args_free_value(struct args_value *value)
+{
+ switch (value->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_STRING:
+ free(value->string);
+ break;
+ case ARGS_COMMANDS:
+ cmd_list_free(value->cmdlist);
+ break;
+ }
+ free(value->cached);
+}
+
+/* Free values. */
+void
+args_free_values(struct args_value *values, u_int count)
+{
+ u_int i;
+
+ for (i = 0; i < count; i++)
+ args_free_value(&values[i]);
+}
+
+/* Free an arguments set. */
+void
+args_free(struct args *args)
+{
+ struct args_entry *entry;
+ struct args_entry *entry1;
+ struct args_value *value;
+ struct args_value *value1;
+
+ args_free_values(args->values, args->count);
+ free(args->values);
+
+ RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) {
+ RB_REMOVE(args_tree, &args->tree, entry);
+ TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) {
+ TAILQ_REMOVE(&entry->values, value, entry);
+ args_free_value(value);
+ free(value);
+ }
+ free(entry);
+ }
+
+ free(args);
+}
+
+/* Convert arguments to vector. */
+void
+args_to_vector(struct args *args, int *argc, char ***argv)
+{
+ char *s;
+ u_int i;
+
+ *argc = 0;
+ *argv = NULL;
+
+ for (i = 0; i < args->count; i++) {
+ switch (args->values[i].type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_STRING:
+ cmd_append_argv(argc, argv, args->values[i].string);
+ break;
+ case ARGS_COMMANDS:
+ s = cmd_list_print(args->values[i].cmdlist, 0);
+ cmd_append_argv(argc, argv, s);
+ free(s);
+ break;
+ }
+ }
+}
+
+/* Convert arguments from vector. */
+struct args_value *
+args_from_vector(int argc, char **argv)
+{
+ struct args_value *values;
+ int i;
+
+ values = xcalloc(argc, sizeof *values);
+ for (i = 0; i < argc; i++) {
+ values[i].type = ARGS_STRING;
+ values[i].string = xstrdup(argv[i]);
+ }
+ return (values);
+}
+
+/* Add to string. */
+static void printflike(3, 4)
+args_print_add(char **buf, size_t *len, const char *fmt, ...)
+{
+ va_list ap;
+ char *s;
+ size_t slen;
+
+ va_start(ap, fmt);
+ slen = xvasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ *len += slen;
+ *buf = xrealloc(*buf, *len);
+
+ strlcat(*buf, s, *len);
+ free(s);
+}
+
+/* Add value to string. */
+static void
+args_print_add_value(char **buf, size_t *len, struct args_value *value)
+{
+ char *expanded = NULL;
+
+ if (**buf != '\0')
+ args_print_add(buf, len, " ");
+
+ switch (value->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_COMMANDS:
+ expanded = cmd_list_print(value->cmdlist, 0);
+ args_print_add(buf, len, "{ %s }", expanded);
+ break;
+ case ARGS_STRING:
+ expanded = args_escape(value->string);
+ args_print_add(buf, len, "%s", expanded);
+ break;
+ }
+ free(expanded);
+}
+
+/* Print a set of arguments. */
+char *
+args_print(struct args *args)
+{
+ size_t len;
+ char *buf;
+ u_int i, j;
+ struct args_entry *entry;
+ struct args_value *value;
+
+ len = 1;
+ buf = xcalloc(1, len);
+
+ /* Process the flags first. */
+ RB_FOREACH(entry, args_tree, &args->tree) {
+ if (!TAILQ_EMPTY(&entry->values))
+ continue;
+
+ if (*buf == '\0')
+ args_print_add(&buf, &len, "-");
+ for (j = 0; j < entry->count; j++)
+ args_print_add(&buf, &len, "%c", entry->flag);
+ }
+
+ /* Then the flags with arguments. */
+ RB_FOREACH(entry, args_tree, &args->tree) {
+ TAILQ_FOREACH(value, &entry->values, entry) {
+ if (*buf != '\0')
+ args_print_add(&buf, &len, " -%c", entry->flag);
+ else
+ args_print_add(&buf, &len, "-%c", entry->flag);
+ args_print_add_value(&buf, &len, value);
+ }
+ }
+
+ /* And finally the argument vector. */
+ for (i = 0; i < args->count; i++)
+ args_print_add_value(&buf, &len, &args->values[i]);
+
+ return (buf);
+}
+
+/* Escape an argument. */
+char *
+args_escape(const char *s)
+{
+ static const char dquoted[] = " #';${}%";
+ static const char squoted[] = " \"";
+ char *escaped, *result;
+ int flags, quotes = 0;
+
+ if (*s == '\0') {
+ xasprintf(&result, "''");
+ return (result);
+ }
+ if (s[strcspn(s, dquoted)] != '\0')
+ quotes = '"';
+ else if (s[strcspn(s, squoted)] != '\0')
+ quotes = '\'';
+
+ if (s[0] != ' ' &&
+ s[1] == '\0' &&
+ (quotes != 0 || s[0] == '~')) {
+ xasprintf(&escaped, "\\%c", s[0]);
+ return (escaped);
+ }
+
+ flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
+ if (quotes == '"')
+ flags |= VIS_DQ;
+ utf8_stravis(&escaped, s, flags);
+
+ if (quotes == '\'')
+ xasprintf(&result, "'%s'", escaped);
+ else if (quotes == '"') {
+ if (*escaped == '~')
+ xasprintf(&result, "\"\\%s\"", escaped);
+ else
+ xasprintf(&result, "\"%s\"", escaped);
+ } else {
+ if (*escaped == '~')
+ xasprintf(&result, "\\%s", escaped);
+ else
+ result = xstrdup(escaped);
+ }
+ free(escaped);
+ return (result);
+}
+
+/* Return if an argument is present. */
+int
+args_has(struct args *args, u_char flag)
+{
+ struct args_entry *entry;
+
+ entry = args_find(args, flag);
+ if (entry == NULL)
+ return (0);
+ return (entry->count);
+}
+
+/* Set argument value in the arguments tree. */
+void
+args_set(struct args *args, u_char flag, struct args_value *value)
+{
+ struct args_entry *entry;
+
+ entry = args_find(args, flag);
+ if (entry == NULL) {
+ entry = xcalloc(1, sizeof *entry);
+ entry->flag = flag;
+ entry->count = 1;
+ TAILQ_INIT(&entry->values);
+ RB_INSERT(args_tree, &args->tree, entry);
+ } else
+ entry->count++;
+ if (value != NULL && value->type != ARGS_NONE)
+ TAILQ_INSERT_TAIL(&entry->values, value, entry);
+}
+
+/* Get argument value. Will be NULL if it isn't present. */
+const char *
+args_get(struct args *args, u_char flag)
+{
+ struct args_entry *entry;
+
+ if ((entry = args_find(args, flag)) == NULL)
+ return (NULL);
+ if (TAILQ_EMPTY(&entry->values))
+ return (NULL);
+ return (TAILQ_LAST(&entry->values, args_values)->string);
+}
+
+/* Get first argument. */
+u_char
+args_first(struct args *args, struct args_entry **entry)
+{
+ *entry = RB_MIN(args_tree, &args->tree);
+ if (*entry == NULL)
+ return (0);
+ return ((*entry)->flag);
+}
+
+/* Get next argument. */
+u_char
+args_next(struct args_entry **entry)
+{
+ *entry = RB_NEXT(args_tree, &args->tree, *entry);
+ if (*entry == NULL)
+ return (0);
+ return ((*entry)->flag);
+}
+
+/* Get argument count. */
+u_int
+args_count(struct args *args)
+{
+ return (args->count);
+}
+
+/* Get argument values. */
+struct args_value *
+args_values(struct args *args)
+{
+ return (args->values);
+}
+
+/* Get argument value. */
+struct args_value *
+args_value(struct args *args, u_int idx)
+{
+ if (idx >= args->count)
+ return (NULL);
+ return (&args->values[idx]);
+}
+
+/* Return argument as string. */
+const char *
+args_string(struct args *args, u_int idx)
+{
+ if (idx >= args->count)
+ return (NULL);
+ return (args_value_as_string(&args->values[idx]));
+}
+
+/* Make a command now. */
+struct cmd_list *
+args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
+ int expand)
+{
+ struct args_command_state *state;
+ char *error;
+ struct cmd_list *cmdlist;
+
+ state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
+ cmdlist = args_make_commands(state, 0, NULL, &error);
+ if (cmdlist == NULL) {
+ cmdq_error(item, "%s", error);
+ free(error);
+ }
+ else
+ cmdlist->references++;
+ args_make_commands_free(state);
+ return (cmdlist);
+}
+
+/* Save bits to make a command later. */
+struct args_command_state *
+args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
+ const char *default_command, int wait, int expand)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *tc = cmdq_get_target_client(item);
+ struct args_value *value;
+ struct args_command_state *state;
+ const char *cmd;
+
+ state = xcalloc(1, sizeof *state);
+
+ if (idx < args->count) {
+ value = &args->values[idx];
+ if (value->type == ARGS_COMMANDS) {
+ state->cmdlist = value->cmdlist;
+ state->cmdlist->references++;
+ return (state);
+ }
+ cmd = value->string;
+ } else {
+ if (default_command == NULL)
+ fatalx("argument out of range");
+ cmd = default_command;
+ }
+
+
+ if (expand)
+ state->cmd = format_single_from_target(item, cmd);
+ else
+ state->cmd = xstrdup(cmd);
+ log_debug("%s: %s", __func__, state->cmd);
+
+ if (wait)
+ state->pi.item = item;
+ cmd_get_source(self, &state->pi.file, &state->pi.line);
+ state->pi.c = tc;
+ if (state->pi.c != NULL)
+ state->pi.c->references++;
+ cmd_find_copy_state(&state->pi.fs, target);
+
+ return (state);
+}
+
+/* Return argument as command. */
+struct cmd_list *
+args_make_commands(struct args_command_state *state, int argc, char **argv,
+ char **error)
+{
+ struct cmd_parse_result *pr;
+ char *cmd, *new_cmd;
+ int i;
+
+ if (state->cmdlist != NULL) {
+ if (argc == 0)
+ return (state->cmdlist);
+ return (cmd_list_copy(state->cmdlist, argc, argv));
+ }
+
+ cmd = xstrdup(state->cmd);
+ 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);
+ free(cmd);
+ cmd = new_cmd;
+ }
+ log_debug("%s: %s", __func__, cmd);
+
+ pr = cmd_parse_from_string(cmd, &state->pi);
+ free(cmd);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ *error = pr->error;
+ return (NULL);
+ case CMD_PARSE_SUCCESS:
+ return (pr->cmdlist);
+ }
+ fatalx("invalid parse return state");
+}
+
+/* Free commands state. */
+void
+args_make_commands_free(struct args_command_state *state)
+{
+ if (state->cmdlist != NULL)
+ cmd_list_free(state->cmdlist);
+ if (state->pi.c != NULL)
+ server_client_unref(state->pi.c);
+ free(state->cmd);
+ free(state);
+}
+
+/* Get prepared command. */
+char *
+args_make_commands_get_command(struct args_command_state *state)
+{
+ struct cmd *first;
+ int n;
+ char *s;
+
+ if (state->cmdlist != NULL) {
+ first = cmd_list_first(state->cmdlist);
+ if (first == NULL)
+ return (xstrdup(""));
+ return (xstrdup(cmd_get_entry(first)->name));
+ }
+ n = strcspn(state->cmd, " ,");
+ xasprintf(&s, "%.*s", n, state->cmd);
+ return (s);
+}
+
+/* Get first value in argument. */
+struct args_value *
+args_first_value(struct args *args, u_char flag)
+{
+ struct args_entry *entry;
+
+ if ((entry = args_find(args, flag)) == NULL)
+ return (NULL);
+ return (TAILQ_FIRST(&entry->values));
+}
+
+/* Get next value in argument. */
+struct args_value *
+args_next_value(struct args_value *value)
+{
+ return (TAILQ_NEXT(value, entry));
+}
+
+/* Convert an argument value to a number. */
+long long
+args_strtonum(struct args *args, u_char flag, long long minval,
+ long long maxval, char **cause)
+{
+ const char *errstr;
+ 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);
+ }
+
+ ll = strtonum(value->string, minval, maxval, &errstr);
+ 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,
+ long long maxval, long long curval, char **cause)
+{
+ const char *value;
+ struct args_entry *entry;
+
+ if ((entry = args_find(args, flag)) == NULL) {
+ *cause = xstrdup("missing");
+ return (0);
+ }
+ value = TAILQ_LAST(&entry->values, args_values)->string;
+ return (args_string_percentage(value, minval, maxval, curval, cause));
+}
+
+/* Convert a string to a number which may be a percentage. */
+long long
+args_string_percentage(const char *value, long long minval, long long maxval,
+ long long curval, char **cause)
+{
+ const char *errstr;
+ long long ll;
+ size_t valuelen = strlen(value);
+ char *copy;
+
+ if (value[valuelen - 1] == '%') {
+ copy = xstrdup(value);
+ copy[valuelen - 1] = '\0';
+
+ ll = strtonum(copy, 0, 100, &errstr);
+ 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 {
+ ll = strtonum(value, minval, maxval, &errstr);
+ if (errstr != NULL) {
+ *cause = xstrdup(errstr);
+ return (0);
+ }
+ }
+
+ *cause = NULL;
+ return (ll);
+}
diff --git a/attributes.c b/attributes.c
new file mode 100644
index 0000000..b839f06
--- /dev/null
+++ b/attributes.c
@@ -0,0 +1,108 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>
+ *
+ * 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 <string.h>
+
+#include "tmux.h"
+
+const char *
+attributes_tostring(int attr)
+{
+ static char buf[512];
+ size_t len;
+
+ if (attr == 0)
+ return ("none");
+
+ len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ (attr & GRID_ATTR_CHARSET) ? "acs," : "",
+ (attr & GRID_ATTR_BRIGHT) ? "bright," : "",
+ (attr & GRID_ATTR_DIM) ? "dim," : "",
+ (attr & GRID_ATTR_UNDERSCORE) ? "underscore," : "",
+ (attr & GRID_ATTR_BLINK)? "blink," : "",
+ (attr & GRID_ATTR_REVERSE) ? "reverse," : "",
+ (attr & GRID_ATTR_HIDDEN) ? "hidden," : "",
+ (attr & GRID_ATTR_ITALICS) ? "italics," : "",
+ (attr & GRID_ATTR_STRIKETHROUGH) ? "strikethrough," : "",
+ (attr & GRID_ATTR_UNDERSCORE_2) ? "double-underscore," : "",
+ (attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "",
+ (attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "",
+ (attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "",
+ (attr & GRID_ATTR_OVERLINE) ? "overline," : "");
+ if (len > 0)
+ buf[len - 1] = '\0';
+
+ return (buf);
+}
+
+int
+attributes_fromstring(const char *str)
+{
+ const char delimiters[] = " ,|";
+ int attr;
+ size_t end;
+ u_int i;
+ struct {
+ const char *name;
+ int attr;
+ } table[] = {
+ { "acs", GRID_ATTR_CHARSET },
+ { "bright", GRID_ATTR_BRIGHT },
+ { "bold", GRID_ATTR_BRIGHT },
+ { "dim", GRID_ATTR_DIM },
+ { "underscore", GRID_ATTR_UNDERSCORE },
+ { "blink", GRID_ATTR_BLINK },
+ { "reverse", GRID_ATTR_REVERSE },
+ { "hidden", GRID_ATTR_HIDDEN },
+ { "italics", GRID_ATTR_ITALICS },
+ { "strikethrough", GRID_ATTR_STRIKETHROUGH },
+ { "double-underscore", GRID_ATTR_UNDERSCORE_2 },
+ { "curly-underscore", GRID_ATTR_UNDERSCORE_3 },
+ { "dotted-underscore", GRID_ATTR_UNDERSCORE_4 },
+ { "dashed-underscore", GRID_ATTR_UNDERSCORE_5 },
+ { "overline", GRID_ATTR_OVERLINE }
+ };
+
+ if (*str == '\0' || strcspn(str, delimiters) == 0)
+ return (-1);
+ if (strchr(delimiters, str[strlen(str) - 1]) != NULL)
+ return (-1);
+
+ if (strcasecmp(str, "default") == 0 || strcasecmp(str, "none") == 0)
+ return (0);
+
+ attr = 0;
+ do {
+ end = strcspn(str, delimiters);
+ for (i = 0; i < nitems(table); i++) {
+ if (end != strlen(table[i].name))
+ continue;
+ if (strncasecmp(str, table[i].name, end) == 0) {
+ attr |= table[i].attr;
+ break;
+ }
+ }
+ if (i == nitems(table))
+ return (-1);
+ str += end + strspn(str + end, delimiters);
+ } while (*str != '\0');
+
+ return (attr);
+}
diff --git a/cfg.c b/cfg.c
new file mode 100644
index 0000000..e92faa7
--- /dev/null
+++ b/cfg.c
@@ -0,0 +1,260 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+struct client *cfg_client;
+int cfg_finished;
+static char **cfg_causes;
+static u_int cfg_ncauses;
+static struct cmdq_item *cfg_item;
+
+int cfg_quiet = 1;
+char **cfg_files;
+u_int cfg_nfiles;
+
+static enum cmd_retval
+cfg_client_done(__unused struct cmdq_item *item, __unused void *data)
+{
+ if (!cfg_finished)
+ return (CMD_RETURN_WAIT);
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cfg_done(__unused struct cmdq_item *item, __unused void *data)
+{
+ if (cfg_finished)
+ return (CMD_RETURN_NORMAL);
+ cfg_finished = 1;
+
+ if (!RB_EMPTY(&sessions))
+ cfg_show_causes(RB_MIN(sessions, &sessions));
+
+ if (cfg_item != NULL)
+ cmdq_continue(cfg_item);
+
+ status_prompt_load_history();
+
+ return (CMD_RETURN_NORMAL);
+}
+
+void
+start_cfg(void)
+{
+ struct client *c;
+ u_int i;
+
+ /*
+ * Configuration files are loaded without a client, so commands are run
+ * in the global queue with item->client NULL.
+ *
+ * However, we must block the initial client (but just the initial
+ * client) so that its command runs after the configuration is loaded.
+ * Because start_cfg() is called so early, we can be sure the client's
+ * command queue is currently empty and our callback will be at the
+ * front - we need to get in before MSG_COMMAND.
+ */
+ cfg_client = c = TAILQ_FIRST(&clients);
+ if (c != NULL) {
+ cfg_item = cmdq_get_callback(cfg_client_done, NULL);
+ 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);
+ }
+
+ 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)
+{
+ FILE *f;
+ struct cmd_parse_input pi;
+ struct cmd_parse_result *pr;
+ struct cmdq_item *new_item0;
+ struct cmdq_state *state;
+
+ if (new_item != NULL)
+ *new_item = NULL;
+
+ log_debug("loading %s", path);
+ if ((f = fopen(path, "rb")) == NULL) {
+ if (errno == ENOENT && (flags & CMD_PARSE_QUIET))
+ return (0);
+ cfg_add_cause("%s: %s", path, strerror(errno));
+ return (-1);
+ }
+
+ memset(&pi, 0, sizeof pi);
+ pi.flags = flags;
+ pi.file = path;
+ pi.line = 1;
+ pi.item = item;
+ pi.c = c;
+
+ pr = cmd_parse_from_file(f, &pi);
+ fclose(f);
+ if (pr->status == CMD_PARSE_ERROR) {
+ cfg_add_cause("%s", pr->error);
+ free(pr->error);
+ return (-1);
+ }
+ if (flags & CMD_PARSE_PARSEONLY) {
+ cmd_list_free(pr->cmdlist);
+ return (0);
+ }
+
+ if (item != NULL)
+ state = cmdq_copy_state(cmdq_get_state(item));
+ else
+ state = cmdq_new_state(NULL, NULL, 0);
+ cmdq_add_format(state, "current_file", "%s", pi.file);
+
+ new_item0 = cmdq_get_command(pr->cmdlist, state);
+ if (item != NULL)
+ new_item0 = cmdq_insert_after(item, new_item0);
+ else
+ new_item0 = cmdq_append(NULL, new_item0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_free_state(state);
+
+ if (new_item != NULL)
+ *new_item = new_item0;
+ return (0);
+}
+
+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 cmd_parse_input pi;
+ struct cmd_parse_result *pr;
+ struct cmdq_item *new_item0;
+ struct cmdq_state *state;
+
+ if (new_item != NULL)
+ *new_item = NULL;
+
+ log_debug("loading %s", path);
+
+ memset(&pi, 0, sizeof pi);
+ pi.flags = flags;
+ pi.file = path;
+ pi.line = 1;
+ pi.item = item;
+ pi.c = c;
+
+ pr = cmd_parse_from_buffer(buf, len, &pi);
+ if (pr->status == CMD_PARSE_ERROR) {
+ cfg_add_cause("%s", pr->error);
+ free(pr->error);
+ return (-1);
+ }
+ if (flags & CMD_PARSE_PARSEONLY) {
+ cmd_list_free(pr->cmdlist);
+ return (0);
+ }
+
+ if (item != NULL)
+ state = cmdq_copy_state(cmdq_get_state(item));
+ else
+ state = cmdq_new_state(NULL, NULL, 0);
+ cmdq_add_format(state, "current_file", "%s", pi.file);
+
+ new_item0 = cmdq_get_command(pr->cmdlist, state);
+ if (item != NULL)
+ new_item0 = cmdq_insert_after(item, new_item0);
+ else
+ new_item0 = cmdq_append(NULL, new_item0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_free_state(state);
+
+ if (new_item != NULL)
+ *new_item = new_item0;
+ return (0);
+}
+
+void
+cfg_add_cause(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ va_end(ap);
+
+ cfg_ncauses++;
+ cfg_causes = xreallocarray(cfg_causes, cfg_ncauses, sizeof *cfg_causes);
+ cfg_causes[cfg_ncauses - 1] = msg;
+}
+
+void
+cfg_print_causes(struct cmdq_item *item)
+{
+ u_int i;
+
+ for (i = 0; i < cfg_ncauses; i++) {
+ cmdq_print(item, "%s", cfg_causes[i]);
+ free(cfg_causes[i]);
+ }
+
+ free(cfg_causes);
+ cfg_causes = NULL;
+ cfg_ncauses = 0;
+}
+
+void
+cfg_show_causes(struct session *s)
+{
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+ u_int i;
+
+ if (s == NULL || cfg_ncauses == 0)
+ return;
+ wp = s->curw->window->active;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL || wme->mode != &window_view_mode)
+ window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL);
+ for (i = 0; i < cfg_ncauses; i++) {
+ window_copy_add(wp, 0, "%s", cfg_causes[i]);
+ free(cfg_causes[i]);
+ }
+
+ free(cfg_causes);
+ cfg_causes = NULL;
+ cfg_ncauses = 0;
+}
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..df6cee9
--- /dev/null
+++ b/client.c
@@ -0,0 +1,801 @@
+/* $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 <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/file.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static struct tmuxproc *client_proc;
+static struct tmuxpeer *client_peer;
+static uint64_t client_flags;
+static int client_suspended;
+static enum {
+ CLIENT_EXIT_NONE,
+ CLIENT_EXIT_DETACHED,
+ CLIENT_EXIT_DETACHED_HUP,
+ CLIENT_EXIT_LOST_TTY,
+ CLIENT_EXIT_TERMINATED,
+ CLIENT_EXIT_LOST_SERVER,
+ CLIENT_EXIT_EXITED,
+ CLIENT_EXIT_SERVER_EXITED,
+ CLIENT_EXIT_MESSAGE_PROVIDED
+} client_exitreason = CLIENT_EXIT_NONE;
+static int client_exitflag;
+static int client_exitval;
+static enum msgtype client_exittype;
+static const char *client_exitsession;
+static char *client_exitmessage;
+static const char *client_execshell;
+static const char *client_execcmd;
+static int client_attached;
+static struct client_files client_files = RB_INITIALIZER(&client_files);
+
+static __dead void client_exec(const char *,const char *);
+static int client_get_lock(char *);
+static int client_connect(struct event_base *, const char *,
+ uint64_t);
+static void client_send_identify(const char *, const char *,
+ char **, u_int, const char *, int);
+static void client_signal(int);
+static void client_dispatch(struct imsg *, void *);
+static void client_dispatch_attached(struct imsg *);
+static void client_dispatch_wait(struct imsg *);
+static const char *client_exit_message(void);
+
+/*
+ * Get server create lock. If already held then server start is happening in
+ * another client, so block until the lock is released and return -2 to
+ * retry. Return -1 on failure to continue and start the server anyway.
+ */
+static int
+client_get_lock(char *lockfile)
+{
+ int lockfd;
+
+ log_debug("lock file is %s", lockfile);
+
+ if ((lockfd = open(lockfile, O_WRONLY|O_CREAT, 0600)) == -1) {
+ log_debug("open failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ if (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
+ log_debug("flock failed: %s", strerror(errno));
+ if (errno != EAGAIN)
+ return (lockfd);
+ while (flock(lockfd, LOCK_EX) == -1 && errno == EINTR)
+ /* nothing */;
+ close(lockfd);
+ return (-2);
+ }
+ log_debug("flock succeeded");
+
+ return (lockfd);
+}
+
+/* Connect client to server. */
+static int
+client_connect(struct event_base *base, const char *path, uint64_t flags)
+{
+ struct sockaddr_un sa;
+ size_t size;
+ int fd, lockfd = -1, locked = 0;
+ char *lockfile = NULL;
+
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ size = strlcpy(sa.sun_path, path, sizeof sa.sun_path);
+ if (size >= sizeof sa.sun_path) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ log_debug("socket is %s", path);
+
+retry:
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ return (-1);
+
+ log_debug("trying connect");
+ if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
+ log_debug("connect failed: %s", strerror(errno));
+ if (errno != ECONNREFUSED && errno != ENOENT)
+ goto failed;
+ if (flags & CLIENT_NOSTARTSERVER)
+ goto failed;
+ if (~flags & CLIENT_STARTSERVER)
+ goto failed;
+ close(fd);
+
+ if (!locked) {
+ xasprintf(&lockfile, "%s.lock", path);
+ if ((lockfd = client_get_lock(lockfile)) < 0) {
+ log_debug("didn't get lock (%d)", lockfd);
+
+ free(lockfile);
+ lockfile = NULL;
+
+ if (lockfd == -2)
+ goto retry;
+ }
+ log_debug("got lock (%d)", lockfd);
+
+ /*
+ * Always retry at least once, even if we got the lock,
+ * because another client could have taken the lock,
+ * started the server and released the lock between our
+ * connect() and flock().
+ */
+ locked = 1;
+ goto retry;
+ }
+
+ if (lockfd >= 0 && unlink(path) != 0 && errno != ENOENT) {
+ free(lockfile);
+ close(lockfd);
+ return (-1);
+ }
+ fd = server_start(client_proc, flags, base, lockfd, lockfile);
+ }
+
+ if (locked && lockfd >= 0) {
+ free(lockfile);
+ close(lockfd);
+ }
+ setblocking(fd, 0);
+ return (fd);
+
+failed:
+ if (locked) {
+ free(lockfile);
+ close(lockfd);
+ }
+ close(fd);
+ return (-1);
+}
+
+/* Get exit string from reason number. */
+const char *
+client_exit_message(void)
+{
+ static char msg[256];
+
+ switch (client_exitreason) {
+ case CLIENT_EXIT_NONE:
+ break;
+ case CLIENT_EXIT_DETACHED:
+ if (client_exitsession != NULL) {
+ xsnprintf(msg, sizeof msg, "detached "
+ "(from session %s)", client_exitsession);
+ return (msg);
+ }
+ return ("detached");
+ case CLIENT_EXIT_DETACHED_HUP:
+ if (client_exitsession != NULL) {
+ xsnprintf(msg, sizeof msg, "detached and SIGHUP "
+ "(from session %s)", client_exitsession);
+ return (msg);
+ }
+ return ("detached and SIGHUP");
+ case CLIENT_EXIT_LOST_TTY:
+ return ("lost tty");
+ case CLIENT_EXIT_TERMINATED:
+ return ("terminated");
+ case CLIENT_EXIT_LOST_SERVER:
+ return ("server exited unexpectedly");
+ case CLIENT_EXIT_EXITED:
+ return ("exited");
+ case CLIENT_EXIT_SERVER_EXITED:
+ return ("server exited");
+ case CLIENT_EXIT_MESSAGE_PROVIDED:
+ return (client_exitmessage);
+ }
+ return ("unknown reason");
+}
+
+/* Exit if all streams flushed. */
+static void
+client_exit(void)
+{
+ if (!file_write_left(&client_files))
+ proc_exit(client_proc);
+}
+
+/* Client main loop. */
+int
+client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
+ int feat)
+{
+ struct cmd_parse_result *pr;
+ struct msg_command *data;
+ int fd, i;
+ const char *ttynam, *termname, *cwd;
+ pid_t ppid;
+ enum msgtype msg;
+ struct termios tio, saved_tio;
+ size_t size, linesize = 0;
+ ssize_t linelen;
+ char *line = NULL, **caps = NULL, *cause;
+ 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;
+ flags |= CLIENT_STARTSERVER;
+ } else if (argc == 0) {
+ msg = MSG_COMMAND;
+ flags |= CLIENT_STARTSERVER;
+ } else {
+ msg = MSG_COMMAND;
+
+ /*
+ * It's annoying parsing the command string twice (in client
+ * and later in server) but it is necessary to get the start
+ * server flag.
+ */
+ values = args_from_vector(argc, argv);
+ pr = cmd_parse_from_arguments(values, argc, NULL);
+ if (pr->status == CMD_PARSE_SUCCESS) {
+ if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
+ flags |= CLIENT_STARTSERVER;
+ cmd_list_free(pr->cmdlist);
+ } else
+ free(pr->error);
+ args_free_values(values, argc);
+ free(values);
+ }
+
+ /* Create client process structure (starts logging). */
+ client_proc = proc_start("client");
+ proc_set_signals(client_proc, client_signal);
+
+ /* Save the flags. */
+ client_flags = flags;
+ log_debug("flags are %#llx", (unsigned long long)client_flags);
+
+ /* Initialize the client socket and start the server. */
+ fd = client_connect(base, socket_path, client_flags);
+ if (fd == -1) {
+ if (errno == ECONNREFUSED) {
+ fprintf(stderr, "no server running on %s\n",
+ socket_path);
+ } else {
+ fprintf(stderr, "error connecting to %s (%s)\n",
+ socket_path, strerror(errno));
+ }
+ return (1);
+ }
+ client_peer = proc_add_peer(client_proc, fd, client_dispatch, NULL);
+
+ /* Save these before pledge(). */
+ if ((cwd = find_cwd()) == NULL && (cwd = find_home()) == NULL)
+ cwd = "/";
+ if ((ttynam = ttyname(STDIN_FILENO)) == NULL)
+ ttynam = "";
+ if ((termname = getenv("TERM")) == NULL)
+ termname = "";
+
+ /*
+ * Drop privileges for client. "proc exec" is needed for -c and for
+ * locking (which uses system(3)).
+ *
+ * "tty" is needed to restore termios(4) and also for some reason -CC
+ * does not work properly without it (input is not recognised).
+ *
+ * "sendfd" is dropped later in client_dispatch_wait().
+ */
+ if (pledge(
+ "stdio rpath wpath cpath unix sendfd proc exec tty",
+ NULL) != 0)
+ fatal("pledge failed");
+
+ /* Load terminfo entry if any. */
+ if (isatty(STDIN_FILENO) &&
+ *termname != '\0' &&
+ tty_term_read_list(termname, STDIN_FILENO, &caps, &ncaps,
+ &cause) != 0) {
+ fprintf(stderr, "%s\n", cause);
+ free(cause);
+ return (1);
+ }
+
+ /* Free stuff that is not used in the client. */
+ if (ptm_fd != -1)
+ close(ptm_fd);
+ options_free(global_options);
+ options_free(global_s_options);
+ options_free(global_w_options);
+ environ_free(global_environ);
+
+ /* Set up control mode. */
+ if (client_flags & CLIENT_CONTROLCONTROL) {
+ if (tcgetattr(STDIN_FILENO, &saved_tio) != 0) {
+ fprintf(stderr, "tcgetattr failed: %s\n",
+ strerror(errno));
+ return (1);
+ }
+ cfmakeraw(&tio);
+ tio.c_iflag = ICRNL|IXANY;
+ tio.c_oflag = OPOST|ONLCR;
+#ifdef NOKERNINFO
+ tio.c_lflag = NOKERNINFO;
+#endif
+ tio.c_cflag = CREAD|CS8|HUPCL;
+ tio.c_cc[VMIN] = 1;
+ tio.c_cc[VTIME] = 0;
+ cfsetispeed(&tio, cfgetispeed(&saved_tio));
+ cfsetospeed(&tio, cfgetospeed(&saved_tio));
+ tcsetattr(STDIN_FILENO, TCSANOW, &tio);
+ }
+
+ /* Send identify messages. */
+ client_send_identify(ttynam, termname, caps, ncaps, cwd, feat);
+ tty_term_free_list(caps, ncaps);
+ proc_flush_peer(client_peer);
+
+ /* Send first command. */
+ if (msg == MSG_COMMAND) {
+ /* How big is the command? */
+ size = 0;
+ for (i = 0; i < argc; i++)
+ size += strlen(argv[i]) + 1;
+ if (size > MAX_IMSGSIZE - (sizeof *data)) {
+ fprintf(stderr, "command too long\n");
+ return (1);
+ }
+ data = xmalloc((sizeof *data) + size);
+
+ /* Prepare command for server. */
+ data->argc = argc;
+ if (cmd_pack_argv(argc, argv, (char *)(data + 1), size) != 0) {
+ fprintf(stderr, "command too long\n");
+ free(data);
+ return (1);
+ }
+ size += sizeof *data;
+
+ /* Send the command. */
+ if (proc_send(client_peer, msg, -1, data, size) != 0) {
+ fprintf(stderr, "failed to send command\n");
+ free(data);
+ return (1);
+ }
+ free(data);
+ } else if (msg == MSG_SHELL)
+ proc_send(client_peer, msg, -1, NULL, 0);
+
+ /* Start main loop. */
+ proc_loop(client_proc, NULL);
+
+ /* Run command if user requested exec, instead of exiting. */
+ if (client_exittype == MSG_EXEC) {
+ if (client_flags & CLIENT_CONTROLCONTROL)
+ tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
+ client_exec(client_execshell, client_execcmd);
+ }
+
+ /* Restore streams to blocking. */
+ setblocking(STDIN_FILENO, 1);
+ setblocking(STDOUT_FILENO, 1);
+ setblocking(STDERR_FILENO, 1);
+
+ /* Print the exit message, if any, and exit. */
+ if (client_attached) {
+ if (client_exitreason != CLIENT_EXIT_NONE)
+ printf("[%s]\n", client_exit_message());
+
+ ppid = getppid();
+ if (client_exittype == MSG_DETACHKILL && ppid > 1)
+ kill(ppid, SIGHUP);
+ } else if (client_flags & CLIENT_CONTROL) {
+ if (client_exitreason != CLIENT_EXIT_NONE)
+ printf("%%exit %s\n", client_exit_message());
+ else
+ printf("%%exit\n");
+ fflush(stdout);
+ if (client_flags & CLIENT_CONTROL_WAITEXIT) {
+ setvbuf(stdin, NULL, _IOLBF, 0);
+ for (;;) {
+ linelen = getline(&line, &linesize, stdin);
+ if (linelen <= 1)
+ break;
+ }
+ free(line);
+ }
+ if (client_flags & CLIENT_CONTROLCONTROL) {
+ printf("\033\\");
+ fflush(stdout);
+ tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
+ }
+ } else if (client_exitreason != CLIENT_EXIT_NONE)
+ fprintf(stderr, "%s\n", client_exit_message());
+ return (client_exitval);
+}
+
+/* Send identify messages to server. */
+static void
+client_send_identify(const char *ttynam, const char *termname, char **caps,
+ u_int ncaps, const char *cwd, int feat)
+{
+ char **ss;
+ size_t sslen;
+ int fd, flags = client_flags;
+ pid_t pid;
+ u_int i;
+
+ proc_send(client_peer, MSG_IDENTIFY_FLAGS, -1, &flags, sizeof flags);
+ proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &client_flags,
+ sizeof client_flags);
+
+ proc_send(client_peer, MSG_IDENTIFY_TERM, -1, termname,
+ strlen(termname) + 1);
+ proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat);
+
+ proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam,
+ strlen(ttynam) + 1);
+ proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1);
+
+ for (i = 0; i < ncaps; i++) {
+ proc_send(client_peer, MSG_IDENTIFY_TERMINFO, -1,
+ caps[i], strlen(caps[i]) + 1);
+ }
+
+ if ((fd = dup(STDIN_FILENO)) == -1)
+ fatal("dup failed");
+ proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0);
+ if ((fd = dup(STDOUT_FILENO)) == -1)
+ fatal("dup failed");
+ proc_send(client_peer, MSG_IDENTIFY_STDOUT, fd, NULL, 0);
+
+ pid = getpid();
+ proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid);
+
+ for (ss = environ; *ss != NULL; ss++) {
+ sslen = strlen(*ss) + 1;
+ if (sslen > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
+ continue;
+ proc_send(client_peer, MSG_IDENTIFY_ENVIRON, -1, *ss, sslen);
+ }
+
+ proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0);
+}
+
+/* Run command in shell; used for -c. */
+static __dead void
+client_exec(const char *shell, const char *shellcmd)
+{
+ const char *name, *ptr;
+ char *argv0;
+
+ log_debug("shell %s, command %s", shell, shellcmd);
+
+ ptr = strrchr(shell, '/');
+ if (ptr != NULL && *(ptr + 1) != '\0')
+ name = ptr + 1;
+ else
+ name = shell;
+ if (client_flags & CLIENT_LOGIN)
+ xasprintf(&argv0, "-%s", name);
+ else
+ xasprintf(&argv0, "%s", name);
+ setenv("SHELL", shell, 1);
+
+ proc_clear_signals(client_proc, 1);
+
+ setblocking(STDIN_FILENO, 1);
+ setblocking(STDOUT_FILENO, 1);
+ setblocking(STDERR_FILENO, 1);
+ closefrom(STDERR_FILENO + 1);
+
+ execl(shell, argv0, "-c", shellcmd, (char *) NULL);
+ fatal("execl failed");
+}
+
+/* Callback to handle signals in the client. */
+static void
+client_signal(int sig)
+{
+ struct sigaction sigact;
+ int status;
+
+ log_debug("%s: %s", __func__, strsignal(sig));
+ if (sig == SIGCHLD)
+ waitpid(WAIT_ANY, &status, WNOHANG);
+ else if (!client_attached) {
+ if (sig == SIGTERM || sig == SIGHUP)
+ proc_exit(client_proc);
+ } else {
+ switch (sig) {
+ case SIGHUP:
+ client_exitreason = CLIENT_EXIT_LOST_TTY;
+ client_exitval = 1;
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ break;
+ case SIGTERM:
+ if (!client_suspended)
+ client_exitreason = CLIENT_EXIT_TERMINATED;
+ client_exitval = 1;
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ break;
+ case SIGWINCH:
+ proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
+ break;
+ case SIGCONT:
+ memset(&sigact, 0, sizeof sigact);
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = SA_RESTART;
+ sigact.sa_handler = SIG_IGN;
+ if (sigaction(SIGTSTP, &sigact, NULL) != 0)
+ fatal("sigaction failed");
+ proc_send(client_peer, MSG_WAKEUP, -1, NULL, 0);
+ client_suspended = 0;
+ break;
+ }
+ }
+}
+
+/* Callback for file write error or close. */
+static void
+client_file_check_cb(__unused struct client *c, __unused const char *path,
+ __unused int error, __unused int closed, __unused struct evbuffer *buffer,
+ __unused void *data)
+{
+ if (client_exitflag)
+ client_exit();
+}
+
+/* Callback for client read events. */
+static void
+client_dispatch(struct imsg *imsg, __unused void *arg)
+{
+ if (imsg == NULL) {
+ if (!client_exitflag) {
+ client_exitreason = CLIENT_EXIT_LOST_SERVER;
+ client_exitval = 1;
+ }
+ proc_exit(client_proc);
+ return;
+ }
+
+ if (client_attached)
+ client_dispatch_attached(imsg);
+ else
+ client_dispatch_wait(imsg);
+}
+
+/* Process an exit message. */
+static void
+client_dispatch_exit_message(char *data, size_t datalen)
+{
+ int retval;
+
+ if (datalen < sizeof retval && datalen != 0)
+ fatalx("bad MSG_EXIT size");
+
+ if (datalen >= sizeof retval) {
+ memcpy(&retval, data, sizeof retval);
+ client_exitval = retval;
+ }
+
+ if (datalen > sizeof retval) {
+ datalen -= sizeof retval;
+ data += sizeof retval;
+
+ client_exitmessage = xmalloc(datalen);
+ memcpy(client_exitmessage, data, datalen);
+ client_exitmessage[datalen - 1] = '\0';
+
+ client_exitreason = CLIENT_EXIT_MESSAGE_PROVIDED;
+ }
+}
+
+/* Dispatch imsgs when in wait state (before MSG_READY). */
+static void
+client_dispatch_wait(struct imsg *imsg)
+{
+ char *data;
+ ssize_t datalen;
+ static int pledge_applied;
+
+ /*
+ * "sendfd" is no longer required once all of the identify messages
+ * have been sent. We know the server won't send us anything until that
+ * point (because we don't ask it to), so we can drop "sendfd" once we
+ * get the first message from the server.
+ */
+ if (!pledge_applied) {
+ if (pledge(
+ "stdio rpath wpath cpath unix proc exec tty",
+ NULL) != 0)
+ fatal("pledge failed");
+ pledge_applied = 1;
+ }
+
+ data = imsg->data;
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+
+ switch (imsg->hdr.type) {
+ case MSG_EXIT:
+ case MSG_SHUTDOWN:
+ client_dispatch_exit_message(data, datalen);
+ client_exitflag = 1;
+ client_exit();
+ break;
+ case MSG_READY:
+ if (datalen != 0)
+ fatalx("bad MSG_READY size");
+
+ client_attached = 1;
+ proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
+ break;
+ case MSG_VERSION:
+ if (datalen != 0)
+ fatalx("bad MSG_VERSION size");
+
+ fprintf(stderr, "protocol version mismatch "
+ "(client %d, server %u)\n", PROTOCOL_VERSION,
+ imsg->hdr.peerid & 0xff);
+ client_exitval = 1;
+ proc_exit(client_proc);
+ break;
+ case MSG_FLAGS:
+ if (datalen != sizeof client_flags)
+ fatalx("bad MSG_FLAGS string");
+
+ memcpy(&client_flags, data, sizeof client_flags);
+ log_debug("new flags are %#llx",
+ (unsigned long long)client_flags);
+ break;
+ case MSG_SHELL:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_SHELL string");
+
+ client_exec(data, shell_command);
+ /* NOTREACHED */
+ case MSG_DETACH:
+ case MSG_DETACHKILL:
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ break;
+ case MSG_EXITED:
+ proc_exit(client_proc);
+ break;
+ case MSG_READ_OPEN:
+ file_read_open(&client_files, client_peer, imsg, 1,
+ !(client_flags & CLIENT_CONTROL), client_file_check_cb,
+ NULL);
+ break;
+ case MSG_WRITE_OPEN:
+ file_write_open(&client_files, client_peer, imsg, 1,
+ !(client_flags & CLIENT_CONTROL), client_file_check_cb,
+ NULL);
+ break;
+ case MSG_WRITE:
+ file_write_data(&client_files, imsg);
+ break;
+ case MSG_WRITE_CLOSE:
+ file_write_close(&client_files, imsg);
+ break;
+ case MSG_OLDSTDERR:
+ case MSG_OLDSTDIN:
+ case MSG_OLDSTDOUT:
+ fprintf(stderr, "server version is too old for client\n");
+ proc_exit(client_proc);
+ break;
+ }
+}
+
+/* Dispatch imsgs in attached state (after MSG_READY). */
+static void
+client_dispatch_attached(struct imsg *imsg)
+{
+ struct sigaction sigact;
+ char *data;
+ ssize_t datalen;
+
+ data = imsg->data;
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+
+ switch (imsg->hdr.type) {
+ case MSG_FLAGS:
+ if (datalen != sizeof client_flags)
+ fatalx("bad MSG_FLAGS string");
+
+ memcpy(&client_flags, data, sizeof client_flags);
+ log_debug("new flags are %#llx",
+ (unsigned long long)client_flags);
+ break;
+ case MSG_DETACH:
+ case MSG_DETACHKILL:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_DETACH string");
+
+ client_exitsession = xstrdup(data);
+ client_exittype = imsg->hdr.type;
+ if (imsg->hdr.type == MSG_DETACHKILL)
+ client_exitreason = CLIENT_EXIT_DETACHED_HUP;
+ else
+ client_exitreason = CLIENT_EXIT_DETACHED;
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ break;
+ case MSG_EXEC:
+ if (datalen == 0 || data[datalen - 1] != '\0' ||
+ strlen(data) + 1 == (size_t)datalen)
+ fatalx("bad MSG_EXEC string");
+ client_execcmd = xstrdup(data);
+ client_execshell = xstrdup(data + strlen(data) + 1);
+
+ client_exittype = imsg->hdr.type;
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ break;
+ case MSG_EXIT:
+ client_dispatch_exit_message(data, datalen);
+ if (client_exitreason == CLIENT_EXIT_NONE)
+ client_exitreason = CLIENT_EXIT_EXITED;
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ break;
+ case MSG_EXITED:
+ if (datalen != 0)
+ fatalx("bad MSG_EXITED size");
+
+ proc_exit(client_proc);
+ break;
+ case MSG_SHUTDOWN:
+ if (datalen != 0)
+ fatalx("bad MSG_SHUTDOWN size");
+
+ proc_send(client_peer, MSG_EXITING, -1, NULL, 0);
+ client_exitreason = CLIENT_EXIT_SERVER_EXITED;
+ client_exitval = 1;
+ break;
+ case MSG_SUSPEND:
+ if (datalen != 0)
+ fatalx("bad MSG_SUSPEND size");
+
+ memset(&sigact, 0, sizeof sigact);
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = SA_RESTART;
+ sigact.sa_handler = SIG_DFL;
+ if (sigaction(SIGTSTP, &sigact, NULL) != 0)
+ fatal("sigaction failed");
+ client_suspended = 1;
+ kill(getpid(), SIGTSTP);
+ break;
+ case MSG_LOCK:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_LOCK string");
+
+ system(data);
+ proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
+ break;
+ }
+}
diff --git a/cmd-attach-session.c b/cmd-attach-session.c
new file mode 100644
index 0000000..b92a7f2
--- /dev/null
+++ b/cmd-attach-session.c
@@ -0,0 +1,172 @@
+/* $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 <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Attach existing session to the current terminal.
+ */
+
+static enum cmd_retval cmd_attach_session_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_attach_session_entry = {
+ .name = "attach-session",
+ .alias = "attach",
+
+ .args = { "c:dEf:rt:x", 0, 0, NULL },
+ .usage = "[-dErx] [-c working-directory] [-f flags] "
+ CMD_TARGET_SESSION_USAGE,
+
+ /* -t is special */
+
+ .flags = CMD_STARTSERVER|CMD_READONLY,
+ .exec = cmd_attach_session_exec
+};
+
+enum cmd_retval
+cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag,
+ int xflag, int rflag, const char *cflag, int Eflag, const char *fflag)
+{
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state target;
+ enum cmd_find_type type;
+ int flags;
+ struct client *c = cmdq_get_client(item), *c_loop;
+ struct session *s;
+ struct winlink *wl;
+ struct window_pane *wp;
+ char *cwd, *cause;
+ enum msgtype msgtype;
+
+ if (RB_EMPTY(&sessions)) {
+ cmdq_error(item, "no sessions");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (c == NULL)
+ return (CMD_RETURN_NORMAL);
+
+ if (server_client_check_nested(c)) {
+ cmdq_error(item, "sessions should be nested with care, "
+ "unset $TMUX to force");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (tflag != NULL && tflag[strcspn(tflag, ":.")] != '\0') {
+ type = CMD_FIND_PANE;
+ flags = 0;
+ } else {
+ type = CMD_FIND_SESSION;
+ flags = CMD_FIND_PREFER_UNATTACHED;
+ }
+ if (cmd_find_target(&target, item, tflag, type, flags) != 0)
+ return (CMD_RETURN_ERROR);
+ s = target.s;
+ wl = target.wl;
+ wp = target.wp;
+
+ if (wl != NULL) {
+ if (wp != NULL)
+ window_set_active_pane(wp->window, wp, 1);
+ session_set_current(s, wl);
+ if (wp != NULL)
+ cmd_find_from_winlink_pane(current, wl, wp, 0);
+ else
+ cmd_find_from_winlink(current, wl, 0);
+ }
+
+ if (cflag != NULL) {
+ cwd = format_single(item, cflag, c, s, wl, wp);
+ free((void *)s->cwd);
+ s->cwd = cwd;
+ }
+ if (fflag)
+ server_client_set_flags(c, fflag);
+ if (rflag)
+ c->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE);
+
+ c->last_session = c->session;
+ if (c->session != NULL) {
+ if (dflag || xflag) {
+ if (xflag)
+ msgtype = MSG_DETACHKILL;
+ else
+ msgtype = MSG_DETACH;
+ TAILQ_FOREACH(c_loop, &clients, entry) {
+ if (c_loop->session != s || c == c_loop)
+ continue;
+ server_client_detach(c_loop, msgtype);
+ }
+ }
+ if (!Eflag)
+ environ_update(s->options, c->environ, s->environ);
+
+ server_client_set_session(c, s);
+ if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT)
+ server_client_set_key_table(c, NULL);
+ } else {
+ if (server_client_open(c, &cause) != 0) {
+ cmdq_error(item, "open terminal failed: %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (dflag || xflag) {
+ if (xflag)
+ msgtype = MSG_DETACHKILL;
+ else
+ msgtype = MSG_DETACH;
+ TAILQ_FOREACH(c_loop, &clients, entry) {
+ if (c_loop->session != s || c == c_loop)
+ continue;
+ server_client_detach(c_loop, msgtype);
+ }
+ }
+ if (!Eflag)
+ environ_update(s->options, c->environ, s->environ);
+
+ server_client_set_session(c, s);
+ server_client_set_key_table(c, NULL);
+
+ if (~c->flags & CLIENT_CONTROL)
+ proc_send(c->peer, MSG_READY, -1, NULL, 0);
+ notify_client("client-attached", c);
+ c->flags |= CLIENT_ATTACHED;
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cmd_attach_session_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+
+ return (cmd_attach_session(item, args_get(args, 't'),
+ args_has(args, 'd'), args_has(args, 'x'), args_has(args, 'r'),
+ args_get(args, 'c'), args_has(args, 'E'), args_get(args, 'f')));
+}
diff --git a/cmd-bind-key.c b/cmd-bind-key.c
new file mode 100644
index 0000000..dab03b0
--- /dev/null
+++ b/cmd-bind-key.c
@@ -0,0 +1,107 @@
+/* $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"
+
+/*
+ * Bind a key to a command.
+ */
+
+static enum args_parse_type cmd_bind_key_args_parse(struct args *, u_int,
+ char **);
+static enum cmd_retval cmd_bind_key_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_bind_key_entry = {
+ .name = "bind-key",
+ .alias = "bind",
+
+ .args = { "nrN:T:", 1, -1, cmd_bind_key_args_parse },
+ .usage = "[-nr] [-T key-table] [-N note] key "
+ "[command [arguments]]",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_bind_key_exec
+};
+
+static enum args_parse_type
+cmd_bind_key_args_parse(__unused struct args *args, __unused u_int idx,
+ __unused char **cause)
+{
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+}
+
+static enum cmd_retval
+cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ key_code key;
+ const char *tablename, *note = args_get(args, 'N');
+ struct cmd_parse_result *pr;
+ int repeat;
+ struct args_value *value;
+ u_int count = args_count(args);
+
+ key = key_string_lookup_string(args_string(args, 0));
+ if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
+ cmdq_error(item, "unknown key: %s", args_string(args, 0));
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'T'))
+ tablename = args_get(args, 'T');
+ else if (args_has(args, 'n'))
+ tablename = "root";
+ else
+ tablename = "prefix";
+ repeat = args_has(args, 'r');
+
+ if (count == 1) {
+ key_bindings_add(tablename, key, note, repeat, NULL);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ value = args_value(args, 1);
+ if (count == 2 && value->type == ARGS_COMMANDS) {
+ key_bindings_add(tablename, key, note, repeat, value->cmdlist);
+ value->cmdlist->references++;
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (count == 2)
+ pr = cmd_parse_from_string(args_string(args, 1), NULL);
+ else {
+ pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1,
+ NULL);
+ }
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ cmdq_error(item, "%s", pr->error);
+ free(pr->error);
+ return (CMD_RETURN_ERROR);
+ case CMD_PARSE_SUCCESS:
+ break;
+ }
+ key_bindings_add(tablename, key, note, repeat, pr->cmdlist);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-break-pane.c b/cmd-break-pane.c
new file mode 100644
index 0000000..4f38d4b
--- /dev/null
+++ b/cmd-break-pane.c
@@ -0,0 +1,142 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * Break pane off into a window.
+ */
+
+#define BREAK_PANE_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}"
+
+static enum cmd_retval cmd_break_pane_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_break_pane_entry = {
+ .name = "break-pane",
+ .alias = "breakp",
+
+ .args = { "abdPF:n:s:t:", 0, 0, NULL },
+ .usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] "
+ "[-t dst-window]",
+
+ .source = { 's', CMD_FIND_PANE, 0 },
+ .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX },
+
+ .flags = 0,
+ .exec = cmd_break_pane_exec
+};
+
+static enum cmd_retval
+cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct client *tc = cmdq_get_target_client(item);
+ struct winlink *wl = source->wl;
+ struct session *src_s = source->s;
+ struct session *dst_s = target->s;
+ struct window_pane *wp = source->wp;
+ struct window *w = wl->window;
+ char *name, *cause, *cp;
+ int idx = target->idx, before;
+ const char *template;
+
+ before = args_has(args, 'b');
+ if (args_has(args, 'a') || before) {
+ if (target->wl != NULL)
+ idx = winlink_shuffle_up(dst_s, target->wl, before);
+ else
+ idx = winlink_shuffle_up(dst_s, dst_s->curw, before);
+ if (idx == -1)
+ return (CMD_RETURN_ERROR);
+ }
+ server_unzoom_window(w);
+
+ if (window_count_panes(w) == 1) {
+ if (server_link_window(src_s, wl, dst_s, idx, 0,
+ !args_has(args, 'd'), &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ if (args_has(args, 'n')) {
+ window_set_name(w, args_get(args, 'n'));
+ options_set_number(w->options, "automatic-rename", 0);
+ }
+ server_unlink_window(src_s, wl);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) {
+ cmdq_error(item, "index in use: %d", idx);
+ return (CMD_RETURN_ERROR);
+ }
+
+ TAILQ_REMOVE(&w->panes, wp, entry);
+ server_client_remove_pane(wp);
+ window_lost_pane(w, wp);
+ layout_close_pane(wp);
+
+ w = wp->window = window_create(w->sx, w->sy, w->xpixel, w->ypixel);
+ options_set_parent(wp->options, w->options);
+ wp->flags |= PANE_STYLECHANGED;
+ TAILQ_INSERT_HEAD(&w->panes, wp, entry);
+ w->active = wp;
+ w->latest = tc;
+
+ if (!args_has(args, 'n')) {
+ name = default_window_name(w);
+ window_set_name(w, name);
+ free(name);
+ } else {
+ window_set_name(w, args_get(args, 'n'));
+ options_set_number(w->options, "automatic-rename", 0);
+ }
+
+ layout_init(w, wp);
+ wp->flags |= PANE_CHANGED;
+
+ if (idx == -1)
+ idx = -1 - options_get_number(dst_s->options, "base-index");
+ wl = session_attach(dst_s, w, idx, &cause); /* can't fail */
+ if (!args_has(args, 'd')) {
+ session_select(dst_s, wl->idx);
+ cmd_find_from_session(current, dst_s, 0);
+ }
+
+ server_redraw_session(src_s);
+ if (src_s != dst_s)
+ server_redraw_session(dst_s);
+ server_status_session_group(src_s);
+ if (src_s != dst_s)
+ server_status_session_group(dst_s);
+
+ if (args_has(args, 'P')) {
+ if ((template = args_get(args, 'F')) == NULL)
+ template = BREAK_PANE_TEMPLATE;
+ cp = format_single(item, template, tc, dst_s, wl, wp);
+ cmdq_print(item, "%s", cp);
+ free(cp);
+ }
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c
new file mode 100644
index 0000000..964f831
--- /dev/null
+++ b/cmd-capture-pane.c
@@ -0,0 +1,245 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net>
+ *
+ * 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"
+
+/*
+ * Write the entire contents of a pane to a buffer or stdout.
+ */
+
+static enum cmd_retval cmd_capture_pane_exec(struct cmd *, struct cmdq_item *);
+
+static char *cmd_capture_pane_append(char *, size_t *, char *, size_t);
+static char *cmd_capture_pane_pending(struct args *, struct window_pane *,
+ size_t *);
+static char *cmd_capture_pane_history(struct args *, struct cmdq_item *,
+ struct window_pane *, size_t *);
+
+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] "
+ "[-S start-line] " CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_capture_pane_exec
+};
+
+const struct cmd_entry cmd_clear_history_entry = {
+ .name = "clear-history",
+ .alias = "clearhist",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_capture_pane_exec
+};
+
+static char *
+cmd_capture_pane_append(char *buf, size_t *len, char *line, size_t linelen)
+{
+ buf = xrealloc(buf, *len + linelen + 1);
+ memcpy(buf + *len, line, linelen);
+ *len += linelen;
+ return (buf);
+}
+
+static char *
+cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
+ size_t *len)
+{
+ struct evbuffer *pending;
+ char *buf, *line, tmp[5];
+ size_t linelen;
+ u_int i;
+
+ pending = input_pending(wp->ictx);
+ if (pending == NULL)
+ return (xstrdup(""));
+
+ line = EVBUFFER_DATA(pending);
+ linelen = EVBUFFER_LENGTH(pending);
+
+ buf = xstrdup("");
+ if (args_has(args, 'C')) {
+ for (i = 0; i < linelen; i++) {
+ if (line[i] >= ' ' && line[i] != '\\') {
+ tmp[0] = line[i];
+ tmp[1] = '\0';
+ } else
+ xsnprintf(tmp, sizeof tmp, "\\%03hho", line[i]);
+ buf = cmd_capture_pane_append(buf, len, tmp,
+ strlen(tmp));
+ }
+ } else
+ buf = cmd_capture_pane_append(buf, len, line, linelen);
+ return (buf);
+}
+
+static char *
+cmd_capture_pane_history(struct args *args, struct cmdq_item *item,
+ struct window_pane *wp, size_t *len)
+{
+ struct grid *gd;
+ const struct grid_line *gl;
+ struct grid_cell *gc = NULL;
+ int n, with_codes, escape_c0, join_lines, no_trim;
+ u_int i, sx, top, bottom, tmp;
+ char *cause, *buf, *line;
+ const char *Sflag, *Eflag;
+ size_t linelen;
+
+ sx = screen_size_x(&wp->base);
+ if (args_has(args, 'a')) {
+ gd = wp->base.saved_grid;
+ if (gd == NULL) {
+ if (!args_has(args, 'q')) {
+ cmdq_error(item, "no alternate screen");
+ return (NULL);
+ }
+ return (xstrdup(""));
+ }
+ } else
+ gd = wp->base.grid;
+
+ Sflag = args_get(args, 'S');
+ if (Sflag != NULL && strcmp(Sflag, "-") == 0)
+ top = 0;
+ else {
+ n = args_strtonum(args, 'S', INT_MIN, SHRT_MAX, &cause);
+ if (cause != NULL) {
+ top = gd->hsize;
+ free(cause);
+ } else if (n < 0 && (u_int) -n > gd->hsize)
+ top = 0;
+ else
+ top = gd->hsize + n;
+ if (top > gd->hsize + gd->sy - 1)
+ top = gd->hsize + gd->sy - 1;
+ }
+
+ Eflag = args_get(args, 'E');
+ if (Eflag != NULL && strcmp(Eflag, "-") == 0)
+ bottom = gd->hsize + gd->sy - 1;
+ else {
+ n = args_strtonum(args, 'E', INT_MIN, SHRT_MAX, &cause);
+ if (cause != NULL) {
+ bottom = gd->hsize + gd->sy - 1;
+ free(cause);
+ } else if (n < 0 && (u_int) -n > gd->hsize)
+ bottom = 0;
+ else
+ bottom = gd->hsize + n;
+ if (bottom > gd->hsize + gd->sy - 1)
+ bottom = gd->hsize + gd->sy - 1;
+ }
+
+ if (bottom < top) {
+ tmp = bottom;
+ bottom = top;
+ 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');
+
+ 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);
+ linelen = strlen(line);
+
+ buf = cmd_capture_pane_append(buf, len, line, linelen);
+
+ gl = grid_peek_line(gd, i);
+ if (!join_lines || !(gl->flags & GRID_LINE_WRAPPED))
+ buf[(*len)++] = '\n';
+
+ free(line);
+ }
+ return (buf);
+}
+
+static enum cmd_retval
+cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *c = cmdq_get_client(item);
+ struct window_pane *wp = cmdq_get_target(item)->wp;
+ char *buf, *cause;
+ const char *bufname;
+ size_t len;
+
+ if (cmd_get_entry(self) == &cmd_clear_history_entry) {
+ window_pane_reset_mode_all(wp);
+ grid_clear_history(wp->base.grid);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ len = 0;
+ if (args_has(args, 'P'))
+ buf = cmd_capture_pane_pending(args, wp, &len);
+ else
+ buf = cmd_capture_pane_history(args, item, wp, &len);
+ if (buf == NULL)
+ return (CMD_RETURN_ERROR);
+
+ if (args_has(args, 'p')) {
+ if (len > 0 && buf[len - 1] == '\n')
+ len--;
+ if (c->flags & CLIENT_CONTROL)
+ control_write(c, "%.*s", (int)len, buf);
+ else {
+ if (!file_can_print(c)) {
+ cmdq_error(item, "can't write to client");
+ free(buf);
+ return (CMD_RETURN_ERROR);
+ }
+ file_print_buffer(c, buf, len);
+ file_print(c, "\n");
+ free(buf);
+ }
+ } else {
+ bufname = NULL;
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
+
+ if (paste_set(buf, len, bufname, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ free(buf);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c
new file mode 100644
index 0000000..7aa1d21
--- /dev/null
+++ b/cmd-choose-tree.c
@@ -0,0 +1,117 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 Thomas Adam <thomas@xteddy.org>
+ *
+ * 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 "tmux.h"
+
+/*
+ * Enter a mode.
+ */
+
+static enum args_parse_type cmd_choose_tree_args_parse(struct args *args,
+ u_int idx, char **cause);
+static enum cmd_retval cmd_choose_tree_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_choose_tree_entry = {
+ .name = "choose-tree",
+ .alias = NULL,
+
+ .args = { "F:f:GK:NO:rst:wZ", 0, 1, cmd_choose_tree_args_parse },
+ .usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] "
+ "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_choose_tree_exec
+};
+
+const struct cmd_entry cmd_choose_client_entry = {
+ .name = "choose-client",
+ .alias = NULL,
+
+ .args = { "F:f:K:NO:rt:Z", 0, 1, cmd_choose_tree_args_parse },
+ .usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
+ "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_choose_tree_exec
+};
+
+const struct cmd_entry cmd_choose_buffer_entry = {
+ .name = "choose-buffer",
+ .alias = NULL,
+
+ .args = { "F:f:K:NO:rt:Z", 0, 1, cmd_choose_tree_args_parse },
+ .usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
+ "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_choose_tree_exec
+};
+
+const struct cmd_entry cmd_customize_mode_entry = {
+ .name = "customize-mode",
+ .alias = NULL,
+
+ .args = { "F:f:Nt:Z", 0, 0, NULL },
+ .usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_choose_tree_exec
+};
+
+static enum args_parse_type
+cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx,
+ __unused char **cause)
+{
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+}
+
+static enum cmd_retval
+cmd_choose_tree_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 window_pane *wp = target->wp;
+ const struct window_mode *mode;
+
+ if (cmd_get_entry(self) == &cmd_choose_buffer_entry) {
+ if (paste_get_top(NULL) == NULL)
+ return (CMD_RETURN_NORMAL);
+ mode = &window_buffer_mode;
+ } else if (cmd_get_entry(self) == &cmd_choose_client_entry) {
+ if (server_client_how_many() == 0)
+ return (CMD_RETURN_NORMAL);
+ mode = &window_client_mode;
+ } else if (cmd_get_entry(self) == &cmd_customize_mode_entry)
+ mode = &window_customize_mode;
+ else
+ mode = &window_tree_mode;
+
+ window_pane_set_mode(wp, NULL, mode, target, args);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
new file mode 100644
index 0000000..4455856
--- /dev/null
+++ b/cmd-command-prompt.c
@@ -0,0 +1,238 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "tmux.h"
+
+/*
+ * Prompt for command in client.
+ */
+
+static enum args_parse_type cmd_command_prompt_args_parse(struct args *,
+ u_int, char **);
+static enum cmd_retval cmd_command_prompt_exec(struct cmd *,
+ struct cmdq_item *);
+
+static int cmd_command_prompt_callback(struct client *, void *,
+ const char *, int);
+static void cmd_command_prompt_free(void *);
+
+const struct cmd_entry cmd_command_prompt_entry = {
+ .name = "command-prompt",
+ .alias = NULL,
+
+ .args = { "1bFkiI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse },
+ .usage = "[-1bFkiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
+ " [-T type] [template]",
+
+ .flags = CMD_CLIENT_TFLAG,
+ .exec = cmd_command_prompt_exec
+};
+
+struct cmd_command_prompt_prompt {
+ char *input;
+ char *prompt;
+};
+
+struct cmd_command_prompt_cdata {
+ struct cmdq_item *item;
+ struct args_command_state *state;
+
+ int flags;
+ enum prompt_type prompt_type;
+
+ struct cmd_command_prompt_prompt *prompts;
+ u_int count;
+ u_int current;
+
+ int argc;
+ char **argv;
+};
+
+static enum args_parse_type
+cmd_command_prompt_args_parse(__unused struct args *args, __unused u_int idx,
+ __unused char **cause)
+{
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+}
+
+static enum cmd_retval
+cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ const char *type, *s, *input;
+ struct cmd_command_prompt_cdata *cdata;
+ char *tmp, *prompts, *prompt, *next_prompt;
+ char *inputs = NULL, *next_input;
+ u_int count = args_count(args);
+ int wait = !args_has(args, 'b'), space = 1;
+
+ if (tc->prompt_string != NULL)
+ return (CMD_RETURN_NORMAL);
+ if (args_has(args, 'i'))
+ wait = 0;
+
+ cdata = xcalloc(1, sizeof *cdata);
+ if (wait)
+ cdata->item = item;
+ cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
+ args_has(args, 'F'));
+
+ if ((s = args_get(args, 'p')) == NULL) {
+ if (count != 0) {
+ tmp = args_make_commands_get_command(cdata->state);
+ xasprintf(&prompts, "(%s)", tmp);
+ free(tmp);
+ } else {
+ prompts = xstrdup(":");
+ space = 0;
+ }
+ next_prompt = prompts;
+ } else
+ next_prompt = prompts = xstrdup(s);
+ if ((s = args_get(args, 'I')) != NULL)
+ next_input = inputs = xstrdup(s);
+ else
+ next_input = NULL;
+ while ((prompt = strsep(&next_prompt, ",")) != NULL) {
+ cdata->prompts = xreallocarray(cdata->prompts, cdata->count + 1,
+ sizeof *cdata->prompts);
+ if (!space)
+ tmp = xstrdup(prompt);
+ else
+ xasprintf(&tmp, "%s ", prompt);
+ cdata->prompts[cdata->count].prompt = tmp;
+
+ if (next_input != NULL) {
+ input = strsep(&next_input, ",");
+ if (input == NULL)
+ input = "";
+ } else
+ input = "";
+ cdata->prompts[cdata->count].input = xstrdup(input);
+
+ cdata->count++;
+ }
+ free(inputs);
+ free(prompts);
+
+ if ((type = args_get(args, 'T')) != NULL) {
+ cdata->prompt_type = status_prompt_type(type);
+ if (cdata->prompt_type == PROMPT_TYPE_INVALID) {
+ cmdq_error(item, "unknown type: %s", type);
+ return (CMD_RETURN_ERROR);
+ }
+ } else
+ cdata->prompt_type = PROMPT_TYPE_COMMAND;
+
+ if (args_has(args, '1'))
+ cdata->flags |= PROMPT_SINGLE;
+ else if (args_has(args, 'N'))
+ cdata->flags |= PROMPT_NUMERIC;
+ else if (args_has(args, 'i'))
+ cdata->flags |= PROMPT_INCREMENTAL;
+ else if (args_has(args, 'k'))
+ cdata->flags |= PROMPT_KEY;
+ status_prompt_set(tc, target, cdata->prompts[0].prompt,
+ cdata->prompts[0].input, cmd_command_prompt_callback,
+ cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type);
+
+ if (!wait)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
+
+static int
+cmd_command_prompt_callback(struct client *c, void *data, const char *s,
+ int done)
+{
+ struct cmd_command_prompt_cdata *cdata = data;
+ char *error;
+ struct cmdq_item *item = cdata->item, *new_item;
+ struct cmd_list *cmdlist;
+ struct cmd_command_prompt_prompt *prompt;
+ int argc = 0;
+ char **argv = NULL;
+
+ 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];
+ status_prompt_update(c, prompt->prompt, prompt->input);
+ return (1);
+ }
+ }
+
+ argc = cdata->argc;
+ argv = cmd_copy_argv(cdata->argc, cdata->argv);
+ cmd_append_argv(&argc, &argv, s);
+ if (done) {
+ cdata->argc = argc;
+ cdata->argv = cmd_copy_argv(argc, argv);
+ }
+
+ cmdlist = args_make_commands(cdata->state, argc, argv, &error);
+ if (cmdlist == NULL) {
+ cmdq_append(c, cmdq_get_error(error));
+ free(error);
+ } else if (item == NULL) {
+ new_item = cmdq_get_command(cmdlist, NULL);
+ cmdq_append(c, new_item);
+ } else {
+ new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
+ cmdq_insert_after(item, new_item);
+ }
+ cmd_free_argv(argc, argv);
+
+ if (c->prompt_inputcb != cmd_command_prompt_callback)
+ return (1);
+
+out:
+ if (item != NULL)
+ cmdq_continue(item);
+ return (0);
+}
+
+static void
+cmd_command_prompt_free(void *data)
+{
+ struct cmd_command_prompt_cdata *cdata = data;
+ u_int i;
+
+ for (i = 0; i < cdata->count; i++) {
+ free(cdata->prompts[i].prompt);
+ free(cdata->prompts[i].input);
+ }
+ free(cdata->prompts);
+ cmd_free_argv(cdata->argc, cdata->argv);
+ args_make_commands_free(cdata->state);
+ free(cdata);
+}
diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c
new file mode 100644
index 0000000..ce8c95e
--- /dev/null
+++ b/cmd-confirm-before.c
@@ -0,0 +1,142 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
+ *
+ * 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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Asks for confirmation before executing a command.
+ */
+
+static enum args_parse_type cmd_confirm_before_args_parse(struct args *,
+ u_int, char **);
+static enum cmd_retval cmd_confirm_before_exec(struct cmd *,
+ struct cmdq_item *);
+
+static int cmd_confirm_before_callback(struct client *, void *,
+ const char *, int);
+static void cmd_confirm_before_free(void *);
+
+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",
+
+ .flags = CMD_CLIENT_TFLAG,
+ .exec = cmd_confirm_before_exec
+};
+
+struct cmd_confirm_before_data {
+ struct cmdq_item *item;
+ struct cmd_list *cmdlist;
+};
+
+static enum args_parse_type
+cmd_confirm_before_args_parse(__unused struct args *args, __unused u_int idx,
+ __unused char **cause)
+{
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+}
+
+static enum cmd_retval
+cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_confirm_before_data *cdata;
+ struct client *tc = cmdq_get_target_client(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ char *new_prompt;
+ const char *prompt, *cmd;
+ int wait = !args_has(args, 'b');
+
+ cdata = xcalloc(1, sizeof *cdata);
+ cdata->cmdlist = args_make_commands_now(self, item, 0, 1);
+ if (cdata->cmdlist == NULL)
+ return (CMD_RETURN_ERROR);
+
+ if (wait)
+ cdata->item = item;
+
+ 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);
+ }
+
+ status_prompt_set(tc, target, new_prompt, NULL,
+ cmd_confirm_before_callback, cmd_confirm_before_free, cdata,
+ PROMPT_SINGLE, PROMPT_TYPE_COMMAND);
+ free(new_prompt);
+
+ if (!wait)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
+
+static int
+cmd_confirm_before_callback(struct client *c, void *data, const char *s,
+ __unused int done)
+{
+ struct cmd_confirm_before_data *cdata = data;
+ struct cmdq_item *item = cdata->item, *new_item;
+ int retcode = 1;
+
+ if (c->flags & CLIENT_DEAD)
+ goto out;
+
+ if (s == NULL || *s == '\0')
+ goto out;
+ if (tolower((u_char)s[0]) != 'y' || s[1] != '\0')
+ goto out;
+ retcode = 0;
+
+ if (item == NULL) {
+ new_item = cmdq_get_command(cdata->cmdlist, NULL);
+ cmdq_append(c, new_item);
+ } else {
+ new_item = cmdq_get_command(cdata->cmdlist,
+ cmdq_get_state(item));
+ cmdq_insert_after(item, new_item);
+ }
+
+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);
+ }
+ return (0);
+}
+
+static void
+cmd_confirm_before_free(void *data)
+{
+ struct cmd_confirm_before_data *cdata = data;
+
+ cmd_list_free(cdata->cmdlist);
+ free(cdata);
+}
diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c
new file mode 100644
index 0000000..8f698ce
--- /dev/null
+++ b/cmd-copy-mode.c
@@ -0,0 +1,96 @@
+/* $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 "tmux.h"
+
+/*
+ * Enter copy or clock mode.
+ */
+
+static enum cmd_retval cmd_copy_mode_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_copy_mode_entry = {
+ .name = "copy-mode",
+ .alias = NULL,
+
+ .args = { "eHMs:t:uq", 0, 0, NULL },
+ .usage = "[-eHMuq] [-s src-pane] " CMD_TARGET_PANE_USAGE,
+
+ .source = { 's', CMD_FIND_PANE, 0 },
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_copy_mode_exec
+};
+
+const struct cmd_entry cmd_clock_mode_entry = {
+ .name = "clock-mode",
+ .alias = NULL,
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_copy_mode_exec
+};
+
+static enum cmd_retval
+cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct key_event *event = cmdq_get_event(item);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *c = cmdq_get_client(item);
+ struct session *s;
+ struct window_pane *wp = target->wp, *swp;
+
+ if (args_has(args, 'q')) {
+ window_pane_reset_mode_all(wp);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'M')) {
+ if ((wp = cmd_mouse_pane(&event->m, &s, NULL)) == NULL)
+ return (CMD_RETURN_NORMAL);
+ if (c == NULL || c->session != s)
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (cmd_get_entry(self) == &cmd_clock_mode_entry) {
+ window_pane_set_mode(wp, NULL, &window_clock_mode, NULL, NULL);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 's'))
+ swp = source->wp;
+ else
+ swp = wp;
+ if (!window_pane_set_mode(wp, swp, &window_copy_mode, NULL, args)) {
+ if (args_has(args, 'M'))
+ window_copy_start_drag(c, &event->m);
+ }
+ if (args_has(args, 'u'))
+ window_copy_pageup(wp, 0);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-detach-client.c b/cmd-detach-client.c
new file mode 100644
index 0000000..661293a
--- /dev/null
+++ b/cmd-detach-client.c
@@ -0,0 +1,109 @@
+/* $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 <string.h>
+
+#include "tmux.h"
+
+/*
+ * Detach a client.
+ */
+
+static enum cmd_retval cmd_detach_client_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_detach_client_entry = {
+ .name = "detach-client",
+ .alias = "detach",
+
+ .args = { "aE:s:t:P", 0, 0, NULL },
+ .usage = "[-aP] [-E shell-command] "
+ "[-s target-session] " CMD_TARGET_CLIENT_USAGE,
+
+ .source = { 's', CMD_FIND_SESSION, CMD_FIND_CANFAIL },
+
+ .flags = CMD_READONLY|CMD_CLIENT_TFLAG,
+ .exec = cmd_detach_client_exec
+};
+
+const struct cmd_entry cmd_suspend_client_entry = {
+ .name = "suspend-client",
+ .alias = "suspendc",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_CLIENT_USAGE,
+
+ .flags = CMD_CLIENT_TFLAG,
+ .exec = cmd_detach_client_exec
+};
+
+static enum cmd_retval
+cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct client *tc = cmdq_get_target_client(item), *loop;
+ struct session *s;
+ enum msgtype msgtype;
+ const char *cmd = args_get(args, 'E');
+
+ if (cmd_get_entry(self) == &cmd_suspend_client_entry) {
+ server_client_suspend(tc);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'P'))
+ msgtype = MSG_DETACHKILL;
+ else
+ msgtype = MSG_DETACH;
+
+ if (args_has(args, 's')) {
+ s = source->s;
+ if (s == NULL)
+ return (CMD_RETURN_NORMAL);
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop->session == s) {
+ if (cmd != NULL)
+ server_client_exec(loop, cmd);
+ else
+ server_client_detach(loop, msgtype);
+ }
+ }
+ return (CMD_RETURN_STOP);
+ }
+
+ if (args_has(args, 'a')) {
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop->session != NULL && loop != tc) {
+ if (cmd != NULL)
+ server_client_exec(loop, cmd);
+ else
+ server_client_detach(loop, msgtype);
+ }
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (cmd != NULL)
+ server_client_exec(tc, cmd);
+ else
+ server_client_detach(tc, msgtype);
+ return (CMD_RETURN_STOP);
+}
diff --git a/cmd-display-menu.c b/cmd-display-menu.c
new file mode 100644
index 0000000..e6a503b
--- /dev/null
+++ b/cmd-display-menu.c
@@ -0,0 +1,465 @@
+/* $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"
+
+/*
+ * Display a menu on a client.
+ */
+
+static enum args_parse_type cmd_display_menu_args_parse(struct args *,
+ u_int, char **);
+static enum cmd_retval cmd_display_menu_exec(struct cmd *,
+ struct cmdq_item *);
+static enum cmd_retval cmd_display_popup_exec(struct cmd *,
+ struct cmdq_item *);
+
+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] "
+ "[-x position] [-y position] name key command ...",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG,
+ .exec = cmd_display_menu_exec
+};
+
+const struct cmd_entry cmd_display_popup_entry = {
+ .name = "display-popup",
+ .alias = "popup",
+
+ .args = { "Bb:Cc:d:e:Eh:s:S:t:T:w:x:y:", 0, -1, NULL },
+ .usage = "[-BCE] [-b border-lines] [-c target-client] "
+ "[-d start-directory] [-e environment] [-h height] "
+ "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
+ "[-T title] [-w width] [-x position] [-y position] "
+ "[shell-command]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG,
+ .exec = cmd_display_popup_exec
+};
+
+static enum args_parse_type
+cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
+{
+ u_int i = 0;
+ enum args_parse_type type = ARGS_PARSE_STRING;
+
+ for (;;) {
+ type = ARGS_PARSE_STRING;
+ if (i == idx)
+ break;
+ if (*args_string(args, i++) == '\0')
+ continue;
+
+ type = ARGS_PARSE_STRING;
+ if (i++ == idx)
+ break;
+
+ type = ARGS_PARSE_COMMANDS_OR_STRING;
+ if (i++ == idx)
+ break;
+ }
+ return (type);
+}
+
+static int
+cmd_display_menu_get_position(struct client *tc, struct cmdq_item *item,
+ struct args *args, u_int *px, u_int *py, u_int w, u_int h)
+{
+ struct tty *tty = &tc->tty;
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct key_event *event = cmdq_get_event(item);
+ struct session *s = tc->session;
+ struct winlink *wl = target->wl;
+ struct window_pane *wp = target->wp;
+ struct style_ranges *ranges = NULL;
+ struct style_range *sr = NULL;
+ const char *xp, *yp;
+ char *p;
+ int top;
+ u_int line, ox, oy, sx, sy, lines, position;
+ long n;
+ struct format_tree *ft;
+
+ /*
+ * Work out the position from the -x and -y arguments. This is the
+ * bottom-left position.
+ */
+
+ /* If the popup is too big, stop now. */
+ if (w > tty->sx || h > tty->sy)
+ return (0);
+
+ /* Create format with mouse position if any. */
+ ft = format_create_from_target(item);
+ if (event->m.valid) {
+ format_add(ft, "popup_mouse_x", "%u", event->m.x);
+ format_add(ft, "popup_mouse_y", "%u", event->m.y);
+ }
+
+ /*
+ * If there are any status lines, add this window position and the
+ * status line position.
+ */
+ top = status_at_line(tc);
+ if (top != -1) {
+ lines = status_line_size(tc);
+ if (top == 0)
+ top = lines;
+ else
+ top = 0;
+ position = options_get_number(s->options, "status-position");
+
+ for (line = 0; line < lines; line++) {
+ ranges = &tc->status.entries[line].ranges;
+ TAILQ_FOREACH(sr, ranges, entry) {
+ if (sr->type != STYLE_RANGE_WINDOW)
+ continue;
+ if (sr->argument == (u_int)wl->idx)
+ break;
+ }
+ if (sr != NULL)
+ break;
+ }
+
+ if (sr != NULL) {
+ format_add(ft, "popup_window_status_line_x", "%u",
+ sr->start);
+ if (position == 0) {
+ format_add(ft, "popup_window_status_line_y",
+ "%u", line + 1 + h);
+ } else {
+ format_add(ft, "popup_window_status_line_y",
+ "%u", tty->sy - lines + line);
+ }
+ }
+
+ if (position == 0)
+ format_add(ft, "popup_status_line_y", "%u", lines + h);
+ else {
+ format_add(ft, "popup_status_line_y", "%u",
+ tty->sy - lines);
+ }
+ } else
+ top = 0;
+
+ /* Popup width and height. */
+ format_add(ft, "popup_width", "%u", w);
+ format_add(ft, "popup_height", "%u", h);
+
+ /* Position so popup is in the centre. */
+ n = (long)(tty->sx - 1) / 2 - w / 2;
+ if (n < 0)
+ format_add(ft, "popup_centre_x", "%u", 0);
+ else
+ format_add(ft, "popup_centre_x", "%ld", n);
+ n = (tty->sy - 1) / 2 + h / 2;
+ if (n >= tty->sy)
+ format_add(ft, "popup_centre_y", "%u", tty->sy - h);
+ else
+ format_add(ft, "popup_centre_y", "%ld", n);
+
+ /* Position of popup relative to mouse. */
+ if (event->m.valid) {
+ n = (long)event->m.x - w / 2;
+ if (n < 0)
+ format_add(ft, "popup_mouse_centre_x", "%u", 0);
+ else
+ format_add(ft, "popup_mouse_centre_x", "%ld", n);
+ n = event->m.y - h / 2;
+ if (n + h >= tty->sy) {
+ format_add(ft, "popup_mouse_centre_y", "%u",
+ tty->sy - h);
+ } else
+ format_add(ft, "popup_mouse_centre_y", "%ld", n);
+ n = (long)event->m.y + h;
+ if (n >= tty->sy)
+ format_add(ft, "popup_mouse_top", "%u", tty->sy - 1);
+ else
+ format_add(ft, "popup_mouse_top", "%ld", n);
+ n = event->m.y - h;
+ if (n < 0)
+ format_add(ft, "popup_mouse_bottom", "%u", 0);
+ else
+ format_add(ft, "popup_mouse_bottom", "%ld", n);
+ }
+
+ /* Position in pane. */
+ tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
+ n = top + wp->yoff - oy + h;
+ if (n >= tty->sy)
+ format_add(ft, "popup_pane_top", "%u", tty->sy - h);
+ else
+ format_add(ft, "popup_pane_top", "%ld", n);
+ format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy);
+ format_add(ft, "popup_pane_left", "%u", wp->xoff - ox);
+ n = (long)wp->xoff + wp->sx - ox - w;
+ if (n < 0)
+ format_add(ft, "popup_pane_right", "%u", 0);
+ else
+ format_add(ft, "popup_pane_right", "%ld", n);
+
+ /* Expand horizontal position. */
+ xp = args_get(args, 'x');
+ if (xp == NULL || strcmp(xp, "C") == 0)
+ xp = "#{popup_centre_x}";
+ else if (strcmp(xp, "R") == 0)
+ xp = "#{popup_pane_right}";
+ else if (strcmp(xp, "P") == 0)
+ xp = "#{popup_pane_left}";
+ else if (strcmp(xp, "M") == 0)
+ xp = "#{popup_mouse_centre_x}";
+ else if (strcmp(xp, "W") == 0)
+ xp = "#{popup_window_status_line_x}";
+ p = format_expand(ft, xp);
+ n = strtol(p, NULL, 10);
+ if (n + w >= tty->sx)
+ n = tty->sx - w;
+ else if (n < 0)
+ n = 0;
+ *px = n;
+ log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
+ free(p);
+
+ /* Expand vertical position */
+ yp = args_get(args, 'y');
+ if (yp == NULL || strcmp(yp, "C") == 0)
+ yp = "#{popup_centre_y}";
+ else if (strcmp(yp, "P") == 0)
+ yp = "#{popup_pane_bottom}";
+ else if (strcmp(yp, "M") == 0)
+ yp = "#{popup_mouse_top}";
+ else if (strcmp(yp, "S") == 0)
+ yp = "#{popup_status_line_y}";
+ else if (strcmp(yp, "W") == 0)
+ yp = "#{popup_window_status_line_y}";
+ p = format_expand(ft, yp);
+ n = strtol(p, NULL, 10);
+ if (n < h)
+ n = 0;
+ else
+ n -= h;
+ if (n + h >= tty->sy)
+ n = tty->sy - h;
+ else if (n < 0)
+ n = 0;
+ *py = n;
+ log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
+ free(p);
+
+ return (1);
+}
+
+static enum cmd_retval
+cmd_display_menu_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 key_event *event = cmdq_get_event(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;
+ u_int px, py, i, count = args_count(args);
+
+ if (tc->overlay_draw != NULL)
+ return (CMD_RETURN_NORMAL);
+
+ if (args_has(args, 'T'))
+ title = format_single_from_target(item, args_get(args, 'T'));
+ else
+ title = xstrdup("");
+ menu = menu_create(title);
+
+ for (i = 0; i != count; /* nothing */) {
+ name = args_string(args, i++);
+ if (*name == '\0') {
+ menu_add_item(menu, NULL, item, tc, target);
+ continue;
+ }
+
+ if (count - i < 2) {
+ cmdq_error(item, "not enough arguments");
+ free(title);
+ menu_free(menu);
+ return (CMD_RETURN_ERROR);
+ }
+ key = args_string(args, i++);
+
+ menu_item.name = name;
+ menu_item.key = key_string_lookup_string(key);
+ menu_item.command = args_string(args, i++);
+
+ menu_add_item(menu, &menu_item, item, tc, target);
+ }
+ free(title);
+ if (menu == NULL) {
+ cmdq_error(item, "invalid menu arguments");
+ return (CMD_RETURN_ERROR);
+ }
+ if (menu->count == 0) {
+ menu_free(menu);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (!cmd_display_menu_get_position(tc, item, args, &px, &py,
+ menu->width + 4, menu->count + 2)) {
+ menu_free(menu);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ 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)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
+
+static enum cmd_retval
+cmd_display_popup_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 session *s = target->s;
+ struct client *tc = cmdq_get_target_client(item);
+ struct tty *tty = &tc->tty;
+ const char *value, *shell, *shellcmd = NULL;
+ const char *style = args_get(args, 's');
+ const char *border_style = args_get(args, 'S');
+ char *cwd, *cause = NULL, **argv = NULL, *title;
+ int flags = 0, argc = 0;
+ enum box_lines lines = BOX_LINES_DEFAULT;
+ u_int px, py, w, h, count = args_count(args);
+ struct args_value *av;
+ struct environ *env = NULL;
+ struct options *o = s->curw->window->options;
+ struct options_entry *oe;
+
+ if (args_has(args, 'C')) {
+ server_client_clear_overlay(tc);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (tc->overlay_draw != NULL)
+ return (CMD_RETURN_NORMAL);
+
+ h = tty->sy / 2;
+ if (args_has(args, 'h')) {
+ h = args_percentage(args, 'h', 1, tty->sy, tty->sy, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "height %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ w = tty->sx / 2;
+ if (args_has(args, 'w')) {
+ w = args_percentage(args, 'w', 1, tty->sx, tty->sx, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "width %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (w > tty->sx)
+ w = tty->sx;
+ if (h > tty->sy)
+ h = tty->sy;
+ if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h))
+ return (CMD_RETURN_NORMAL);
+
+ value = args_get(args, 'b');
+ if (args_has(args, 'B'))
+ lines = BOX_LINES_NONE;
+ else if (value != NULL) {
+ oe = options_get(o, "popup-border-lines");
+ lines = options_find_choice(options_table_entry(oe), value,
+ &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "popup-border-lines %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ value = args_get(args, 'd');
+ if (value != NULL)
+ cwd = format_single_from_target(item, value);
+ else
+ cwd = xstrdup(server_client_get_cwd(tc, s));
+ if (count == 0)
+ shellcmd = options_get_string(s->options, "default-command");
+ else if (count == 1)
+ shellcmd = args_string(args, 0);
+ if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
+ shellcmd = NULL;
+ shell = options_get_string(s->options, "default-shell");
+ if (!checkshell(shell))
+ shell = _PATH_BSHELL;
+ cmd_append_argv(&argc, &argv, shell);
+ } else
+ args_to_vector(args, &argc, &argv);
+
+ if (args_has(args, 'e') >= 1) {
+ env = environ_create();
+ av = args_first_value(args, 'e');
+ while (av != NULL) {
+ environ_put(env, av->string, 0);
+ av = args_next_value(av);
+ }
+ }
+
+ if (args_has(args, 'T'))
+ title = format_single_from_target(item, args_get(args, 'T'));
+ else
+ title = xstrdup("");
+ if (args_has(args, 'E') > 1)
+ flags |= POPUP_CLOSEEXITZERO;
+ else if (args_has(args, 'E'))
+ flags |= POPUP_CLOSEEXIT;
+ if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
+ argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) {
+ cmd_free_argv(argc, argv);
+ if (env != NULL)
+ environ_free(env);
+ free(title);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (env != NULL)
+ environ_free(env);
+ free(title);
+ cmd_free_argv(argc, argv);
+ return (CMD_RETURN_WAIT);
+}
diff --git a/cmd-display-message.c b/cmd-display-message.c
new file mode 100644
index 0000000..7828f69
--- /dev/null
+++ b/cmd-display-message.c
@@ -0,0 +1,149 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
+ *
+ * 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 <time.h>
+
+#include "tmux.h"
+
+/*
+ * Displays a message in the status line.
+ */
+
+#define DISPLAY_MESSAGE_TEMPLATE \
+ "[#{session_name}] #{window_index}:" \
+ "#{window_name}, current pane #{pane_index} " \
+ "- (%H:%M %d-%b-%y)"
+
+static enum cmd_retval cmd_display_message_exec(struct cmd *,
+ struct cmdq_item *);
+
+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] "
+ CMD_TARGET_PANE_USAGE " [message]",
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL,
+ .exec = cmd_display_message_exec
+};
+
+static void
+cmd_display_message_each(const char *key, const char *value, void *arg)
+{
+ struct cmdq_item *item = arg;
+
+ cmdq_print(item, "%s=%s", key, value);
+}
+
+static enum cmd_retval
+cmd_display_message_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 client *tc = cmdq_get_target_client(item), *c;
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ struct window_pane *wp = target->wp;
+ const char *template;
+ char *msg, *cause;
+ int delay = -1, flags;
+ struct format_tree *ft;
+ u_int count = args_count(args);
+
+ if (args_has(args, 'I')) {
+ if (wp == NULL)
+ return (CMD_RETURN_NORMAL);
+ switch (window_pane_start_input(wp, item, &cause)) {
+ case -1:
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ case 1:
+ return (CMD_RETURN_NORMAL);
+ case 0:
+ return (CMD_RETURN_WAIT);
+ }
+ }
+
+ if (args_has(args, 'F') && count != 0) {
+ cmdq_error(item, "only one of -F or argument must be given");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'd')) {
+ delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "delay %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (count != 0)
+ template = args_string(args, 0);
+ else
+ template = args_get(args, 'F');
+ if (template == NULL)
+ template = DISPLAY_MESSAGE_TEMPLATE;
+
+ /*
+ * -c is intended to be the client where the message should be
+ * displayed if -p is not given. But it makes sense to use it for the
+ * formats too, assuming it matches the session. If it doesn't, use the
+ * best client for the session.
+ */
+ if (tc != NULL && tc->session == s)
+ c = tc;
+ else if (s != NULL)
+ c = cmd_find_best_client(s);
+ else
+ c = NULL;
+ if (args_has(args, 'v'))
+ flags = FORMAT_VERBOSE;
+ else
+ flags = 0;
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags);
+ format_defaults(ft, c, s, wl, wp);
+
+ if (args_has(args, 'a')) {
+ format_each(ft, cmd_display_message_each, item);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ 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);
+ }
+ free(msg);
+
+ format_free(ft);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-display-panes.c b/cmd-display-panes.c
new file mode 100644
index 0000000..5773a2d
--- /dev/null
+++ b/cmd-display-panes.c
@@ -0,0 +1,312 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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"
+
+/*
+ * Display panes on a client.
+ */
+
+static enum args_parse_type cmd_display_panes_args_parse(struct args *,
+ u_int, char **);
+static enum cmd_retval cmd_display_panes_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_display_panes_entry = {
+ .name = "display-panes",
+ .alias = "displayp",
+
+ .args = { "bd:Nt:", 0, 1, cmd_display_panes_args_parse },
+ .usage = "[-bN] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]",
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG,
+ .exec = cmd_display_panes_exec
+};
+
+struct cmd_display_panes_data {
+ struct cmdq_item *item;
+ struct args_command_state *state;
+};
+
+static enum args_parse_type
+cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx,
+ __unused char **cause)
+{
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+}
+
+static void
+cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
+ struct window_pane *wp)
+{
+ struct client *c = ctx->c;
+ struct tty *tty = &c->tty;
+ struct session *s = c->session;
+ struct options *oo = s->options;
+ struct window *w = wp->window;
+ struct grid_cell fgc, bgc;
+ u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy;
+ int colour, active_colour;
+ char buf[16], lbuf[16], rbuf[16], *ptr;
+ size_t len, llen, rlen;
+
+ if (wp->xoff + wp->sx <= ctx->ox ||
+ wp->xoff >= ctx->ox + ctx->sx ||
+ wp->yoff + wp->sy <= ctx->oy ||
+ wp->yoff >= ctx->oy + ctx->sy)
+ return;
+
+ if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) {
+ /* All visible. */
+ xoff = wp->xoff - ctx->ox;
+ sx = wp->sx;
+ } else if (wp->xoff < ctx->ox &&
+ wp->xoff + wp->sx > ctx->ox + ctx->sx) {
+ /* Both left and right not visible. */
+ xoff = 0;
+ sx = ctx->sx;
+ } else if (wp->xoff < ctx->ox) {
+ /* Left not visible. */
+ xoff = 0;
+ sx = wp->sx - (ctx->ox - wp->xoff);
+ } else {
+ /* Right not visible. */
+ xoff = wp->xoff - ctx->ox;
+ sx = wp->sx - xoff;
+ }
+ if (wp->yoff >= ctx->oy && wp->yoff + wp->sy <= ctx->oy + ctx->sy) {
+ /* All visible. */
+ yoff = wp->yoff - ctx->oy;
+ sy = wp->sy;
+ } else if (wp->yoff < ctx->oy &&
+ wp->yoff + wp->sy > ctx->oy + ctx->sy) {
+ /* Both top and bottom not visible. */
+ yoff = 0;
+ sy = ctx->sy;
+ } else if (wp->yoff < ctx->oy) {
+ /* Top not visible. */
+ yoff = 0;
+ sy = wp->sy - (ctx->oy - wp->yoff);
+ } else {
+ /* Bottom not visible. */
+ yoff = wp->yoff - ctx->oy;
+ sy = wp->sy - yoff;
+ }
+
+ if (ctx->statustop)
+ yoff += ctx->statuslines;
+ px = sx / 2;
+ py = sy / 2;
+
+ if (window_pane_index(wp, &pane) != 0)
+ fatalx("index not found");
+ len = xsnprintf(buf, sizeof buf, "%u", pane);
+
+ if (sx < len)
+ return;
+ colour = options_get_number(oo, "display-panes-colour");
+ active_colour = options_get_number(oo, "display-panes-active-colour");
+
+ memcpy(&fgc, &grid_default_cell, sizeof fgc);
+ memcpy(&bgc, &grid_default_cell, sizeof bgc);
+ if (w->active == wp) {
+ fgc.fg = active_colour;
+ bgc.bg = active_colour;
+ } else {
+ fgc.fg = colour;
+ bgc.bg = colour;
+ }
+
+ rlen = xsnprintf(rbuf, sizeof rbuf, "%ux%u", wp->sx, wp->sy);
+ if (pane > 9 && pane < 35)
+ llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10));
+ else
+ llen = 0;
+
+ if (sx < len * 6 || sy < 5) {
+ tty_attributes(tty, &fgc, &grid_default_cell, NULL);
+ if (sx >= len + llen + 1) {
+ len += llen + 1;
+ tty_cursor(tty, xoff + px - len / 2, yoff + py);
+ tty_putn(tty, buf, len, len);
+ tty_putn(tty, " ", 1, 1);
+ tty_putn(tty, lbuf, llen, llen);
+ } else {
+ tty_cursor(tty, xoff + px - len / 2, yoff + py);
+ tty_putn(tty, buf, len, len);
+ }
+ goto out;
+ }
+
+ px -= len * 3;
+ py -= 2;
+
+ tty_attributes(tty, &bgc, &grid_default_cell, NULL);
+ for (ptr = buf; *ptr != '\0'; ptr++) {
+ if (*ptr < '0' || *ptr > '9')
+ continue;
+ idx = *ptr - '0';
+
+ for (j = 0; j < 5; j++) {
+ for (i = px; i < px + 5; i++) {
+ tty_cursor(tty, xoff + i, yoff + py + j);
+ if (window_clock_table[idx][j][i - px])
+ tty_putc(tty, ' ');
+ }
+ }
+ px += 6;
+ }
+
+ if (sy <= 6)
+ goto out;
+ tty_attributes(tty, &fgc, &grid_default_cell, NULL);
+ if (rlen != 0 && sx >= rlen) {
+ tty_cursor(tty, xoff + sx - rlen, yoff);
+ tty_putn(tty, rbuf, rlen, rlen);
+ }
+ if (llen != 0) {
+ tty_cursor(tty, xoff + sx / 2 + len * 3 - llen - 1,
+ yoff + py + 5);
+ tty_putn(tty, lbuf, llen, llen);
+ }
+
+out:
+ tty_cursor(tty, 0, 0);
+}
+
+static void
+cmd_display_panes_draw(struct client *c, __unused void *data,
+ struct screen_redraw_ctx *ctx)
+{
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+
+ log_debug("%s: %s @%u", __func__, c->name, w->id);
+
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (window_pane_visible(wp))
+ cmd_display_panes_draw_pane(ctx, wp);
+ }
+}
+
+static void
+cmd_display_panes_free(__unused struct client *c, void *data)
+{
+ struct cmd_display_panes_data *cdata = data;
+
+ if (cdata->item != NULL)
+ cmdq_continue(cdata->item);
+ args_make_commands_free(cdata->state);
+ free(cdata);
+}
+
+static int
+cmd_display_panes_key(struct client *c, void *data, struct key_event *event)
+{
+ struct cmd_display_panes_data *cdata = data;
+ char *expanded, *error;
+ struct cmdq_item *item = cdata->item, *new_item;
+ struct cmd_list *cmdlist;
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+ u_int index;
+ key_code key;
+
+ if (event->key >= '0' && event->key <= '9')
+ index = event->key - '0';
+ else if ((event->key & KEYC_MASK_MODIFIERS) == 0) {
+ key = (event->key & KEYC_MASK_KEY);
+ if (key >= 'a' && key <= 'z')
+ index = 10 + (key - 'a');
+ else
+ return (-1);
+ } else
+ return (-1);
+
+ wp = window_pane_at_index(w, index);
+ if (wp == NULL)
+ return (1);
+ window_unzoom(w);
+
+ xasprintf(&expanded, "%%%u", wp->id);
+
+ cmdlist = args_make_commands(cdata->state, 1, &expanded, &error);
+ if (cmdlist == NULL) {
+ cmdq_append(c, cmdq_get_error(error));
+ free(error);
+ } else if (item == NULL) {
+ new_item = cmdq_get_command(cmdlist, NULL);
+ cmdq_append(c, new_item);
+ } else {
+ new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
+ cmdq_insert_after(item, new_item);
+ }
+
+ free(expanded);
+ return (1);
+}
+
+static enum cmd_retval
+cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s = tc->session;
+ u_int delay;
+ char *cause;
+ struct cmd_display_panes_data *cdata;
+ int wait = !args_has(args, 'b');
+
+ if (tc->overlay_draw != NULL)
+ return (CMD_RETURN_NORMAL);
+
+ if (args_has(args, 'd')) {
+ delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "delay %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ } else
+ delay = options_get_number(s->options, "display-panes-time");
+
+ cdata = xcalloc(1, sizeof *cdata);
+ if (wait)
+ cdata->item = item;
+ cdata->state = args_make_commands_prepare(self, item, 0,
+ "select-pane -t \"%%%\"", wait, 0);
+
+ if (args_has(args, 'N')) {
+ server_client_set_overlay(tc, delay, NULL, NULL,
+ cmd_display_panes_draw, NULL, cmd_display_panes_free, NULL,
+ cdata);
+ } else {
+ server_client_set_overlay(tc, delay, NULL, NULL,
+ cmd_display_panes_draw, cmd_display_panes_key,
+ cmd_display_panes_free, NULL, cdata);
+ }
+
+ if (!wait)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
diff --git a/cmd-find-window.c b/cmd-find-window.c
new file mode 100644
index 0000000..6e07537
--- /dev/null
+++ b/cmd-find-window.c
@@ -0,0 +1,113 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * Find window containing text.
+ */
+
+static enum cmd_retval cmd_find_window_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_find_window_entry = {
+ .name = "find-window",
+ .alias = "findw",
+
+ .args = { "CiNrt:TZ", 1, 1, NULL },
+ .usage = "[-CiNrTZ] " CMD_TARGET_PANE_USAGE " match-string",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_find_window_exec
+};
+
+static enum cmd_retval
+cmd_find_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self), *new_args;
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct window_pane *wp = target->wp;
+ const char *s = args_string(args, 0), *suffix = "";
+ struct args_value *filter;
+ int C, N, T;
+
+ C = args_has(args, 'C');
+ N = args_has(args, 'N');
+ T = args_has(args, 'T');
+
+ if (args_has(args, 'r') && args_has(args, 'i'))
+ suffix = "/ri";
+ else if (args_has(args, 'r'))
+ suffix = "/r";
+ else if (args_has(args, 'i'))
+ suffix = "/i";
+
+ if (!C && !N && !T)
+ C = N = T = 1;
+
+ filter = xcalloc(1, sizeof *filter);
+ filter->type = ARGS_STRING;
+
+ 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);
+ } else if (C && N) {
+ xasprintf(&filter->string,
+ "#{||:#{C%s:%s},#{m%s:*%s*,#{window_name}}}",
+ suffix, s, suffix, s);
+ } else if (C && T) {
+ xasprintf(&filter->string,
+ "#{||:#{C%s:%s},#{m%s:*%s*,#{pane_title}}}",
+ suffix, s, suffix, s);
+ } else if (N && T) {
+ xasprintf(&filter->string,
+ "#{||:#{m%s:*%s*,#{window_name}},"
+ "#{m%s:*%s*,#{pane_title}}}",
+ suffix, s, suffix, s);
+ } else if (C) {
+ xasprintf(&filter->string,
+ "#{C%s:%s}",
+ suffix, s);
+ } else if (N) {
+ xasprintf(&filter->string,
+ "#{m%s:*%s*,#{window_name}}",
+ suffix, s);
+ } else {
+ xasprintf(&filter->string,
+ "#{m%s:*%s*,#{pane_title}}",
+ suffix, s);
+ }
+
+ new_args = args_create();
+ if (args_has(args, 'Z'))
+ args_set(new_args, 'Z', NULL);
+ args_set(new_args, 'f', filter);
+
+ window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args);
+ args_free(new_args);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-find.c b/cmd-find.c
new file mode 100644
index 0000000..e98090d
--- /dev/null
+++ b/cmd-find.c
@@ -0,0 +1,1314 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 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 <fnmatch.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static int cmd_find_session_better(struct session *, struct session *,
+ int);
+static struct session *cmd_find_best_session(struct session **, u_int, int);
+static int cmd_find_best_session_with_window(struct cmd_find_state *);
+static int cmd_find_best_winlink_with_window(struct cmd_find_state *);
+
+static const char *cmd_find_map_table(const char *[][2], const char *);
+
+static void cmd_find_log_state(const char *, struct cmd_find_state *);
+static int cmd_find_get_session(struct cmd_find_state *, const char *);
+static int cmd_find_get_window(struct cmd_find_state *, const char *, int);
+static int cmd_find_get_window_with_session(struct cmd_find_state *,
+ const char *);
+static int cmd_find_get_pane(struct cmd_find_state *, const char *, int);
+static int cmd_find_get_pane_with_session(struct cmd_find_state *,
+ const char *);
+static int cmd_find_get_pane_with_window(struct cmd_find_state *,
+ const char *);
+
+static const char *cmd_find_session_table[][2] = {
+ { NULL, NULL }
+};
+static const char *cmd_find_window_table[][2] = {
+ { "{start}", "^" },
+ { "{last}", "!" },
+ { "{end}", "$" },
+ { "{next}", "+" },
+ { "{previous}", "-" },
+ { NULL, NULL }
+};
+static const char *cmd_find_pane_table[][2] = {
+ { "{last}", "!" },
+ { "{next}", "+" },
+ { "{previous}", "-" },
+ { "{top}", "top" },
+ { "{bottom}", "bottom" },
+ { "{left}", "left" },
+ { "{right}", "right" },
+ { "{top-left}", "top-left" },
+ { "{top-right}", "top-right" },
+ { "{bottom-left}", "bottom-left" },
+ { "{bottom-right}", "bottom-right" },
+ { "{up-of}", "{up-of}" },
+ { "{down-of}", "{down-of}" },
+ { "{left-of}", "{left-of}" },
+ { "{right-of}", "{right-of}" },
+ { NULL, NULL }
+};
+
+/* Find pane containing client if any. */
+static struct window_pane *
+cmd_find_inside_pane(struct client *c)
+{
+ struct window_pane *wp;
+ struct environ_entry *envent;
+
+ if (c == NULL)
+ return (NULL);
+
+ RB_FOREACH(wp, window_pane_tree, &all_window_panes) {
+ if (wp->fd != -1 && strcmp(wp->tty, c->ttyname) == 0)
+ break;
+ }
+ if (wp == NULL) {
+ envent = environ_find(c->environ, "TMUX_PANE");
+ if (envent != NULL)
+ wp = window_pane_find_by_id_str(envent->value);
+ }
+ if (wp != NULL)
+ log_debug("%s: got pane %%%u (%s)", __func__, wp->id, wp->tty);
+ return (wp);
+}
+
+/* Is this client better? */
+static int
+cmd_find_client_better(struct client *c, struct client *than)
+{
+ if (than == NULL)
+ return (1);
+ return (timercmp(&c->activity_time, &than->activity_time, >));
+}
+
+/* Find best client for session. */
+struct client *
+cmd_find_best_client(struct session *s)
+{
+ struct client *c_loop, *c;
+
+ if (s->attached == 0)
+ s = NULL;
+
+ c = NULL;
+ TAILQ_FOREACH(c_loop, &clients, entry) {
+ if (c_loop->session == NULL)
+ continue;
+ if (s != NULL && c_loop->session != s)
+ continue;
+ if (cmd_find_client_better(c_loop, c))
+ c = c_loop;
+ }
+ return (c);
+}
+
+/* Is this session better? */
+static int
+cmd_find_session_better(struct session *s, struct session *than, int flags)
+{
+ int attached;
+
+ if (than == NULL)
+ return (1);
+ if (flags & CMD_FIND_PREFER_UNATTACHED) {
+ attached = (than->attached != 0);
+ if (attached && s->attached == 0)
+ return (1);
+ else if (!attached && s->attached != 0)
+ return (0);
+ }
+ return (timercmp(&s->activity_time, &than->activity_time, >));
+}
+
+/* Find best session from a list, or all if list is NULL. */
+static struct session *
+cmd_find_best_session(struct session **slist, u_int ssize, int flags)
+{
+ struct session *s_loop, *s;
+ u_int i;
+
+ log_debug("%s: %u sessions to try", __func__, ssize);
+
+ s = NULL;
+ if (slist != NULL) {
+ for (i = 0; i < ssize; i++) {
+ if (cmd_find_session_better(slist[i], s, flags))
+ s = slist[i];
+ }
+ } else {
+ RB_FOREACH(s_loop, sessions, &sessions) {
+ if (cmd_find_session_better(s_loop, s, flags))
+ s = s_loop;
+ }
+ }
+ return (s);
+}
+
+/* Find best session and winlink for window. */
+static int
+cmd_find_best_session_with_window(struct cmd_find_state *fs)
+{
+ struct session **slist = NULL;
+ u_int ssize;
+ struct session *s;
+
+ log_debug("%s: window is @%u", __func__, fs->w->id);
+
+ ssize = 0;
+ RB_FOREACH(s, sessions, &sessions) {
+ if (!session_has(s, fs->w))
+ continue;
+ slist = xreallocarray(slist, ssize + 1, sizeof *slist);
+ slist[ssize++] = s;
+ }
+ if (ssize == 0)
+ goto fail;
+ fs->s = cmd_find_best_session(slist, ssize, fs->flags);
+ if (fs->s == NULL)
+ goto fail;
+ free(slist);
+ return (cmd_find_best_winlink_with_window(fs));
+
+fail:
+ free(slist);
+ return (-1);
+}
+
+/*
+ * Find the best winlink for a window (the current if it contains the window,
+ * otherwise the first).
+ */
+static int
+cmd_find_best_winlink_with_window(struct cmd_find_state *fs)
+{
+ struct winlink *wl, *wl_loop;
+
+ log_debug("%s: window is @%u", __func__, fs->w->id);
+
+ wl = NULL;
+ if (fs->s->curw != NULL && fs->s->curw->window == fs->w)
+ wl = fs->s->curw;
+ else {
+ RB_FOREACH(wl_loop, winlinks, &fs->s->windows) {
+ if (wl_loop->window == fs->w) {
+ wl = wl_loop;
+ break;
+ }
+ }
+ }
+ if (wl == NULL)
+ return (-1);
+ fs->wl = wl;
+ fs->idx = fs->wl->idx;
+ return (0);
+}
+
+/* Maps string in table. */
+static const char *
+cmd_find_map_table(const char *table[][2], const char *s)
+{
+ u_int i;
+
+ for (i = 0; table[i][0] != NULL; i++) {
+ if (strcmp(s, table[i][0]) == 0)
+ return (table[i][1]);
+ }
+ return (s);
+}
+
+/* Find session from string. Fills in s. */
+static int
+cmd_find_get_session(struct cmd_find_state *fs, const char *session)
+{
+ struct session *s, *s_loop;
+ struct client *c;
+
+ log_debug("%s: %s", __func__, session);
+
+ /* Check for session ids starting with $. */
+ if (*session == '$') {
+ fs->s = session_find_by_id_str(session);
+ if (fs->s == NULL)
+ return (-1);
+ return (0);
+ }
+
+ /* Look for exactly this session. */
+ fs->s = session_find(session);
+ if (fs->s != NULL)
+ return (0);
+
+ /* Look for as a client. */
+ c = cmd_find_client(NULL, session, 1);
+ if (c != NULL && c->session != NULL) {
+ fs->s = c->session;
+ return (0);
+ }
+
+ /* Stop now if exact only. */
+ if (fs->flags & CMD_FIND_EXACT_SESSION)
+ return (-1);
+
+ /* Otherwise look for prefix. */
+ s = NULL;
+ RB_FOREACH(s_loop, sessions, &sessions) {
+ if (strncmp(session, s_loop->name, strlen(session)) == 0) {
+ if (s != NULL)
+ return (-1);
+ s = s_loop;
+ }
+ }
+ if (s != NULL) {
+ fs->s = s;
+ return (0);
+ }
+
+ /* Then as a pattern. */
+ s = NULL;
+ RB_FOREACH(s_loop, sessions, &sessions) {
+ if (fnmatch(session, s_loop->name, 0) == 0) {
+ if (s != NULL)
+ return (-1);
+ s = s_loop;
+ }
+ }
+ if (s != NULL) {
+ fs->s = s;
+ return (0);
+ }
+
+ return (-1);
+}
+
+/* Find window from string. Fills in s, wl, w. */
+static int
+cmd_find_get_window(struct cmd_find_state *fs, const char *window, int only)
+{
+ log_debug("%s: %s", __func__, window);
+
+ /* Check for window ids starting with @. */
+ if (*window == '@') {
+ fs->w = window_find_by_id_str(window);
+ if (fs->w == NULL)
+ return (-1);
+ return (cmd_find_best_session_with_window(fs));
+ }
+
+ /* Not a window id, so use the current session. */
+ fs->s = fs->current->s;
+
+ /* We now only need to find the winlink in this session. */
+ if (cmd_find_get_window_with_session(fs, window) == 0)
+ return (0);
+
+ /* Otherwise try as a session itself. */
+ if (!only && cmd_find_get_session(fs, window) == 0) {
+ fs->wl = fs->s->curw;
+ fs->w = fs->wl->window;
+ if (~fs->flags & CMD_FIND_WINDOW_INDEX)
+ fs->idx = fs->wl->idx;
+ return (0);
+ }
+
+ return (-1);
+}
+
+/*
+ * Find window from string, assuming it is in given session. Needs s, fills in
+ * wl and w.
+ */
+static int
+cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window)
+{
+ struct winlink *wl;
+ const char *errstr;
+ int idx, n, exact;
+ struct session *s;
+
+ log_debug("%s: %s", __func__, window);
+ exact = (fs->flags & CMD_FIND_EXACT_WINDOW);
+
+ /*
+ * Start with the current window as the default. So if only an index is
+ * found, the window will be the current.
+ */
+ fs->wl = fs->s->curw;
+ fs->w = fs->wl->window;
+
+ /* Check for window ids starting with @. */
+ if (*window == '@') {
+ fs->w = window_find_by_id_str(window);
+ if (fs->w == NULL || !session_has(fs->s, fs->w))
+ return (-1);
+ return (cmd_find_best_winlink_with_window(fs));
+ }
+
+ /* Try as an offset. */
+ if (!exact && (window[0] == '+' || window[0] == '-')) {
+ if (window[1] != '\0')
+ n = strtonum(window + 1, 1, INT_MAX, NULL);
+ else
+ n = 1;
+ s = fs->s;
+ if (fs->flags & CMD_FIND_WINDOW_INDEX) {
+ if (window[0] == '+') {
+ if (INT_MAX - s->curw->idx < n)
+ return (-1);
+ fs->idx = s->curw->idx + n;
+ } else {
+ if (n > s->curw->idx)
+ return (-1);
+ fs->idx = s->curw->idx - n;
+ }
+ return (0);
+ }
+ if (window[0] == '+')
+ fs->wl = winlink_next_by_number(s->curw, s, n);
+ else
+ fs->wl = winlink_previous_by_number(s->curw, s, n);
+ if (fs->wl != NULL) {
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ }
+ }
+
+ /* Try special characters. */
+ if (!exact) {
+ if (strcmp(window, "!") == 0) {
+ fs->wl = TAILQ_FIRST(&fs->s->lastw);
+ if (fs->wl == NULL)
+ return (-1);
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ } else if (strcmp(window, "^") == 0) {
+ fs->wl = RB_MIN(winlinks, &fs->s->windows);
+ if (fs->wl == NULL)
+ return (-1);
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ } else if (strcmp(window, "$") == 0) {
+ fs->wl = RB_MAX(winlinks, &fs->s->windows);
+ if (fs->wl == NULL)
+ return (-1);
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ }
+ }
+
+ /* First see if this is a valid window index in this session. */
+ if (window[0] != '+' && window[0] != '-') {
+ idx = strtonum(window, 0, INT_MAX, &errstr);
+ if (errstr == NULL) {
+ fs->wl = winlink_find_by_index(&fs->s->windows, idx);
+ if (fs->wl != NULL) {
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ }
+ if (fs->flags & CMD_FIND_WINDOW_INDEX) {
+ fs->idx = idx;
+ return (0);
+ }
+ }
+ }
+
+ /* Look for exact matches, error if more than one. */
+ fs->wl = NULL;
+ RB_FOREACH(wl, winlinks, &fs->s->windows) {
+ if (strcmp(window, wl->window->name) == 0) {
+ if (fs->wl != NULL)
+ return (-1);
+ fs->wl = wl;
+ }
+ }
+ if (fs->wl != NULL) {
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ }
+
+ /* Stop now if exact only. */
+ if (exact)
+ return (-1);
+
+ /* Try as the start of a window name, error if multiple. */
+ fs->wl = NULL;
+ RB_FOREACH(wl, winlinks, &fs->s->windows) {
+ if (strncmp(window, wl->window->name, strlen(window)) == 0) {
+ if (fs->wl != NULL)
+ return (-1);
+ fs->wl = wl;
+ }
+ }
+ if (fs->wl != NULL) {
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ }
+
+ /* Now look for pattern matches, again error if multiple. */
+ fs->wl = NULL;
+ RB_FOREACH(wl, winlinks, &fs->s->windows) {
+ if (fnmatch(window, wl->window->name, 0) == 0) {
+ if (fs->wl != NULL)
+ return (-1);
+ fs->wl = wl;
+ }
+ }
+ if (fs->wl != NULL) {
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ return (0);
+ }
+
+ return (-1);
+}
+
+/* Find pane from string. Fills in s, wl, w, wp. */
+static int
+cmd_find_get_pane(struct cmd_find_state *fs, const char *pane, int only)
+{
+ log_debug("%s: %s", __func__, pane);
+
+ /* Check for pane ids starting with %. */
+ if (*pane == '%') {
+ fs->wp = window_pane_find_by_id_str(pane);
+ if (fs->wp == NULL)
+ return (-1);
+ fs->w = fs->wp->window;
+ return (cmd_find_best_session_with_window(fs));
+ }
+
+ /* Not a pane id, so try the current session and window. */
+ fs->s = fs->current->s;
+ fs->wl = fs->current->wl;
+ fs->idx = fs->current->idx;
+ fs->w = fs->current->w;
+
+ /* We now only need to find the pane in this window. */
+ if (cmd_find_get_pane_with_window(fs, pane) == 0)
+ return (0);
+
+ /* Otherwise try as a window itself (this will also try as session). */
+ if (!only && cmd_find_get_window(fs, pane, 0) == 0) {
+ fs->wp = fs->w->active;
+ return (0);
+ }
+
+ return (-1);
+}
+
+/*
+ * Find pane from string, assuming it is in given session. Needs s, fills in wl
+ * and w and wp.
+ */
+static int
+cmd_find_get_pane_with_session(struct cmd_find_state *fs, const char *pane)
+{
+ log_debug("%s: %s", __func__, pane);
+
+ /* Check for pane ids starting with %. */
+ if (*pane == '%') {
+ fs->wp = window_pane_find_by_id_str(pane);
+ if (fs->wp == NULL)
+ return (-1);
+ fs->w = fs->wp->window;
+ return (cmd_find_best_winlink_with_window(fs));
+ }
+
+ /* Otherwise use the current window. */
+ fs->wl = fs->s->curw;
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+
+ /* Now we just need to look up the pane. */
+ return (cmd_find_get_pane_with_window(fs, pane));
+}
+
+/*
+ * Find pane from string, assuming it is in the given window. Needs w, fills in
+ * wp.
+ */
+static int
+cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane)
+{
+ const char *errstr;
+ int idx;
+ struct window_pane *wp;
+ u_int n;
+
+ log_debug("%s: %s", __func__, pane);
+
+ /* Check for pane ids starting with %. */
+ if (*pane == '%') {
+ fs->wp = window_pane_find_by_id_str(pane);
+ if (fs->wp == NULL)
+ return (-1);
+ if (fs->wp->window != fs->w)
+ return (-1);
+ return (0);
+ }
+
+ /* Try special characters. */
+ if (strcmp(pane, "!") == 0) {
+ fs->wp = fs->w->last;
+ if (fs->wp == NULL)
+ return (-1);
+ return (0);
+ } else if (strcmp(pane, "{up-of}") == 0) {
+ fs->wp = window_pane_find_up(fs->current->wp);
+ if (fs->wp == NULL)
+ return (-1);
+ return (0);
+ } else if (strcmp(pane, "{down-of}") == 0) {
+ fs->wp = window_pane_find_down(fs->current->wp);
+ if (fs->wp == NULL)
+ return (-1);
+ return (0);
+ } else if (strcmp(pane, "{left-of}") == 0) {
+ fs->wp = window_pane_find_left(fs->current->wp);
+ if (fs->wp == NULL)
+ return (-1);
+ return (0);
+ } else if (strcmp(pane, "{right-of}") == 0) {
+ fs->wp = window_pane_find_right(fs->current->wp);
+ if (fs->wp == NULL)
+ return (-1);
+ return (0);
+ }
+
+ /* Try as an offset. */
+ if (pane[0] == '+' || pane[0] == '-') {
+ if (pane[1] != '\0')
+ n = strtonum(pane + 1, 1, INT_MAX, NULL);
+ else
+ n = 1;
+ wp = fs->current->wp;
+ if (pane[0] == '+')
+ fs->wp = window_pane_next_by_number(fs->w, wp, n);
+ else
+ fs->wp = window_pane_previous_by_number(fs->w, wp, n);
+ if (fs->wp != NULL)
+ return (0);
+ }
+
+ /* Get pane by index. */
+ idx = strtonum(pane, 0, INT_MAX, &errstr);
+ if (errstr == NULL) {
+ fs->wp = window_pane_at_index(fs->w, idx);
+ if (fs->wp != NULL)
+ return (0);
+ }
+
+ /* Try as a description. */
+ fs->wp = window_find_string(fs->w, pane);
+ if (fs->wp != NULL)
+ return (0);
+
+ return (-1);
+}
+
+/* Clear state. */
+void
+cmd_find_clear_state(struct cmd_find_state *fs, int flags)
+{
+ memset(fs, 0, sizeof *fs);
+
+ fs->flags = flags;
+
+ fs->idx = -1;
+}
+
+/* Check if state is empty. */
+int
+cmd_find_empty_state(struct cmd_find_state *fs)
+{
+ if (fs->s == NULL && fs->wl == NULL && fs->w == NULL && fs->wp == NULL)
+ return (1);
+ return (0);
+}
+
+/* Check if a state if valid. */
+int
+cmd_find_valid_state(struct cmd_find_state *fs)
+{
+ struct winlink *wl;
+
+ if (fs->s == NULL || fs->wl == NULL || fs->w == NULL || fs->wp == NULL)
+ return (0);
+
+ if (!session_alive(fs->s))
+ return (0);
+
+ RB_FOREACH(wl, winlinks, &fs->s->windows) {
+ if (wl->window == fs->w && wl == fs->wl)
+ break;
+ }
+ if (wl == NULL)
+ return (0);
+
+ if (fs->w != fs->wl->window)
+ return (0);
+
+ return (window_has_pane(fs->w, fs->wp));
+}
+
+/* Copy a state. */
+void
+cmd_find_copy_state(struct cmd_find_state *dst, struct cmd_find_state *src)
+{
+ dst->s = src->s;
+ dst->wl = src->wl;
+ dst->idx = src->idx;
+ dst->w = src->w;
+ dst->wp = src->wp;
+}
+
+/* Log the result. */
+static void
+cmd_find_log_state(const char *prefix, struct cmd_find_state *fs)
+{
+ if (fs->s != NULL)
+ log_debug("%s: s=$%u %s", prefix, fs->s->id, fs->s->name);
+ else
+ log_debug("%s: s=none", prefix);
+ if (fs->wl != NULL) {
+ log_debug("%s: wl=%u %d w=@%u %s", prefix, fs->wl->idx,
+ fs->wl->window == fs->w, fs->w->id, fs->w->name);
+ } else
+ log_debug("%s: wl=none", prefix);
+ if (fs->wp != NULL)
+ log_debug("%s: wp=%%%u", prefix, fs->wp->id);
+ else
+ log_debug("%s: wp=none", prefix);
+ if (fs->idx != -1)
+ log_debug("%s: idx=%d", prefix, fs->idx);
+ else
+ log_debug("%s: idx=none", prefix);
+}
+
+/* Find state from a session. */
+void
+cmd_find_from_session(struct cmd_find_state *fs, struct session *s, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ fs->s = s;
+ fs->wl = fs->s->curw;
+ fs->w = fs->wl->window;
+ fs->wp = fs->w->active;
+
+ cmd_find_log_state(__func__, fs);
+}
+
+/* Find state from a winlink. */
+void
+cmd_find_from_winlink(struct cmd_find_state *fs, struct winlink *wl, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ fs->s = wl->session;
+ fs->wl = wl;
+ fs->w = wl->window;
+ fs->wp = wl->window->active;
+
+ cmd_find_log_state(__func__, fs);
+}
+
+/* Find state from a session and window. */
+int
+cmd_find_from_session_window(struct cmd_find_state *fs, struct session *s,
+ struct window *w, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ fs->s = s;
+ fs->w = w;
+ if (cmd_find_best_winlink_with_window(fs) != 0) {
+ cmd_find_clear_state(fs, flags);
+ return (-1);
+ }
+ fs->wp = fs->w->active;
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+}
+
+/* Find state from a window. */
+int
+cmd_find_from_window(struct cmd_find_state *fs, struct window *w, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ fs->w = w;
+ if (cmd_find_best_session_with_window(fs) != 0) {
+ cmd_find_clear_state(fs, flags);
+ return (-1);
+ }
+ if (cmd_find_best_winlink_with_window(fs) != 0) {
+ cmd_find_clear_state(fs, flags);
+ return (-1);
+ }
+ fs->wp = fs->w->active;
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+}
+
+/* Find state from a winlink and pane. */
+void
+cmd_find_from_winlink_pane(struct cmd_find_state *fs, struct winlink *wl,
+ struct window_pane *wp, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ fs->s = wl->session;
+ fs->wl = wl;
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ fs->wp = wp;
+
+ cmd_find_log_state(__func__, fs);
+}
+
+/* Find state from a pane. */
+int
+cmd_find_from_pane(struct cmd_find_state *fs, struct window_pane *wp, int flags)
+{
+ if (cmd_find_from_window(fs, wp->window, flags) != 0)
+ return (-1);
+ fs->wp = wp;
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+}
+
+/* Find state from nothing. */
+int
+cmd_find_from_nothing(struct cmd_find_state *fs, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ fs->s = cmd_find_best_session(NULL, 0, flags);
+ if (fs->s == NULL) {
+ cmd_find_clear_state(fs, flags);
+ return (-1);
+ }
+ fs->wl = fs->s->curw;
+ fs->idx = fs->wl->idx;
+ fs->w = fs->wl->window;
+ fs->wp = fs->w->active;
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+}
+
+/* Find state from mouse. */
+int
+cmd_find_from_mouse(struct cmd_find_state *fs, struct mouse_event *m, int flags)
+{
+ cmd_find_clear_state(fs, flags);
+
+ if (!m->valid)
+ return (-1);
+
+ fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
+ if (fs->wp == NULL) {
+ cmd_find_clear_state(fs, flags);
+ return (-1);
+ }
+ fs->w = fs->wl->window;
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+}
+
+/* Find state from client. */
+int
+cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags)
+{
+ struct window_pane *wp;
+
+ /* If no client, treat as from nothing. */
+ if (c == NULL)
+ return (cmd_find_from_nothing(fs, flags));
+
+ /* If this is an attached client, all done. */
+ if (c->session != NULL) {
+ cmd_find_clear_state(fs, flags);
+
+ fs->wp = server_client_get_pane(c);
+ if (fs->wp == NULL) {
+ cmd_find_from_session(fs, c->session, flags);
+ return (0);
+ }
+ fs->s = c->session;
+ fs->wl = fs->s->curw;
+ fs->w = fs->wl->window;
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+ }
+ cmd_find_clear_state(fs, flags);
+
+ /*
+ * If this is an unattached client running in a pane, we can use that
+ * to limit the list of sessions to those containing that pane.
+ */
+ wp = cmd_find_inside_pane(c);
+ if (wp == NULL)
+ goto unknown_pane;
+
+ /*
+ * Don't have a session, or it doesn't have this pane. Try all
+ * sessions.
+ */
+ fs->w = wp->window;
+ if (cmd_find_best_session_with_window(fs) != 0) {
+ /*
+ * The window may have been destroyed but the pane
+ * still on all_window_panes due to something else
+ * holding a reference.
+ */
+ goto unknown_pane;
+ }
+ fs->wl = fs->s->curw;
+ fs->w = fs->wl->window;
+ fs->wp = fs->w->active; /* use active pane */
+
+ cmd_find_log_state(__func__, fs);
+ return (0);
+
+unknown_pane:
+ /* We can't find the pane so need to guess. */
+ return (cmd_find_from_nothing(fs, flags));
+}
+
+/*
+ * Split target into pieces and resolve for the given type. Fills in the given
+ * state. Returns 0 on success or -1 on error.
+ */
+int
+cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
+ const char *target, enum cmd_find_type type, int flags)
+{
+ struct mouse_event *m;
+ struct cmd_find_state current;
+ char *colon, *period, *copy = NULL, tmp[256];
+ const char *session, *window, *pane, *s;
+ int window_only = 0, pane_only = 0;
+
+ /* Can fail flag implies quiet. */
+ if (flags & CMD_FIND_CANFAIL)
+ flags |= CMD_FIND_QUIET;
+
+ /* Log the arguments. */
+ if (type == CMD_FIND_PANE)
+ s = "pane";
+ else if (type == CMD_FIND_WINDOW)
+ s = "window";
+ else if (type == CMD_FIND_SESSION)
+ s = "session";
+ else
+ s = "unknown";
+ *tmp = '\0';
+ if (flags & CMD_FIND_PREFER_UNATTACHED)
+ strlcat(tmp, "PREFER_UNATTACHED,", sizeof tmp);
+ if (flags & CMD_FIND_QUIET)
+ strlcat(tmp, "QUIET,", sizeof tmp);
+ if (flags & CMD_FIND_WINDOW_INDEX)
+ strlcat(tmp, "WINDOW_INDEX,", sizeof tmp);
+ if (flags & CMD_FIND_DEFAULT_MARKED)
+ strlcat(tmp, "DEFAULT_MARKED,", sizeof tmp);
+ if (flags & CMD_FIND_EXACT_SESSION)
+ strlcat(tmp, "EXACT_SESSION,", sizeof tmp);
+ if (flags & CMD_FIND_EXACT_WINDOW)
+ strlcat(tmp, "EXACT_WINDOW,", sizeof tmp);
+ if (flags & CMD_FIND_CANFAIL)
+ strlcat(tmp, "CANFAIL,", sizeof tmp);
+ if (*tmp != '\0')
+ tmp[strlen(tmp) - 1] = '\0';
+ else
+ strlcat(tmp, "NONE", sizeof tmp);
+ log_debug("%s: target %s, type %s, item %p, flags %s", __func__,
+ target == NULL ? "none" : target, s, item, tmp);
+
+ /* Clear new state. */
+ cmd_find_clear_state(fs, flags);
+
+ /* Find current state. */
+ if (server_check_marked() && (flags & CMD_FIND_DEFAULT_MARKED)) {
+ fs->current = &marked_pane;
+ log_debug("%s: current is marked pane", __func__);
+ } else if (cmd_find_valid_state(cmdq_get_current(item))) {
+ fs->current = cmdq_get_current(item);
+ log_debug("%s: current is from queue", __func__);
+ } else if (cmd_find_from_client(&current, cmdq_get_client(item),
+ flags) == 0) {
+ fs->current = &current;
+ log_debug("%s: current is from client", __func__);
+ } else {
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "no current target");
+ goto error;
+ }
+ if (!cmd_find_valid_state(fs->current))
+ fatalx("invalid current find state");
+
+ /* An empty or NULL target is the current. */
+ if (target == NULL || *target == '\0')
+ goto current;
+
+ /* Mouse target is a plain = or {mouse}. */
+ if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
+ m = &cmdq_get_event(item)->m;
+ switch (type) {
+ case CMD_FIND_PANE:
+ fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
+ if (fs->wp != NULL) {
+ fs->w = fs->wl->window;
+ break;
+ }
+ /* FALLTHROUGH */
+ case CMD_FIND_WINDOW:
+ case CMD_FIND_SESSION:
+ fs->wl = cmd_mouse_window(m, &fs->s);
+ if (fs->wl == NULL && fs->s != NULL)
+ fs->wl = fs->s->curw;
+ if (fs->wl != NULL) {
+ fs->w = fs->wl->window;
+ fs->wp = fs->w->active;
+ }
+ break;
+ }
+ if (fs->wp == NULL) {
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "no mouse target");
+ goto error;
+ }
+ goto found;
+ }
+
+ /* Marked target is a plain ~ or {marked}. */
+ if (strcmp(target, "~") == 0 || strcmp(target, "{marked}") == 0) {
+ if (!server_check_marked()) {
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "no marked target");
+ goto error;
+ }
+ cmd_find_copy_state(fs, &marked_pane);
+ goto found;
+ }
+
+ /* Find separators if they exist. */
+ copy = xstrdup(target);
+ colon = strchr(copy, ':');
+ if (colon != NULL)
+ *colon++ = '\0';
+ if (colon == NULL)
+ period = strchr(copy, '.');
+ else
+ period = strchr(colon, '.');
+ if (period != NULL)
+ *period++ = '\0';
+
+ /* Set session, window and pane parts. */
+ session = window = pane = NULL;
+ if (colon != NULL && period != NULL) {
+ session = copy;
+ window = colon;
+ window_only = 1;
+ pane = period;
+ pane_only = 1;
+ } else if (colon != NULL && period == NULL) {
+ session = copy;
+ window = colon;
+ window_only = 1;
+ } else if (colon == NULL && period != NULL) {
+ window = copy;
+ pane = period;
+ pane_only = 1;
+ } else {
+ if (*copy == '$')
+ session = copy;
+ else if (*copy == '@')
+ window = copy;
+ else if (*copy == '%')
+ pane = copy;
+ else {
+ switch (type) {
+ case CMD_FIND_SESSION:
+ session = copy;
+ break;
+ case CMD_FIND_WINDOW:
+ window = copy;
+ break;
+ case CMD_FIND_PANE:
+ pane = copy;
+ break;
+ }
+ }
+ }
+
+ /* Set exact match flags. */
+ if (session != NULL && *session == '=') {
+ session++;
+ fs->flags |= CMD_FIND_EXACT_SESSION;
+ }
+ if (window != NULL && *window == '=') {
+ window++;
+ fs->flags |= CMD_FIND_EXACT_WINDOW;
+ }
+
+ /* Empty is the same as NULL. */
+ if (session != NULL && *session == '\0')
+ session = NULL;
+ if (window != NULL && *window == '\0')
+ window = NULL;
+ if (pane != NULL && *pane == '\0')
+ pane = NULL;
+
+ /* Map though conversion table. */
+ if (session != NULL)
+ session = cmd_find_map_table(cmd_find_session_table, session);
+ if (window != NULL)
+ window = cmd_find_map_table(cmd_find_window_table, window);
+ if (pane != NULL)
+ pane = cmd_find_map_table(cmd_find_pane_table, pane);
+
+ if (session != NULL || window != NULL || pane != NULL) {
+ log_debug("%s: target %s is %s%s%s%s%s%s",
+ __func__, target,
+ session == NULL ? "" : "session ",
+ session == NULL ? "" : session,
+ window == NULL ? "" : "window ",
+ window == NULL ? "" : window,
+ pane == NULL ? "" : "pane ",
+ pane == NULL ? "" : pane);
+ }
+
+ /* No pane is allowed if want an index. */
+ if (pane != NULL && (flags & CMD_FIND_WINDOW_INDEX)) {
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "can't specify pane here");
+ goto error;
+ }
+
+ /* If the session isn't NULL, look it up. */
+ if (session != NULL) {
+ /* This will fill in session. */
+ if (cmd_find_get_session(fs, session) != 0)
+ goto no_session;
+
+ /* If window and pane are NULL, use that session's current. */
+ if (window == NULL && pane == NULL) {
+ fs->wl = fs->s->curw;
+ fs->idx = -1;
+ fs->w = fs->wl->window;
+ fs->wp = fs->w->active;
+ goto found;
+ }
+
+ /* If window is present but pane not, find window in session. */
+ if (window != NULL && pane == NULL) {
+ /* This will fill in winlink and window. */
+ if (cmd_find_get_window_with_session(fs, window) != 0)
+ goto no_window;
+ if (fs->wl != NULL) /* can be NULL if index only */
+ fs->wp = fs->wl->window->active;
+ goto found;
+ }
+
+ /* If pane is present but window not, find pane. */
+ if (window == NULL && pane != NULL) {
+ /* This will fill in winlink and window and pane. */
+ if (cmd_find_get_pane_with_session(fs, pane) != 0)
+ goto no_pane;
+ goto found;
+ }
+
+ /*
+ * If window and pane are present, find both in session. This
+ * will fill in winlink and window.
+ */
+ if (cmd_find_get_window_with_session(fs, window) != 0)
+ goto no_window;
+ /* This will fill in pane. */
+ if (cmd_find_get_pane_with_window(fs, pane) != 0)
+ goto no_pane;
+ goto found;
+ }
+
+ /* No session. If window and pane, try them. */
+ if (window != NULL && pane != NULL) {
+ /* This will fill in session, winlink and window. */
+ if (cmd_find_get_window(fs, window, window_only) != 0)
+ goto no_window;
+ /* This will fill in pane. */
+ if (cmd_find_get_pane_with_window(fs, pane) != 0)
+ goto no_pane;
+ goto found;
+ }
+
+ /* If just window is present, try it. */
+ if (window != NULL && pane == NULL) {
+ /* This will fill in session, winlink and window. */
+ if (cmd_find_get_window(fs, window, window_only) != 0)
+ goto no_window;
+ if (fs->wl != NULL) /* can be NULL if index only */
+ fs->wp = fs->wl->window->active;
+ goto found;
+ }
+
+ /* If just pane is present, try it. */
+ if (window == NULL && pane != NULL) {
+ /* This will fill in session, winlink, window and pane. */
+ if (cmd_find_get_pane(fs, pane, pane_only) != 0)
+ goto no_pane;
+ goto found;
+ }
+
+current:
+ /* Use the current session. */
+ cmd_find_copy_state(fs, fs->current);
+ if (flags & CMD_FIND_WINDOW_INDEX)
+ fs->idx = -1;
+ goto found;
+
+error:
+ fs->current = NULL;
+ log_debug("%s: error", __func__);
+
+ free(copy);
+ if (flags & CMD_FIND_CANFAIL)
+ return (0);
+ return (-1);
+
+found:
+ fs->current = NULL;
+ cmd_find_log_state(__func__, fs);
+
+ free(copy);
+ return (0);
+
+no_session:
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "can't find session: %s", session);
+ goto error;
+
+no_window:
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "can't find window: %s", window);
+ goto error;
+
+no_pane:
+ if (~flags & CMD_FIND_QUIET)
+ cmdq_error(item, "can't find pane: %s", pane);
+ goto error;
+}
+
+/* Find the current client. */
+static struct client *
+cmd_find_current_client(struct cmdq_item *item, int quiet)
+{
+ struct client *c = NULL, *found;
+ struct session *s;
+ struct window_pane *wp;
+ struct cmd_find_state fs;
+
+ if (item != NULL)
+ c = cmdq_get_client(item);
+ if (c != NULL && c->session != NULL)
+ return (c);
+
+ found = NULL;
+ if (c != NULL && (wp = cmd_find_inside_pane(c)) != NULL) {
+ cmd_find_clear_state(&fs, CMD_FIND_QUIET);
+ fs.w = wp->window;
+ if (cmd_find_best_session_with_window(&fs) == 0)
+ found = cmd_find_best_client(fs.s);
+ } else {
+ s = cmd_find_best_session(NULL, 0, CMD_FIND_QUIET);
+ if (s != NULL)
+ found = cmd_find_best_client(s);
+ }
+ if (found == NULL && item != NULL && !quiet)
+ cmdq_error(item, "no current client");
+ log_debug("%s: no target, return %p", __func__, found);
+ return (found);
+}
+
+/* Find the target client or report an error and return NULL. */
+struct client *
+cmd_find_client(struct cmdq_item *item, const char *target, int quiet)
+{
+ struct client *c;
+ char *copy;
+ size_t size;
+
+ /* A NULL argument means the current client. */
+ if (target == NULL)
+ return (cmd_find_current_client(item, quiet));
+ copy = xstrdup(target);
+
+ /* Trim a single trailing colon if any. */
+ size = strlen(copy);
+ if (size != 0 && copy[size - 1] == ':')
+ copy[size - 1] = '\0';
+
+ /* Check name and path of each client. */
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == NULL)
+ continue;
+ if (strcmp(copy, c->name) == 0)
+ break;
+
+ if (*c->ttyname == '\0')
+ continue;
+ if (strcmp(copy, c->ttyname) == 0)
+ break;
+ if (strncmp(c->ttyname, _PATH_DEV, (sizeof _PATH_DEV) - 1) != 0)
+ continue;
+ if (strcmp(copy, c->ttyname + (sizeof _PATH_DEV) - 1) == 0)
+ break;
+ }
+
+ /* If no client found, report an error. */
+ if (c == NULL && !quiet)
+ cmdq_error(item, "can't find client: %s", copy);
+
+ free(copy);
+ log_debug("%s: target %s, return %p", __func__, target, c);
+ return (c);
+}
diff --git a/cmd-if-shell.c b/cmd-if-shell.c
new file mode 100644
index 0000000..205a8ce
--- /dev/null
+++ b/cmd-if-shell.c
@@ -0,0 +1,190 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
+ * Copyright (c) 2009 Nicholas Marriott <nicm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Executes a tmux command if a shell command returns true or false.
+ */
+
+static enum args_parse_type cmd_if_shell_args_parse(struct args *, u_int,
+ char **);
+static enum cmd_retval cmd_if_shell_exec(struct cmd *,
+ struct cmdq_item *);
+
+static void cmd_if_shell_callback(struct job *);
+static void cmd_if_shell_free(void *);
+
+const struct cmd_entry cmd_if_shell_entry = {
+ .name = "if-shell",
+ .alias = "if",
+
+ .args = { "bFt:", 2, 3, cmd_if_shell_args_parse },
+ .usage = "[-bF] " CMD_TARGET_PANE_USAGE " shell-command command "
+ "[command]",
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = 0,
+ .exec = cmd_if_shell_exec
+};
+
+struct cmd_if_shell_data {
+ struct args_command_state *cmd_if;
+ struct args_command_state *cmd_else;
+
+ struct client *client;
+ struct cmdq_item *item;
+};
+
+static enum args_parse_type
+cmd_if_shell_args_parse(__unused struct args *args, u_int idx,
+ __unused char **cause)
+{
+ if (idx == 1 || idx == 2)
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+ return (ARGS_PARSE_STRING);
+}
+
+static enum cmd_retval
+cmd_if_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_if_shell_data *cdata;
+ struct cmdq_item *new_item;
+ char *shellcmd;
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct cmd_list *cmdlist;
+ u_int count = args_count(args);
+ int wait = !args_has(args, 'b');
+
+ shellcmd = format_single_from_target(item, args_string(args, 0));
+ if (args_has(args, 'F')) {
+ if (*shellcmd != '0' && *shellcmd != '\0')
+ cmdlist = args_make_commands_now(self, item, 1, 0);
+ else if (count == 3)
+ cmdlist = args_make_commands_now(self, item, 2, 0);
+ else {
+ free(shellcmd);
+ return (CMD_RETURN_NORMAL);
+ }
+ free(shellcmd);
+ if (cmdlist == NULL)
+ return (CMD_RETURN_ERROR);
+ new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
+ cmdq_insert_after(item, new_item);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ cdata = xcalloc(1, sizeof *cdata);
+
+ cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait,
+ 0);
+ if (count == 3) {
+ cdata->cmd_else = args_make_commands_prepare(self, item, 2,
+ NULL, wait, 0);
+ }
+
+ if (wait) {
+ cdata->client = cmdq_get_client(item);
+ cdata->item = item;
+ } else
+ cdata->client = tc;
+ if (cdata->client != NULL)
+ cdata->client->references++;
+
+ if (job_run(shellcmd, 0, NULL, NULL, s,
+ server_client_get_cwd(cmdq_get_client(item), s), NULL,
+ cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
+ -1) == NULL) {
+ cmdq_error(item, "failed to run command: %s", shellcmd);
+ free(shellcmd);
+ free(cdata);
+ return (CMD_RETURN_ERROR);
+ }
+ free(shellcmd);
+
+ if (!wait)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
+
+static void
+cmd_if_shell_callback(struct job *job)
+{
+ struct cmd_if_shell_data *cdata = job_get_data(job);
+ struct client *c = cdata->client;
+ struct cmdq_item *item = cdata->item, *new_item;
+ struct args_command_state *state;
+ struct cmd_list *cmdlist;
+ char *error;
+ int status;
+
+ status = job_get_status(job);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ state = cdata->cmd_else;
+ else
+ state = cdata->cmd_if;
+ if (state == NULL)
+ goto out;
+
+ cmdlist = args_make_commands(state, 0, NULL, &error);
+ if (cmdlist == NULL) {
+ if (cdata->item == NULL) {
+ *error = toupper((u_char)*error);
+ status_message_set(c, -1, 1, 0, "%s", error);
+ } else
+ cmdq_error(cdata->item, "%s", error);
+ free(error);
+ } else if (item == NULL) {
+ new_item = cmdq_get_command(cmdlist, NULL);
+ cmdq_append(c, new_item);
+ } else {
+ new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
+ cmdq_insert_after(item, new_item);
+ }
+
+out:
+ if (cdata->item != NULL)
+ cmdq_continue(cdata->item);
+}
+
+static void
+cmd_if_shell_free(void *data)
+{
+ struct cmd_if_shell_data *cdata = data;
+
+ if (cdata->client != NULL)
+ server_client_unref(cdata->client);
+
+ if (cdata->cmd_else != NULL)
+ args_make_commands_free(cdata->cmd_else);
+ args_make_commands_free(cdata->cmd_if);
+
+ free(cdata);
+}
diff --git a/cmd-join-pane.c b/cmd-join-pane.c
new file mode 100644
index 0000000..cb3fb34
--- /dev/null
+++ b/cmd-join-pane.c
@@ -0,0 +1,171 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2011 George Nachman <tmux@georgester.com>
+ * Copyright (c) 2009 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 <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Join or move a pane into another (like split/swap/kill).
+ */
+
+static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_join_pane_entry = {
+ .name = "join-pane",
+ .alias = "joinp",
+
+ .args = { "bdfhvp:l:s:t:", 0, 0, NULL },
+ .usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE,
+
+ .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED },
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_join_pane_exec
+};
+
+const struct cmd_entry cmd_move_pane_entry = {
+ .name = "move-pane",
+ .alias = "movep",
+
+ .args = { "bdfhvp:l:s:t:", 0, 0, NULL },
+ .usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE,
+
+ .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED },
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_join_pane_exec
+};
+
+static enum cmd_retval
+cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct session *dst_s;
+ struct winlink *src_wl, *dst_wl;
+ struct window *src_w, *dst_w;
+ struct window_pane *src_wp, *dst_wp;
+ char *cause = NULL;
+ int size, percentage, dst_idx;
+ int flags;
+ enum layout_type type;
+ struct layout_cell *lc;
+
+ dst_s = target->s;
+ dst_wl = target->wl;
+ dst_wp = target->wp;
+ dst_w = dst_wl->window;
+ dst_idx = dst_wl->idx;
+ server_unzoom_window(dst_w);
+
+ src_wl = source->wl;
+ src_wp = source->wp;
+ src_w = src_wl->window;
+ server_unzoom_window(src_w);
+
+ if (src_wp == dst_wp) {
+ cmdq_error(item, "source and target panes must be different");
+ return (CMD_RETURN_ERROR);
+ }
+
+ type = LAYOUT_TOPBOTTOM;
+ 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);
+ } 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;
+ else
+ size = (dst_wp->sx * percentage) / 100;
+ }
+ }
+ if (cause != NULL) {
+ cmdq_error(item, "size %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+
+ flags = 0;
+ if (args_has(args, 'b'))
+ flags |= SPAWN_BEFORE;
+ if (args_has(args, 'f'))
+ flags |= SPAWN_FULLSIZE;
+
+ lc = layout_split_pane(dst_wp, type, size, flags);
+ if (lc == NULL) {
+ cmdq_error(item, "create pane failed: pane too small");
+ return (CMD_RETURN_ERROR);
+ }
+
+ layout_close_pane(src_wp);
+
+ server_client_remove_pane(src_wp);
+ window_lost_pane(src_w, src_wp);
+ TAILQ_REMOVE(&src_w->panes, src_wp, entry);
+
+ src_wp->window = dst_w;
+ options_set_parent(src_wp->options, dst_w->options);
+ src_wp->flags |= PANE_STYLECHANGED;
+ if (flags & SPAWN_BEFORE)
+ TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry);
+ else
+ TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry);
+ layout_assign_pane(lc, src_wp, 0);
+
+ recalculate_sizes();
+
+ server_redraw_window(src_w);
+ server_redraw_window(dst_w);
+
+ if (!args_has(args, 'd')) {
+ window_set_active_pane(dst_w, src_wp, 1);
+ session_select(dst_s, dst_idx);
+ cmd_find_from_session(current, dst_s, 0);
+ server_redraw_session(dst_s);
+ } else
+ server_status_session(dst_s);
+
+ if (window_count_panes(src_w) == 0)
+ server_kill_window(src_w, 1);
+ else
+ notify_window("window-layout-changed", src_w);
+ notify_window("window-layout-changed", dst_w);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c
new file mode 100644
index 0000000..e1134a1
--- /dev/null
+++ b/cmd-kill-pane.c
@@ -0,0 +1,67 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * Kill pane.
+ */
+
+static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_kill_pane_entry = {
+ .name = "kill-pane",
+ .alias = "killp",
+
+ .args = { "at:", 0, 0, NULL },
+ .usage = "[-a] " CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_kill_pane_exec
+};
+
+static enum cmd_retval
+cmd_kill_pane_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 winlink *wl = target->wl;
+ struct window_pane *loopwp, *tmpwp, *wp = target->wp;
+
+ if (args_has(args, 'a')) {
+ server_unzoom_window(wl->window);
+ TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
+ if (loopwp == wp)
+ continue;
+ server_client_remove_pane(loopwp);
+ layout_close_pane(loopwp);
+ window_remove_pane(wl->window, loopwp);
+ }
+ server_redraw_window(wl->window);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ server_kill_pane(wp);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-kill-server.c b/cmd-kill-server.c
new file mode 100644
index 0000000..7bb79e3
--- /dev/null
+++ b/cmd-kill-server.c
@@ -0,0 +1,61 @@
+/* $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 <signal.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Kill the server and do nothing else.
+ */
+
+static enum cmd_retval cmd_kill_server_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_kill_server_entry = {
+ .name = "kill-server",
+ .alias = NULL,
+
+ .args = { "", 0, 0, NULL },
+ .usage = "",
+
+ .flags = 0,
+ .exec = cmd_kill_server_exec
+};
+
+const struct cmd_entry cmd_start_server_entry = {
+ .name = "start-server",
+ .alias = "start",
+
+ .args = { "", 0, 0, NULL },
+ .usage = "",
+
+ .flags = CMD_STARTSERVER,
+ .exec = cmd_kill_server_exec
+};
+
+static enum cmd_retval
+cmd_kill_server_exec(struct cmd *self, __unused struct cmdq_item *item)
+{
+ if (cmd_get_entry(self) == &cmd_kill_server_entry)
+ kill(getpid(), SIGTERM);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-kill-session.c b/cmd-kill-session.c
new file mode 100644
index 0000000..19a8d49
--- /dev/null
+++ b/cmd-kill-session.c
@@ -0,0 +1,71 @@
+/* $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 "tmux.h"
+
+/*
+ * Destroy session, detaching all clients attached to it and destroying any
+ * windows linked only to this session.
+ *
+ * Note this deliberately has no alias to make it hard to hit by accident.
+ */
+
+static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_kill_session_entry = {
+ .name = "kill-session",
+ .alias = NULL,
+
+ .args = { "aCt:", 0, 0, NULL },
+ .usage = "[-aC] " CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = 0,
+ .exec = cmd_kill_session_exec
+};
+
+static enum cmd_retval
+cmd_kill_session_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 session *s = target->s, *sloop, *stmp;
+ struct winlink *wl;
+
+ if (args_has(args, 'C')) {
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ wl->window->flags &= ~WINDOW_ALERTFLAGS;
+ wl->flags &= ~WINLINK_ALERTFLAGS;
+ }
+ server_redraw_session(s);
+ } else if (args_has(args, 'a')) {
+ RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
+ if (sloop != s) {
+ server_destroy_session(sloop);
+ session_destroy(sloop, 1, __func__);
+ }
+ }
+ } else {
+ server_destroy_session(s);
+ session_destroy(s, 1, __func__);
+ }
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-kill-window.c b/cmd-kill-window.c
new file mode 100644
index 0000000..f5ff05f
--- /dev/null
+++ b/cmd-kill-window.c
@@ -0,0 +1,110 @@
+/* $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 "tmux.h"
+
+/*
+ * Destroy window.
+ */
+
+static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_kill_window_entry = {
+ .name = "kill-window",
+ .alias = "killw",
+
+ .args = { "at:", 0, 0, NULL },
+ .usage = "[-a] " CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_kill_window_exec
+};
+
+const struct cmd_entry cmd_unlink_window_entry = {
+ .name = "unlink-window",
+ .alias = "unlinkw",
+
+ .args = { "kt:", 0, 0, NULL },
+ .usage = "[-k] " CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_kill_window_exec
+};
+
+static enum cmd_retval
+cmd_kill_window_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 winlink *wl = target->wl, *loop;
+ struct window *w = wl->window;
+ struct session *s = target->s;
+ u_int found;
+
+ if (cmd_get_entry(self) == &cmd_unlink_window_entry) {
+ if (!args_has(args, 'k') && !session_is_linked(s, w)) {
+ cmdq_error(item, "window only linked to one session");
+ return (CMD_RETURN_ERROR);
+ }
+ server_unlink_window(s, wl);
+ recalculate_sizes();
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'a')) {
+ if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
+ RB_NEXT(winlinks, &s->windows, wl) == NULL)
+ return (CMD_RETURN_NORMAL);
+
+ /* Kill all windows except the current one. */
+ do {
+ found = 0;
+ RB_FOREACH(loop, winlinks, &s->windows) {
+ if (loop->window != wl->window) {
+ server_kill_window(loop->window, 0);
+ found++;
+ break;
+ }
+ }
+ } while (found != 0);
+
+ /*
+ * If the current window appears in the session more than once,
+ * kill it as well.
+ */
+ found = 0;
+ RB_FOREACH(loop, winlinks, &s->windows) {
+ if (loop->window == wl->window)
+ found++;
+ }
+ if (found > 1)
+ server_kill_window(wl->window, 0);
+
+ server_renumber_all();
+ return (CMD_RETURN_NORMAL);
+ }
+
+ server_kill_window(wl->window, 1);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c
new file mode 100644
index 0000000..8b12f0b
--- /dev/null
+++ b/cmd-list-buffers.c
@@ -0,0 +1,81 @@
+/* $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"
+
+/*
+ * List paste buffers.
+ */
+
+#define LIST_BUFFERS_TEMPLATE \
+ "#{buffer_name}: #{buffer_size} bytes: \"#{buffer_sample}\""
+
+static enum cmd_retval cmd_list_buffers_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_list_buffers_entry = {
+ .name = "list-buffers",
+ .alias = "lsb",
+
+ .args = { "F:f:", 0, 0, NULL },
+ .usage = "[-F format] [-f filter]",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_list_buffers_exec
+};
+
+static enum cmd_retval
+cmd_list_buffers_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct paste_buffer *pb;
+ struct format_tree *ft;
+ const char *template, *filter;
+ char *line, *expanded;
+ int flag;
+
+ if ((template = args_get(args, 'F')) == NULL)
+ template = LIST_BUFFERS_TEMPLATE;
+ filter = args_get(args, 'f');
+
+ pb = NULL;
+ while ((pb = paste_walk(pb)) != NULL) {
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ format_defaults_paste_buffer(ft, pb);
+
+ 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);
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-list-clients.c b/cmd-list-clients.c
new file mode 100644
index 0000000..53a9917
--- /dev/null
+++ b/cmd-list-clients.c
@@ -0,0 +1,92 @@
+/* $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 <time.h>
+
+#include "tmux.h"
+
+/*
+ * List all clients.
+ */
+
+#define LIST_CLIENTS_TEMPLATE \
+ "#{client_name}: #{session_name} " \
+ "[#{client_width}x#{client_height} #{client_termname}] " \
+ "#{?#{!=:#{client_uid},#{uid}}," \
+ "[user #{?client_user,#{client_user},#{client_uid},}] ,}" \
+ "#{?client_flags,(,}#{client_flags}#{?client_flags,),}"
+
+static enum cmd_retval cmd_list_clients_exec(struct cmd *, struct cmdq_item *);
+
+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,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = CMD_READONLY|CMD_AFTERHOOK,
+ .exec = cmd_list_clients_exec
+};
+
+static enum cmd_retval
+cmd_list_clients_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 client *c;
+ struct session *s;
+ struct format_tree *ft;
+ const char *template;
+ u_int idx;
+ char *line;
+
+ if (args_has(args, 't'))
+ s = target->s;
+ else
+ s = NULL;
+
+ if ((template = args_get(args, 'F')) == NULL)
+ template = LIST_CLIENTS_TEMPLATE;
+
+ idx = 0;
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == NULL || (s != NULL && s != c->session))
+ continue;
+
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ 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);
+
+ format_free(ft);
+
+ idx++;
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-list-keys.c b/cmd-list-keys.c
new file mode 100644
index 0000000..ae9f995
--- /dev/null
+++ b/cmd-list-keys.c
@@ -0,0 +1,365 @@
+/* $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"
+
+/*
+ * List key bindings.
+ */
+
+static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
+
+static enum cmd_retval cmd_list_keys_commands(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_list_keys_entry = {
+ .name = "list-keys",
+ .alias = "lsk",
+
+ .args = { "1aNP:T:", 0, 1, NULL },
+ .usage = "[-1aN] [-P prefix-string] [-T key-table] [key]",
+
+ .flags = CMD_STARTSERVER|CMD_AFTERHOOK,
+ .exec = cmd_list_keys_exec
+};
+
+const struct cmd_entry cmd_list_commands_entry = {
+ .name = "list-commands",
+ .alias = "lscm",
+
+ .args = { "F:", 0, 1, NULL },
+ .usage = "[-F format] [command]",
+
+ .flags = CMD_STARTSERVER|CMD_AFTERHOOK,
+ .exec = cmd_list_keys_exec
+};
+
+static u_int
+cmd_list_keys_get_width(const char *tablename, key_code only)
+{
+ struct key_table *table;
+ struct key_binding *bd;
+ u_int width, keywidth = 0;
+
+ table = key_bindings_get_table(tablename, 0);
+ if (table == NULL)
+ return (0);
+ bd = key_bindings_first(table);
+ while (bd != NULL) {
+ if ((only != KEYC_UNKNOWN && bd->key != only) ||
+ KEYC_IS_MOUSE(bd->key) ||
+ bd->note == NULL ||
+ *bd->note == '\0') {
+ bd = key_bindings_next(table, bd);
+ continue;
+ }
+ width = utf8_cstrwidth(key_string_lookup_key(bd->key, 0));
+ if (width > keywidth)
+ keywidth = width;
+
+ bd = key_bindings_next(table, bd);
+ }
+ return (keywidth);
+}
+
+static int
+cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args,
+ const char *tablename, u_int keywidth, key_code only, const char *prefix)
+{
+ struct client *tc = cmdq_get_target_client(item);
+ struct key_table *table;
+ struct key_binding *bd;
+ const char *key;
+ char *tmp, *note;
+ int found = 0;
+
+ table = key_bindings_get_table(tablename, 0);
+ if (table == NULL)
+ return (0);
+ bd = key_bindings_first(table);
+ while (bd != NULL) {
+ if ((only != KEYC_UNKNOWN && bd->key != only) ||
+ KEYC_IS_MOUSE(bd->key) ||
+ ((bd->note == NULL || *bd->note == '\0') &&
+ !args_has(args, 'a'))) {
+ bd = key_bindings_next(table, bd);
+ continue;
+ }
+ found = 1;
+ key = key_string_lookup_key(bd->key, 0);
+
+ if (bd->note == NULL || *bd->note == '\0')
+ note = cmd_list_print(bd->cmdlist, 1);
+ else
+ note = xstrdup(bd->note);
+ tmp = utf8_padcstr(key, keywidth + 1);
+ if (args_has(args, '1') && tc != NULL) {
+ status_message_set(tc, -1, 1, 0, "%s%s%s", prefix, tmp,
+ note);
+ } else
+ cmdq_print(item, "%s%s%s", prefix, tmp, note);
+ free(tmp);
+ free(note);
+
+ if (args_has(args, '1'))
+ break;
+ bd = key_bindings_next(table, bd);
+ }
+ return (found);
+}
+
+static char *
+cmd_list_keys_get_prefix(struct args *args, key_code *prefix)
+{
+ char *s;
+
+ *prefix = options_get_number(global_s_options, "prefix");
+ if (!args_has(args, 'P')) {
+ if (*prefix != KEYC_NONE)
+ xasprintf(&s, "%s ", key_string_lookup_key(*prefix, 0));
+ else
+ s = xstrdup("");
+ } else
+ s = xstrdup(args_get(args, 'P'));
+ return (s);
+}
+
+static enum cmd_retval
+cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct key_table *table;
+ struct key_binding *bd;
+ const char *tablename, *r, *keystr;
+ char *key, *cp, *tmp, *start, *empty;
+ key_code prefix, only = KEYC_UNKNOWN;
+ int repeat, width, tablewidth, keywidth, found = 0;
+ size_t tmpsize, tmpused, cplen;
+
+ if (cmd_get_entry(self) == &cmd_list_commands_entry)
+ return (cmd_list_keys_commands(self, item));
+
+ if ((keystr = args_string(args, 0)) != NULL) {
+ only = key_string_lookup_string(keystr);
+ if (only == KEYC_UNKNOWN) {
+ cmdq_error(item, "invalid key: %s", keystr);
+ return (CMD_RETURN_ERROR);
+ }
+ only &= (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS);
+ }
+
+ tablename = args_get(args, 'T');
+ if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) {
+ cmdq_error(item, "table %s doesn't exist", tablename);
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'N')) {
+ if (tablename == NULL) {
+ start = cmd_list_keys_get_prefix(args, &prefix);
+ keywidth = cmd_list_keys_get_width("root", only);
+ if (prefix != KEYC_NONE) {
+ width = cmd_list_keys_get_width("prefix", only);
+ if (width == 0)
+ prefix = KEYC_NONE;
+ else if (width > keywidth)
+ keywidth = width;
+ }
+ empty = utf8_padcstr("", utf8_cstrwidth(start));
+
+ found = cmd_list_keys_print_notes(item, args, "root",
+ keywidth, only, empty);
+ if (prefix != KEYC_NONE) {
+ if (cmd_list_keys_print_notes(item, args,
+ "prefix", keywidth, only, start))
+ found = 1;
+ }
+ free(empty);
+ } else {
+ if (args_has(args, 'P'))
+ start = xstrdup(args_get(args, 'P'));
+ else
+ start = xstrdup("");
+ keywidth = cmd_list_keys_get_width(tablename, only);
+ found = cmd_list_keys_print_notes(item, args, tablename,
+ keywidth, only, start);
+
+ }
+ free(start);
+ goto out;
+ }
+
+ repeat = 0;
+ tablewidth = keywidth = 0;
+ table = key_bindings_first_table();
+ while (table != NULL) {
+ if (tablename != NULL && strcmp(table->name, tablename) != 0) {
+ table = key_bindings_next_table(table);
+ continue;
+ }
+ bd = key_bindings_first(table);
+ while (bd != NULL) {
+ if (only != KEYC_UNKNOWN && bd->key != only) {
+ bd = key_bindings_next(table, bd);
+ continue;
+ }
+ key = args_escape(key_string_lookup_key(bd->key, 0));
+
+ if (bd->flags & KEY_BINDING_REPEAT)
+ repeat = 1;
+
+ width = utf8_cstrwidth(table->name);
+ if (width > tablewidth)
+ tablewidth = width;
+ width = utf8_cstrwidth(key);
+ if (width > keywidth)
+ keywidth = width;
+
+ free(key);
+ bd = key_bindings_next(table, bd);
+ }
+ table = key_bindings_next_table(table);
+ }
+
+ tmpsize = 256;
+ tmp = xmalloc(tmpsize);
+
+ table = key_bindings_first_table();
+ while (table != NULL) {
+ if (tablename != NULL && strcmp(table->name, tablename) != 0) {
+ table = key_bindings_next_table(table);
+ continue;
+ }
+ bd = key_bindings_first(table);
+ while (bd != NULL) {
+ if (only != KEYC_UNKNOWN && bd->key != only) {
+ bd = key_bindings_next(table, bd);
+ continue;
+ }
+ found = 1;
+ key = args_escape(key_string_lookup_key(bd->key, 0));
+
+ if (!repeat)
+ r = "";
+ else if (bd->flags & KEY_BINDING_REPEAT)
+ r = "-r ";
+ else
+ r = " ";
+ tmpused = xsnprintf(tmp, tmpsize, "%s-T ", r);
+
+ cp = utf8_padcstr(table->name, tablewidth);
+ cplen = strlen(cp) + 1;
+ while (tmpused + cplen + 1 >= tmpsize) {
+ tmpsize *= 2;
+ tmp = xrealloc(tmp, tmpsize);
+ }
+ strlcat(tmp, cp, tmpsize);
+ tmpused = strlcat(tmp, " ", tmpsize);
+ free(cp);
+
+ cp = utf8_padcstr(key, keywidth);
+ cplen = strlen(cp) + 1;
+ while (tmpused + cplen + 1 >= tmpsize) {
+ tmpsize *= 2;
+ tmp = xrealloc(tmp, tmpsize);
+ }
+ strlcat(tmp, cp, tmpsize);
+ tmpused = strlcat(tmp, " ", tmpsize);
+ free(cp);
+
+ cp = cmd_list_print(bd->cmdlist, 1);
+ cplen = strlen(cp);
+ while (tmpused + cplen + 1 >= tmpsize) {
+ tmpsize *= 2;
+ tmp = xrealloc(tmp, tmpsize);
+ }
+ strlcat(tmp, cp, tmpsize);
+ free(cp);
+
+ cmdq_print(item, "bind-key %s", tmp);
+
+ free(key);
+ bd = key_bindings_next(table, bd);
+ }
+ table = key_bindings_next_table(table);
+ }
+
+ free(tmp);
+
+out:
+ if (only != KEYC_UNKNOWN && !found) {
+ cmdq_error(item, "unknown key: %s", args_string(args, 0));
+ return (CMD_RETURN_ERROR);
+ }
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ const struct cmd_entry **entryp;
+ const struct cmd_entry *entry;
+ struct format_tree *ft;
+ const char *template, *s, *command;
+ char *line;
+
+ if ((template = args_get(args, 'F')) == NULL) {
+ template = "#{command_list_name}"
+ "#{?command_list_alias, (#{command_list_alias}),} "
+ "#{command_list_usage}";
+ }
+
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ format_defaults(ft, NULL, NULL, NULL, NULL);
+
+ command = args_string(args, 0);
+ for (entryp = cmd_table; *entryp != NULL; entryp++) {
+ entry = *entryp;
+ if (command != NULL &&
+ (strcmp(entry->name, command) != 0 &&
+ (entry->alias == NULL ||
+ strcmp(entry->alias, command) != 0)))
+ continue;
+
+ format_add(ft, "command_list_name", "%s", entry->name);
+ if (entry->alias != NULL)
+ s = entry->alias;
+ else
+ s = "";
+ format_add(ft, "command_list_alias", "%s", s);
+ if (entry->usage != NULL)
+ s = entry->usage;
+ else
+ s = "";
+ format_add(ft, "command_list_usage", "%s", s);
+
+ line = format_expand(ft, template);
+ if (*line != '\0')
+ cmdq_print(item, "%s", line);
+ free(line);
+ }
+
+ format_free(ft);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-list-panes.c b/cmd-list-panes.c
new file mode 100644
index 0000000..a29a403
--- /dev/null
+++ b/cmd-list-panes.c
@@ -0,0 +1,148 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * List panes on given window.
+ */
+
+static enum cmd_retval cmd_list_panes_exec(struct cmd *, struct cmdq_item *);
+
+static void cmd_list_panes_server(struct cmd *, struct cmdq_item *);
+static void cmd_list_panes_session(struct cmd *, struct session *,
+ struct cmdq_item *, int);
+static void cmd_list_panes_window(struct cmd *, struct session *,
+ struct winlink *, struct cmdq_item *, int);
+
+const struct cmd_entry cmd_list_panes_entry = {
+ .name = "list-panes",
+ .alias = "lsp",
+
+ .args = { "asF:f:t:", 0, 0, NULL },
+ .usage = "[-as] [-F format] [-f filter] " CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_list_panes_exec
+};
+
+static enum cmd_retval
+cmd_list_panes_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 session *s = target->s;
+ struct winlink *wl = target->wl;
+
+ if (args_has(args, 'a'))
+ cmd_list_panes_server(self, item);
+ else if (args_has(args, 's'))
+ cmd_list_panes_session(self, s, item, 1);
+ else
+ cmd_list_panes_window(self, s, wl, item, 0);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static void
+cmd_list_panes_server(struct cmd *self, struct cmdq_item *item)
+{
+ struct session *s;
+
+ RB_FOREACH(s, sessions, &sessions)
+ cmd_list_panes_session(self, s, item, 2);
+}
+
+static void
+cmd_list_panes_session(struct cmd *self, struct session *s,
+ struct cmdq_item *item, int type)
+{
+ struct winlink *wl;
+
+ RB_FOREACH(wl, winlinks, &s->windows)
+ cmd_list_panes_window(self, s, wl, item, type);
+}
+
+static void
+cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl,
+ struct cmdq_item *item, int type)
+{
+ struct args *args = cmd_get_args(self);
+ struct window_pane *wp;
+ u_int n;
+ struct format_tree *ft;
+ const char *template, *filter;
+ char *line, *expanded;
+ int flag;
+
+ template = args_get(args, 'F');
+ if (template == NULL) {
+ switch (type) {
+ case 0:
+ template = "#{pane_index}: "
+ "[#{pane_width}x#{pane_height}] [history "
+ "#{history_size}/#{history_limit}, "
+ "#{history_bytes} bytes] #{pane_id}"
+ "#{?pane_active, (active),}#{?pane_dead, (dead),}";
+ break;
+ case 1:
+ template = "#{window_index}.#{pane_index}: "
+ "[#{pane_width}x#{pane_height}] [history "
+ "#{history_size}/#{history_limit}, "
+ "#{history_bytes} bytes] #{pane_id}"
+ "#{?pane_active, (active),}#{?pane_dead, (dead),}";
+ break;
+ case 2:
+ template = "#{session_name}:#{window_index}."
+ "#{pane_index}: [#{pane_width}x#{pane_height}] "
+ "[history #{history_size}/#{history_limit}, "
+ "#{history_bytes} bytes] #{pane_id}"
+ "#{?pane_active, (active),}#{?pane_dead, (dead),}";
+ break;
+ }
+ }
+ filter = args_get(args, 'f');
+
+ n = 0;
+ TAILQ_FOREACH(wp, &wl->window->panes, entry) {
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ format_add(ft, "line", "%u", n);
+ format_defaults(ft, NULL, s, wl, wp);
+
+ 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);
+ n++;
+ }
+}
diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c
new file mode 100644
index 0000000..e448524
--- /dev/null
+++ b/cmd-list-sessions.c
@@ -0,0 +1,90 @@
+/* $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 <time.h>
+
+#include "tmux.h"
+
+/*
+ * List all sessions.
+ */
+
+#define LIST_SESSIONS_TEMPLATE \
+ "#{session_name}: #{session_windows} windows " \
+ "(created #{t:session_created})" \
+ "#{?session_grouped, (group ,}" \
+ "#{session_group}#{?session_grouped,),}" \
+ "#{?session_attached, (attached),}"
+
+static enum cmd_retval cmd_list_sessions_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_list_sessions_entry = {
+ .name = "list-sessions",
+ .alias = "ls",
+
+ .args = { "F:f:", 0, 0, NULL },
+ .usage = "[-F format] [-f filter]",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_list_sessions_exec
+};
+
+static enum cmd_retval
+cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct session *s;
+ u_int n;
+ struct format_tree *ft;
+ const char *template, *filter;
+ char *line, *expanded;
+ int flag;
+
+ if ((template = args_get(args, 'F')) == NULL)
+ template = LIST_SESSIONS_TEMPLATE;
+ filter = args_get(args, 'f');
+
+ n = 0;
+ RB_FOREACH(s, sessions, &sessions) {
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ format_add(ft, "line", "%u", n);
+ format_defaults(ft, NULL, s, NULL, NULL);
+
+ 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);
+ n++;
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-list-windows.c b/cmd-list-windows.c
new file mode 100644
index 0000000..035471d
--- /dev/null
+++ b/cmd-list-windows.c
@@ -0,0 +1,130 @@
+/* $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 <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * List windows on given session.
+ */
+
+#define LIST_WINDOWS_TEMPLATE \
+ "#{window_index}: #{window_name}#{window_raw_flags} " \
+ "(#{window_panes} panes) " \
+ "[#{window_width}x#{window_height}] " \
+ "[layout #{window_layout}] #{window_id}" \
+ "#{?window_active, (active),}";
+#define LIST_WINDOWS_WITH_SESSION_TEMPLATE \
+ "#{session_name}:" \
+ "#{window_index}: #{window_name}#{window_raw_flags} " \
+ "(#{window_panes} panes) " \
+ "[#{window_width}x#{window_height}] "
+
+static enum cmd_retval cmd_list_windows_exec(struct cmd *, struct cmdq_item *);
+
+static void cmd_list_windows_server(struct cmd *, struct cmdq_item *);
+static void cmd_list_windows_session(struct cmd *, struct session *,
+ struct cmdq_item *, int);
+
+const struct cmd_entry cmd_list_windows_entry = {
+ .name = "list-windows",
+ .alias = "lsw",
+
+ .args = { "F:f:at:", 0, 0, NULL },
+ .usage = "[-a] [-F format] [-f filter] " CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_list_windows_exec
+};
+
+static enum cmd_retval
+cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *target = cmdq_get_target(item);
+
+ if (args_has(args, 'a'))
+ cmd_list_windows_server(self, item);
+ else
+ cmd_list_windows_session(self, target->s, item, 0);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static void
+cmd_list_windows_server(struct cmd *self, struct cmdq_item *item)
+{
+ struct session *s;
+
+ RB_FOREACH(s, sessions, &sessions)
+ cmd_list_windows_session(self, s, item, 1);
+}
+
+static void
+cmd_list_windows_session(struct cmd *self, struct session *s,
+ struct cmdq_item *item, int type)
+{
+ struct args *args = cmd_get_args(self);
+ struct winlink *wl;
+ u_int n;
+ struct format_tree *ft;
+ const char *template, *filter;
+ char *line, *expanded;
+ int flag;
+
+ template = args_get(args, 'F');
+ if (template == NULL) {
+ switch (type) {
+ case 0:
+ template = LIST_WINDOWS_TEMPLATE;
+ break;
+ case 1:
+ template = LIST_WINDOWS_WITH_SESSION_TEMPLATE;
+ break;
+ }
+ }
+ filter = args_get(args, 'f');
+
+ n = 0;
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ format_add(ft, "line", "%u", n);
+ format_defaults(ft, NULL, s, wl, NULL);
+
+ 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);
+ n++;
+ }
+}
diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c
new file mode 100644
index 0000000..59810de
--- /dev/null
+++ b/cmd-load-buffer.c
@@ -0,0 +1,113 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
+ *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Loads a paste buffer from a file.
+ */
+
+static enum cmd_retval cmd_load_buffer_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_load_buffer_entry = {
+ .name = "load-buffer",
+ .alias = "loadb",
+
+ .args = { "b:t:w", 1, 1, NULL },
+ .usage = CMD_BUFFER_USAGE " " CMD_TARGET_CLIENT_USAGE " path",
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG|CMD_CLIENT_CANFAIL,
+ .exec = cmd_load_buffer_exec
+};
+
+struct cmd_load_buffer_data {
+ struct client *client;
+ struct cmdq_item *item;
+ char *name;
+};
+
+static void
+cmd_load_buffer_done(__unused struct client *c, const char *path, int error,
+ int closed, struct evbuffer *buffer, void *data)
+{
+ struct cmd_load_buffer_data *cdata = data;
+ struct client *tc = cdata->client;
+ struct cmdq_item *item = cdata->item;
+ void *bdata = EVBUFFER_DATA(buffer);
+ size_t bsize = EVBUFFER_LENGTH(buffer);
+ void *copy;
+ char *cause;
+
+ if (!closed)
+ return;
+
+ if (error != 0)
+ cmdq_error(item, "%s: %s", path, strerror(error));
+ else if (bsize != 0) {
+ copy = xmalloc(bsize);
+ memcpy(copy, bdata, bsize);
+ if (paste_set(copy, bsize, cdata->name, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ free(copy);
+ } else if (tc != NULL &&
+ tc->session != NULL &&
+ (~tc->flags & CLIENT_DEAD))
+ tty_set_selection(&tc->tty, copy, bsize);
+ if (tc != NULL)
+ server_client_unref(tc);
+ }
+ cmdq_continue(item);
+
+ free(cdata->name);
+ free(cdata);
+}
+
+static enum cmd_retval
+cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ struct cmd_load_buffer_data *cdata;
+ const char *bufname = args_get(args, 'b');
+ char *path;
+
+ cdata = xcalloc(1, sizeof *cdata);
+ cdata->item = item;
+ if (bufname != NULL)
+ cdata->name = xstrdup(bufname);
+ if (args_has(args, 'w') && tc != NULL) {
+ cdata->client = tc;
+ cdata->client->references++;
+ }
+
+ path = format_single_from_target(item, args_string(args, 0));
+ file_read(cmdq_get_client(item), path, cmd_load_buffer_done, cdata);
+ free(path);
+
+ return (CMD_RETURN_WAIT);
+}
diff --git a/cmd-lock-server.c b/cmd-lock-server.c
new file mode 100644
index 0000000..bd61dcf
--- /dev/null
+++ b/cmd-lock-server.c
@@ -0,0 +1,79 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 "tmux.h"
+
+/*
+ * Lock commands.
+ */
+
+static enum cmd_retval cmd_lock_server_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_lock_server_entry = {
+ .name = "lock-server",
+ .alias = "lock",
+
+ .args = { "", 0, 0, NULL },
+ .usage = "",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_lock_server_exec
+};
+
+const struct cmd_entry cmd_lock_session_entry = {
+ .name = "lock-session",
+ .alias = "locks",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_lock_server_exec
+};
+
+const struct cmd_entry cmd_lock_client_entry = {
+ .name = "lock-client",
+ .alias = "lockc",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_CLIENT_USAGE,
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG,
+ .exec = cmd_lock_server_exec
+};
+
+static enum cmd_retval
+cmd_lock_server_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *tc = cmdq_get_target_client(item);
+
+ if (cmd_get_entry(self) == &cmd_lock_server_entry)
+ server_lock();
+ else if (cmd_get_entry(self) == &cmd_lock_session_entry)
+ server_lock_session(target->s);
+ else
+ server_lock_client(tc);
+ recalculate_sizes();
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-move-window.c b/cmd-move-window.c
new file mode 100644
index 0000000..4b90e70
--- /dev/null
+++ b/cmd-move-window.c
@@ -0,0 +1,122 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 "tmux.h"
+
+/*
+ * Move a window.
+ */
+
+static enum cmd_retval cmd_move_window_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_move_window_entry = {
+ .name = "move-window",
+ .alias = "movew",
+
+ .args = { "abdkrs:t:", 0, 0, NULL },
+ .usage = "[-abdkr] " CMD_SRCDST_WINDOW_USAGE,
+
+ .source = { 's', CMD_FIND_WINDOW, 0 },
+ /* -t is special */
+
+ .flags = 0,
+ .exec = cmd_move_window_exec
+};
+
+const struct cmd_entry cmd_link_window_entry = {
+ .name = "link-window",
+ .alias = "linkw",
+
+ .args = { "abdks:t:", 0, 0, NULL },
+ .usage = "[-abdk] " CMD_SRCDST_WINDOW_USAGE,
+
+ .source = { 's', CMD_FIND_WINDOW, 0 },
+ /* -t is special */
+
+ .flags = 0,
+ .exec = cmd_move_window_exec
+};
+
+static enum cmd_retval
+cmd_move_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct cmd_find_state target;
+ const char *tflag = args_get(args, 't');
+ struct session *src = source->s;
+ struct session *dst;
+ struct winlink *wl = source->wl;
+ char *cause;
+ int idx, kflag, dflag, sflag, before;
+
+ if (args_has(args, 'r')) {
+ if (cmd_find_target(&target, item, tflag, CMD_FIND_SESSION,
+ CMD_FIND_QUIET) != 0)
+ return (CMD_RETURN_ERROR);
+
+ session_renumber_windows(target.s);
+ recalculate_sizes();
+ server_status_session(target.s);
+
+ return (CMD_RETURN_NORMAL);
+ }
+ if (cmd_find_target(&target, item, tflag, CMD_FIND_WINDOW,
+ CMD_FIND_WINDOW_INDEX) != 0)
+ return (CMD_RETURN_ERROR);
+ dst = target.s;
+ idx = target.idx;
+
+ kflag = args_has(args, 'k');
+ dflag = args_has(args, 'd');
+ sflag = args_has(args, 's');
+
+ before = args_has(args, 'b');
+ if (args_has(args, 'a') || before) {
+ if (target.wl != NULL)
+ idx = winlink_shuffle_up(dst, target.wl, before);
+ else
+ idx = winlink_shuffle_up(dst, dst->curw, before);
+ if (idx == -1)
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (server_link_window(src, wl, dst, idx, kflag, !dflag, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ if (cmd_get_entry(self) == &cmd_move_window_entry)
+ server_unlink_window(src, wl);
+
+ /*
+ * Renumber the winlinks in the src session only, the destination
+ * session already has the correct winlink id to us, either
+ * automatically or specified by -s.
+ */
+ if (!sflag && options_get_number(src->options, "renumber-windows"))
+ session_renumber_windows(src);
+
+ recalculate_sizes();
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-new-session.c b/cmd-new-session.c
new file mode 100644
index 0000000..cb9abfb
--- /dev/null
+++ b/cmd-new-session.c
@@ -0,0 +1,374 @@
+/* $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 <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Create a new session and attach to the current terminal unless -d is given.
+ */
+
+#define NEW_SESSION_TEMPLATE "#{session_name}:"
+
+static enum cmd_retval cmd_new_session_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_new_session_entry = {
+ .name = "new-session",
+ .alias = "new",
+
+ .args = { "Ac:dDe:EF:f:n:Ps:t:x:Xy:", 0, -1, NULL },
+ .usage = "[-AdDEPX] [-c start-directory] [-e environment] [-F format] "
+ "[-f flags] [-n window-name] [-s session-name] "
+ CMD_TARGET_SESSION_USAGE " [-x width] [-y height] "
+ "[shell-command]",
+
+ .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL },
+
+ .flags = CMD_STARTSERVER,
+ .exec = cmd_new_session_exec
+};
+
+const struct cmd_entry cmd_has_session_entry = {
+ .name = "has-session",
+ .alias = "has",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = 0,
+ .exec = cmd_new_session_exec
+};
+
+static enum cmd_retval
+cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *c = cmdq_get_client(item);
+ struct session *s, *as, *groupwith = NULL;
+ struct environ *env;
+ struct options *oo;
+ struct termios tio, *tiop;
+ struct session_group *sg = NULL;
+ const char *errstr, *template, *group, *tmp;
+ char *cause, *cwd = NULL, *cp, *newname = NULL;
+ char *name, *prefix = NULL;
+ int detached, already_attached, is_control = 0;
+ u_int sx, sy, dsx, dsy, count = args_count(args);
+ struct spawn_context sc = { 0 };
+ enum cmd_retval retval;
+ struct cmd_find_state fs;
+ struct args_value *av;
+
+ if (cmd_get_entry(self) == &cmd_has_session_entry) {
+ /*
+ * cmd_find_target() will fail if the session cannot be found,
+ * so always return success here.
+ */
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 't') && (count != 0 || args_has(args, 'n'))) {
+ cmdq_error(item, "command or window name given with target");
+ return (CMD_RETURN_ERROR);
+ }
+
+ tmp = args_get(args, 's');
+ if (tmp != NULL) {
+ name = format_single(item, tmp, c, NULL, NULL, NULL);
+ newname = session_check_name(name);
+ if (newname == NULL) {
+ cmdq_error(item, "invalid session: %s", name);
+ free(name);
+ return (CMD_RETURN_ERROR);
+ }
+ free(name);
+ }
+ if (args_has(args, 'A')) {
+ if (newname != NULL)
+ as = session_find(newname);
+ else
+ as = target->s;
+ if (as != NULL) {
+ retval = cmd_attach_session(item, as->name,
+ args_has(args, 'D'), args_has(args, 'X'), 0, NULL,
+ args_has(args, 'E'), args_get(args, 'f'));
+ free(newname);
+ return (retval);
+ }
+ }
+ if (newname != NULL && session_find(newname) != NULL) {
+ cmdq_error(item, "duplicate session: %s", newname);
+ goto fail;
+ }
+
+ /* Is this going to be part of a session group? */
+ group = args_get(args, 't');
+ if (group != NULL) {
+ groupwith = target->s;
+ if (groupwith == NULL)
+ sg = session_group_find(group);
+ else
+ sg = session_group_contains(groupwith);
+ if (sg != NULL)
+ prefix = xstrdup(sg->name);
+ else if (groupwith != NULL)
+ prefix = xstrdup(groupwith->name);
+ else {
+ prefix = session_check_name(group);
+ if (prefix == NULL) {
+ cmdq_error(item, "invalid session group: %s",
+ group);
+ goto fail;
+ }
+ }
+ }
+
+ /* Set -d if no client. */
+ detached = args_has(args, 'd');
+ if (c == NULL)
+ detached = 1;
+ else if (c->flags & CLIENT_CONTROL)
+ is_control = 1;
+
+ /* Is this client already attached? */
+ already_attached = 0;
+ if (c != NULL && c->session != NULL)
+ already_attached = 1;
+
+ /* Get the new session working directory. */
+ if ((tmp = args_get(args, 'c')) != NULL)
+ cwd = format_single(item, tmp, c, NULL, NULL, NULL);
+ else
+ cwd = xstrdup(server_client_get_cwd(c, NULL));
+
+ /*
+ * If this is a new client, check for nesting and save the termios
+ * settings (part of which is used for new windows in this session).
+ *
+ * tcgetattr() is used rather than using tty.tio since if the client is
+ * detached, tty_open won't be called. It must be done before opening
+ * the terminal as that calls tcsetattr() to prepare for tmux taking
+ * over.
+ */
+ if (!detached &&
+ !already_attached &&
+ c->fd != -1 &&
+ (~c->flags & CLIENT_CONTROL)) {
+ if (server_client_check_nested(cmdq_get_client(item))) {
+ cmdq_error(item, "sessions should be nested with care, "
+ "unset $TMUX to force");
+ goto fail;
+ }
+ if (tcgetattr(c->fd, &tio) != 0)
+ fatal("tcgetattr failed");
+ tiop = &tio;
+ } else
+ tiop = NULL;
+
+ /* Open the terminal if necessary. */
+ if (!detached && !already_attached) {
+ if (server_client_open(c, &cause) != 0) {
+ cmdq_error(item, "open terminal failed: %s", cause);
+ free(cause);
+ goto fail;
+ }
+ }
+
+ /* Get default session size. */
+ if (args_has(args, 'x')) {
+ tmp = args_get(args, 'x');
+ if (strcmp(tmp, "-") == 0) {
+ if (c != NULL)
+ dsx = c->tty.sx;
+ else
+ dsx = 80;
+ } else {
+ dsx = strtonum(tmp, 1, USHRT_MAX, &errstr);
+ if (errstr != NULL) {
+ cmdq_error(item, "width %s", errstr);
+ goto fail;
+ }
+ }
+ } else
+ dsx = 80;
+ if (args_has(args, 'y')) {
+ tmp = args_get(args, 'y');
+ if (strcmp(tmp, "-") == 0) {
+ if (c != NULL)
+ dsy = c->tty.sy;
+ else
+ dsy = 24;
+ } else {
+ dsy = strtonum(tmp, 1, USHRT_MAX, &errstr);
+ if (errstr != NULL) {
+ cmdq_error(item, "height %s", errstr);
+ goto fail;
+ }
+ }
+ } else
+ dsy = 24;
+
+ /* Find new session size. */
+ if (!detached && !is_control) {
+ sx = c->tty.sx;
+ sy = c->tty.sy;
+ if (sy > 0 && options_get_number(global_s_options, "status"))
+ sy--;
+ } else {
+ tmp = options_get_string(global_s_options, "default-size");
+ if (sscanf(tmp, "%ux%u", &sx, &sy) != 2) {
+ sx = dsx;
+ sy = dsy;
+ } else {
+ if (args_has(args, 'x'))
+ sx = dsx;
+ if (args_has(args, 'y'))
+ sy = dsy;
+ }
+ }
+ if (sx == 0)
+ sx = 1;
+ if (sy == 0)
+ sy = 1;
+
+ /* Create the new session. */
+ oo = options_create(global_s_options);
+ if (args_has(args, 'x') || args_has(args, 'y')) {
+ if (!args_has(args, 'x'))
+ dsx = sx;
+ if (!args_has(args, 'y'))
+ dsy = sy;
+ options_set_string(oo, "default-size", 0, "%ux%u", dsx, dsy);
+ }
+ env = environ_create();
+ if (c != NULL && !args_has(args, 'E'))
+ environ_update(global_s_options, c->environ, env);
+ av = args_first_value(args, 'e');
+ while (av != NULL) {
+ environ_put(env, av->string, 0);
+ av = args_next_value(av);
+ }
+ s = session_create(prefix, newname, cwd, env, oo, tiop);
+
+ /* Spawn the initial window. */
+ sc.item = item;
+ sc.s = s;
+ if (!detached)
+ sc.tc = c;
+
+ sc.name = args_get(args, 'n');
+ args_to_vector(args, &sc.argc, &sc.argv);
+
+ sc.idx = -1;
+ sc.cwd = args_get(args, 'c');
+
+ sc.flags = 0;
+
+ if (spawn_window(&sc, &cause) == NULL) {
+ session_destroy(s, 0, __func__);
+ cmdq_error(item, "create window failed: %s", cause);
+ free(cause);
+ goto fail;
+ }
+
+ /*
+ * If a target session is given, this is to be part of a session group,
+ * so add it to the group and synchronize.
+ */
+ if (group != NULL) {
+ if (sg == NULL) {
+ if (groupwith != NULL) {
+ sg = session_group_new(groupwith->name);
+ session_group_add(sg, groupwith);
+ } else
+ sg = session_group_new(group);
+ }
+ session_group_add(sg, s);
+ session_group_synchronize_to(s);
+ session_select(s, RB_MIN(winlinks, &s->windows)->idx);
+ }
+ notify_session("session-created", s);
+
+ /*
+ * Set the client to the new session. If a command client exists, it is
+ * taking this session and needs to get MSG_READY and stay around.
+ */
+ if (!detached) {
+ if (args_has(args, 'f'))
+ server_client_set_flags(c, args_get(args, 'f'));
+ if (!already_attached) {
+ if (~c->flags & CLIENT_CONTROL)
+ proc_send(c->peer, MSG_READY, -1, NULL, 0);
+ } else if (c->session != NULL)
+ c->last_session = c->session;
+ server_client_set_session(c, s);
+ if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT)
+ 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)
+ template = NEW_SESSION_TEMPLATE;
+ cp = format_single(item, template, c, s, s->curw, NULL);
+ cmdq_print(item, "%s", cp);
+ free(cp);
+ }
+
+ if (!detached)
+ c->flags |= CLIENT_ATTACHED;
+ if (!args_has(args, 'd'))
+ cmd_find_from_session(current, s, 0);
+
+ cmd_find_from_session(&fs, s, 0);
+ cmdq_insert_hook(s, item, &fs, "after-new-session");
+
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ free(cwd);
+ free(newname);
+ free(prefix);
+ return (CMD_RETURN_NORMAL);
+
+fail:
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ free(cwd);
+ free(newname);
+ free(prefix);
+ return (CMD_RETURN_ERROR);
+}
diff --git a/cmd-new-window.c b/cmd-new-window.c
new file mode 100644
index 0000000..e7f0868
--- /dev/null
+++ b/cmd-new-window.c
@@ -0,0 +1,156 @@
+/* $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 <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Create a new window.
+ */
+
+#define NEW_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}"
+
+static enum cmd_retval cmd_new_window_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_new_window_entry = {
+ .name = "new-window",
+ .alias = "neww",
+
+ .args = { "abc:de:F:kn:PSt:", 0, -1, NULL },
+ .usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] "
+ "[-n window-name] " CMD_TARGET_WINDOW_USAGE " [shell-command]",
+
+ .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX },
+
+ .flags = 0,
+ .exec = cmd_new_window_exec
+};
+
+static enum cmd_retval
+cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *c = cmdq_get_client(item);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct spawn_context sc = { 0 };
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct winlink *wl = target->wl, *new_wl = NULL;
+ int idx = target->idx, before;
+ char *cause = NULL, *cp;
+ const char *template, *name;
+ struct cmd_find_state fs;
+ struct args_value *av;
+
+ /*
+ * If -S and -n are given and -t is not and a single window with this
+ * name already exists, select it.
+ */
+ name = args_get(args, 'n');
+ if (args_has(args, 'S') && name != NULL && target->idx == -1) {
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ if (strcmp(wl->window->name, name) != 0)
+ continue;
+ if (new_wl == NULL) {
+ new_wl = wl;
+ continue;
+ }
+ cmdq_error(item, "multiple windows named %s", name);
+ return (CMD_RETURN_ERROR);
+ }
+ if (new_wl != NULL) {
+ if (args_has(args, 'd'))
+ return (CMD_RETURN_NORMAL);
+ if (session_set_current(s, new_wl) == 0)
+ server_redraw_session(s);
+ if (c != NULL && c->session != NULL)
+ s->curw->window->latest = c;
+ recalculate_sizes();
+ return (CMD_RETURN_NORMAL);
+ }
+ }
+
+ before = args_has(args, 'b');
+ if (args_has(args, 'a') || before) {
+ idx = winlink_shuffle_up(s, wl, before);
+ if (idx == -1)
+ idx = target->idx;
+ }
+
+ sc.item = item;
+ sc.s = s;
+ sc.tc = tc;
+
+ sc.name = args_get(args, 'n');
+ args_to_vector(args, &sc.argc, &sc.argv);
+ sc.environ = environ_create();
+
+ av = args_first_value(args, 'e');
+ while (av != NULL) {
+ environ_put(sc.environ, av->string, 0);
+ av = args_next_value(av);
+ }
+
+ sc.idx = idx;
+ sc.cwd = args_get(args, 'c');
+
+ sc.flags = 0;
+ if (args_has(args, 'd'))
+ sc.flags |= SPAWN_DETACHED;
+ if (args_has(args, 'k'))
+ sc.flags |= SPAWN_KILL;
+
+ if ((new_wl = spawn_window(&sc, &cause)) == NULL) {
+ cmdq_error(item, "create window failed: %s", cause);
+ free(cause);
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_ERROR);
+ }
+ if (!args_has(args, 'd') || new_wl == s->curw) {
+ cmd_find_from_winlink(current, new_wl, 0);
+ server_redraw_session_group(s);
+ } else
+ server_status_session_group(s);
+
+ if (args_has(args, 'P')) {
+ if ((template = args_get(args, 'F')) == NULL)
+ template = NEW_WINDOW_TEMPLATE;
+ cp = format_single(item, template, tc, s, new_wl,
+ new_wl->window->active);
+ cmdq_print(item, "%s", cp);
+ free(cp);
+ }
+
+ cmd_find_from_winlink(&fs, new_wl, 0);
+ cmdq_insert_hook(s, item, &fs, "after-new-window");
+
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-parse.c b/cmd-parse.c
new file mode 100644
index 0000000..309c6fe
--- /dev/null
+++ b/cmd-parse.c
@@ -0,0 +1,2229 @@
+#include <stdlib.h>
+#include <string.h>
+#define YYBYACC 1
+#define YYMAJOR 1
+#define YYMINOR 9
+#define YYLEX yylex()
+#define YYEMPTY -1
+#define yyclearin (yychar=(YYEMPTY))
+#define yyerrok (yyerrflag=0)
+#define YYRECOVERING() (yyerrflag!=0)
+#define YYPREFIX "yy"
+#line 20 "cmd-parse.y"
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "tmux.h"
+
+static int yylex(void);
+static int yyparse(void);
+static int printflike(1,2) yyerror(const char *, ...);
+
+static char *yylex_token(int);
+static char *yylex_format(void);
+
+struct cmd_parse_scope {
+ int flag;
+ TAILQ_ENTRY (cmd_parse_scope) entry;
+};
+
+enum cmd_parse_argument_type {
+ CMD_PARSE_STRING,
+ CMD_PARSE_COMMANDS,
+ CMD_PARSE_PARSED_COMMANDS
+};
+
+struct cmd_parse_argument {
+ enum cmd_parse_argument_type type;
+ char *string;
+ struct cmd_parse_commands *commands;
+ struct cmd_list *cmdlist;
+
+ TAILQ_ENTRY(cmd_parse_argument) entry;
+};
+TAILQ_HEAD(cmd_parse_arguments, cmd_parse_argument);
+
+struct cmd_parse_command {
+ u_int line;
+ struct cmd_parse_arguments arguments;
+
+ TAILQ_ENTRY(cmd_parse_command) entry;
+};
+TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
+
+struct cmd_parse_state {
+ FILE *f;
+
+ const char *buf;
+ size_t len;
+ size_t off;
+
+ int condition;
+ int eol;
+ int eof;
+ struct cmd_parse_input *input;
+ u_int escapes;
+
+ char *error;
+ struct cmd_parse_commands *commands;
+
+ struct cmd_parse_scope *scope;
+ TAILQ_HEAD(, cmd_parse_scope) stack;
+};
+static struct cmd_parse_state parse_state;
+
+static char *cmd_parse_get_error(const char *, u_int, const char *);
+static void cmd_parse_free_command(struct cmd_parse_command *);
+static struct cmd_parse_commands *cmd_parse_new_commands(void);
+static void cmd_parse_free_commands(struct cmd_parse_commands *);
+static void cmd_parse_build_commands(struct cmd_parse_commands *,
+ struct cmd_parse_input *, struct cmd_parse_result *);
+static void cmd_parse_print_commands(struct cmd_parse_input *,
+ struct cmd_list *);
+
+#line 101 "cmd-parse.y"
+#ifndef YYSTYPE_DEFINED
+#define YYSTYPE_DEFINED
+typedef union
+{
+ char *token;
+ struct cmd_parse_arguments *arguments;
+ struct cmd_parse_argument *argument;
+ int flag;
+ struct {
+ int flag;
+ struct cmd_parse_commands *commands;
+ } elif;
+ struct cmd_parse_commands *commands;
+ struct cmd_parse_command *command;
+} YYSTYPE;
+#endif /* YYSTYPE_DEFINED */
+#line 110 "cmd-parse.c"
+#define ERROR 257
+#define HIDDEN 258
+#define IF 259
+#define ELSE 260
+#define ELIF 261
+#define ENDIF 262
+#define FORMAT 263
+#define TOKEN 264
+#define EQUALS 265
+#define YYERRCODE 256
+const short yylhs[] =
+ { -1,
+ 0, 0, 10, 10, 11, 11, 11, 11, 2, 2,
+ 1, 17, 17, 18, 16, 5, 19, 6, 20, 13,
+ 13, 13, 13, 7, 7, 12, 12, 12, 12, 12,
+ 15, 15, 15, 14, 14, 14, 14, 8, 8, 3,
+ 3, 4, 4, 4, 9, 9,
+};
+const short yylen[] =
+ { 2,
+ 0, 1, 2, 3, 0, 1, 1, 1, 1, 1,
+ 1, 0, 1, 1, 2, 2, 1, 2, 1, 4,
+ 7, 5, 8, 3, 4, 1, 2, 3, 3, 1,
+ 1, 2, 3, 3, 5, 4, 6, 2, 3, 1,
+ 2, 1, 1, 2, 2, 3,
+};
+const short yydefred[] =
+ { 0,
+ 0, 0, 14, 0, 0, 0, 0, 0, 7, 30,
+ 26, 6, 0, 0, 15, 9, 10, 16, 11, 0,
+ 0, 0, 0, 3, 0, 0, 0, 17, 0, 19,
+ 0, 0, 0, 34, 4, 28, 29, 42, 43, 0,
+ 33, 0, 0, 0, 0, 20, 18, 0, 0, 36,
+ 0, 44, 0, 0, 41, 0, 0, 22, 0, 39,
+ 0, 35, 0, 45, 0, 0, 0, 37, 46, 25,
+ 0, 21, 23,
+};
+const short yydgoto[] =
+ { 4,
+ 18, 19, 41, 42, 5, 31, 44, 32, 52, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 33, 34,
+};
+const short yysindex[] =
+ { -175,
+ -227, -172, 0, 0, -10, -175, 46, 10, 0, 0,
+ 0, 0, -205, 0, 0, 0, 0, 0, 0, -175,
+ -238, -56, 53, 0, -238, -118, -228, 0, -172, 0,
+ -238, -234, -238, 0, 0, 0, 0, 0, 0, -175,
+ 0, -118, 63, -234, 66, 0, 0, -52, -238, 0,
+ -55, 0, -175, 3, 0, -175, 68, 0, -175, 0,
+ -55, 0, 4, 0, -208, -175, -219, 0, 0, 0,
+ -219, 0, 0,};
+const short yyrindex[] =
+ { 1,
+ 0, 0, 0, 0, -184, 2, 0, 5, 0, 0,
+ 0, 0, 0, -4, 0, 0, 0, 0, 0, 6,
+ -184, 0, 0, 0, 7, 12, 6, 0, 0, 0,
+ -184, 0, -184, 0, 0, 0, 0, 0, 0, -2,
+ 0, 15, 0, 0, 0, 0, 0, -215, -184, 0,
+ 0, 0, -2, 0, 0, 6, 0, 0, 6, 0,
+ 0, 0, 0, 0, -1, 6, 6, 0, 0, 0,
+ 6, 0, 0,};
+const short yygindex[] =
+ { 0,
+ 57, 0, 40, 0, 39, -17, 28, 47, 0, 9,
+ 14, 56, 0, 69, 71, 0, 0, 0, -8, -9,
+};
+#define YYTABLESIZE 277
+const short yytable[] =
+ { 20,
+ 1, 2, 25, 25, 40, 31, 25, 5, 5, 43,
+ 5, 5, 24, 35, 8, 5, 27, 46, 45, 23,
+ 2, 32, 50, 49, 40, 28, 3, 30, 27, 1,
+ 2, 28, 29, 30, 58, 57, 3, 15, 1, 2,
+ 23, 62, 30, 21, 38, 3, 38, 43, 53, 1,
+ 2, 68, 29, 54, 31, 24, 3, 72, 26, 21,
+ 22, 73, 35, 21, 65, 27, 63, 67, 25, 21,
+ 32, 21, 56, 40, 71, 59, 22, 66, 23, 12,
+ 23, 55, 1, 2, 23, 47, 48, 21, 51, 3,
+ 16, 17, 70, 36, 60, 37, 0, 0, 0, 0,
+ 0, 0, 0, 0, 61, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 31, 0, 5, 0, 0, 0, 0, 64, 69, 8,
+ 0, 27, 0, 0, 0, 0, 32, 0, 0, 40,
+ 0, 0, 0, 0, 0, 38, 39, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 28, 29, 30, 30, 0, 29, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 3, 31, 31, 31, 24, 13,
+ 24, 12, 12, 0, 12, 12, 27, 27, 27, 12,
+ 12, 32, 32, 32, 40, 40, 40,
+};
+const short yycheck[] =
+ { 10,
+ 0, 0, 59, 59, 123, 10, 59, 10, 10, 27,
+ 10, 10, 10, 10, 10, 10, 10, 27, 27, 6,
+ 259, 10, 32, 32, 10, 260, 265, 262, 20, 258,
+ 259, 260, 261, 262, 44, 44, 265, 265, 258, 259,
+ 27, 51, 262, 5, 260, 265, 262, 65, 40, 258,
+ 259, 61, 261, 40, 59, 10, 265, 67, 264, 21,
+ 5, 71, 10, 25, 56, 59, 53, 59, 59, 31,
+ 59, 33, 10, 59, 66, 10, 21, 10, 65, 264,
+ 67, 42, 258, 259, 71, 29, 31, 49, 33, 265,
+ 263, 264, 65, 25, 48, 25, -1, -1, -1, -1,
+ -1, -1, -1, -1, 49, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 125, -1, 125, -1, -1, -1, -1, 125, 125, 125,
+ -1, 125, -1, -1, -1, -1, 125, -1, -1, 125,
+ -1, -1, -1, -1, -1, 264, 265, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 260, 261, 262, 262, -1, 261, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 259, -1,
+ -1, -1, -1, -1, 265, 260, 261, 262, 260, 264,
+ 262, 264, 264, -1, 264, 264, 260, 261, 262, 264,
+ 264, 260, 261, 262, 260, 261, 262,
+};
+#define YYFINAL 4
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYMAXTOKEN 265
+#if YYDEBUG
+const char * const yyname[] =
+ {
+"end-of-file",0,0,0,0,0,0,0,0,0,"'\\n'",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"';'",0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,"'{'",0,"'}'",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"ERROR",
+"HIDDEN","IF","ELSE","ELIF","ENDIF","FORMAT","TOKEN","EQUALS",
+};
+const char * const yyrule[] =
+ {"$accept : lines",
+"lines :",
+"lines : statements",
+"statements : statement '\\n'",
+"statements : statements statement '\\n'",
+"statement :",
+"statement : hidden_assignment",
+"statement : condition",
+"statement : commands",
+"format : FORMAT",
+"format : TOKEN",
+"expanded : format",
+"optional_assignment :",
+"optional_assignment : assignment",
+"assignment : EQUALS",
+"hidden_assignment : HIDDEN EQUALS",
+"if_open : IF expanded",
+"if_else : ELSE",
+"if_elif : ELIF expanded",
+"if_close : ENDIF",
+"condition : if_open '\\n' statements if_close",
+"condition : if_open '\\n' statements if_else '\\n' statements if_close",
+"condition : if_open '\\n' statements elif if_close",
+"condition : if_open '\\n' statements elif if_else '\\n' statements if_close",
+"elif : if_elif '\\n' statements",
+"elif : if_elif '\\n' statements elif",
+"commands : command",
+"commands : commands ';'",
+"commands : commands ';' condition1",
+"commands : commands ';' command",
+"commands : condition1",
+"command : assignment",
+"command : optional_assignment TOKEN",
+"command : optional_assignment TOKEN arguments",
+"condition1 : if_open commands if_close",
+"condition1 : if_open commands if_else commands if_close",
+"condition1 : if_open commands elif1 if_close",
+"condition1 : if_open commands elif1 if_else commands if_close",
+"elif1 : if_elif commands",
+"elif1 : if_elif commands elif1",
+"arguments : argument",
+"arguments : argument arguments",
+"argument : TOKEN",
+"argument : EQUALS",
+"argument : '{' argument_statements",
+"argument_statements : statement '}'",
+"argument_statements : statements statement '}'",
+};
+#endif
+#ifdef YYSTACKSIZE
+#undef YYMAXDEPTH
+#define YYMAXDEPTH YYSTACKSIZE
+#else
+#ifdef YYMAXDEPTH
+#define YYSTACKSIZE YYMAXDEPTH
+#else
+#define YYSTACKSIZE 10000
+#define YYMAXDEPTH 10000
+#endif
+#endif
+#define YYINITSTACKSIZE 200
+/* LINTUSED */
+int yydebug;
+int yynerrs;
+int yyerrflag;
+int yychar;
+short *yyssp;
+YYSTYPE *yyvsp;
+YYSTYPE yyval;
+YYSTYPE yylval;
+short *yyss;
+short *yysslim;
+YYSTYPE *yyvs;
+unsigned int yystacksize;
+int yyparse(void);
+#line 575 "cmd-parse.y"
+
+static char *
+cmd_parse_get_error(const char *file, u_int line, const char *error)
+{
+ char *s;
+
+ if (file == NULL)
+ s = xstrdup(error);
+ else
+ xasprintf(&s, "%s:%u: %s", file, line, error);
+ return (s);
+}
+
+static void
+cmd_parse_print_commands(struct cmd_parse_input *pi, struct cmd_list *cmdlist)
+{
+ char *s;
+
+ if (pi->item == NULL || (~pi->flags & CMD_PARSE_VERBOSE))
+ return;
+ s = cmd_list_print(cmdlist, 0);
+ if (pi->file != NULL)
+ cmdq_print(pi->item, "%s:%u: %s", pi->file, pi->line, s);
+ else
+ cmdq_print(pi->item, "%u: %s", pi->line, s);
+ free(s);
+}
+
+static void
+cmd_parse_free_argument(struct cmd_parse_argument *arg)
+{
+ switch (arg->type) {
+ case CMD_PARSE_STRING:
+ free(arg->string);
+ break;
+ case CMD_PARSE_COMMANDS:
+ cmd_parse_free_commands(arg->commands);
+ break;
+ case CMD_PARSE_PARSED_COMMANDS:
+ cmd_list_free(arg->cmdlist);
+ break;
+ }
+ free(arg);
+}
+
+static void
+cmd_parse_free_arguments(struct cmd_parse_arguments *args)
+{
+ struct cmd_parse_argument *arg, *arg1;
+
+ TAILQ_FOREACH_SAFE(arg, args, entry, arg1) {
+ TAILQ_REMOVE(args, arg, entry);
+ cmd_parse_free_argument(arg);
+ }
+}
+
+static void
+cmd_parse_free_command(struct cmd_parse_command *cmd)
+{
+ cmd_parse_free_arguments(&cmd->arguments);
+ free(cmd);
+}
+
+static struct cmd_parse_commands *
+cmd_parse_new_commands(void)
+{
+ struct cmd_parse_commands *cmds;
+
+ cmds = xmalloc(sizeof *cmds);
+ TAILQ_INIT(cmds);
+ return (cmds);
+}
+
+static void
+cmd_parse_free_commands(struct cmd_parse_commands *cmds)
+{
+ struct cmd_parse_command *cmd, *cmd1;
+
+ TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
+ TAILQ_REMOVE(cmds, cmd, entry);
+ cmd_parse_free_command(cmd);
+ }
+ free(cmds);
+}
+
+static struct cmd_parse_commands *
+cmd_parse_run_parser(char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope, *scope1;
+ int retval;
+
+ ps->commands = NULL;
+ TAILQ_INIT(&ps->stack);
+
+ retval = yyparse();
+ TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
+ TAILQ_REMOVE(&ps->stack, scope, entry);
+ free(scope);
+ }
+ if (retval != 0) {
+ *cause = ps->error;
+ return (NULL);
+ }
+
+ if (ps->commands == NULL)
+ return (cmd_parse_new_commands());
+ return (ps->commands);
+}
+
+static struct cmd_parse_commands *
+cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ memset(ps, 0, sizeof *ps);
+ ps->input = pi;
+ ps->f = f;
+ return (cmd_parse_run_parser(cause));
+}
+
+static struct cmd_parse_commands *
+cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi,
+ char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ memset(ps, 0, sizeof *ps);
+ ps->input = pi;
+ ps->buf = buf;
+ ps->len = len;
+ return (cmd_parse_run_parser(cause));
+}
+
+static void
+cmd_parse_log_commands(struct cmd_parse_commands *cmds, const char *prefix)
+{
+ struct cmd_parse_command *cmd;
+ struct cmd_parse_argument *arg;
+ u_int i, j;
+ char *s;
+
+ i = 0;
+ TAILQ_FOREACH(cmd, cmds, entry) {
+ j = 0;
+ TAILQ_FOREACH(arg, &cmd->arguments, entry) {
+ switch (arg->type) {
+ case CMD_PARSE_STRING:
+ log_debug("%s %u:%u: %s", prefix, i, j,
+ arg->string);
+ break;
+ case CMD_PARSE_COMMANDS:
+ xasprintf(&s, "%s %u:%u", prefix, i, j);
+ cmd_parse_log_commands(arg->commands, s);
+ free(s);
+ break;
+ case CMD_PARSE_PARSED_COMMANDS:
+ s = cmd_list_print(arg->cmdlist, 0);
+ log_debug("%s %u:%u: %s", prefix, i, j, s);
+ free(s);
+ break;
+ }
+ j++;
+ }
+ i++;
+ }
+}
+
+static int
+cmd_parse_expand_alias(struct cmd_parse_command *cmd,
+ struct cmd_parse_input *pi, struct cmd_parse_result *pr)
+{
+ struct cmd_parse_argument *arg, *arg1, *first;
+ struct cmd_parse_commands *cmds;
+ struct cmd_parse_command *last;
+ char *alias, *name, *cause;
+
+ if (pi->flags & CMD_PARSE_NOALIAS)
+ return (0);
+ memset(pr, 0, sizeof *pr);
+
+ first = TAILQ_FIRST(&cmd->arguments);
+ if (first == NULL || first->type != CMD_PARSE_STRING) {
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ return (1);
+ }
+ name = first->string;
+
+ alias = cmd_get_alias(name);
+ if (alias == NULL)
+ return (0);
+ log_debug("%s: %u alias %s = %s", __func__, pi->line, name, alias);
+
+ cmds = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause);
+ free(alias);
+ if (cmds == NULL) {
+ pr->status = CMD_PARSE_ERROR;
+ pr->error = cause;
+ return (1);
+ }
+
+ last = TAILQ_LAST(cmds, cmd_parse_commands);
+ if (last == NULL) {
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ return (1);
+ }
+
+ TAILQ_REMOVE(&cmd->arguments, first, entry);
+ cmd_parse_free_argument(first);
+
+ TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) {
+ TAILQ_REMOVE(&cmd->arguments, arg, entry);
+ TAILQ_INSERT_TAIL(&last->arguments, arg, entry);
+ }
+ cmd_parse_log_commands(cmds, __func__);
+
+ pi->flags |= CMD_PARSE_NOALIAS;
+ cmd_parse_build_commands(cmds, pi, pr);
+ pi->flags &= ~CMD_PARSE_NOALIAS;
+ return (1);
+}
+
+static void
+cmd_parse_build_command(struct cmd_parse_command *cmd,
+ struct cmd_parse_input *pi, struct cmd_parse_result *pr)
+{
+ struct cmd_parse_argument *arg;
+ struct cmd *add;
+ char *cause;
+ struct args_value *values = NULL;
+ u_int count = 0, idx;
+
+ memset(pr, 0, sizeof *pr);
+
+ if (cmd_parse_expand_alias(cmd, pi, pr))
+ return;
+
+ TAILQ_FOREACH(arg, &cmd->arguments, entry) {
+ values = xrecallocarray(values, count, count + 1,
+ sizeof *values);
+ switch (arg->type) {
+ case CMD_PARSE_STRING:
+ values[count].type = ARGS_STRING;
+ values[count].string = xstrdup(arg->string);
+ break;
+ case CMD_PARSE_COMMANDS:
+ cmd_parse_build_commands(arg->commands, pi, pr);
+ if (pr->status != CMD_PARSE_SUCCESS)
+ goto out;
+ values[count].type = ARGS_COMMANDS;
+ values[count].cmdlist = pr->cmdlist;
+ break;
+ case CMD_PARSE_PARSED_COMMANDS:
+ values[count].type = ARGS_COMMANDS;
+ values[count].cmdlist = arg->cmdlist;
+ values[count].cmdlist->references++;
+ break;
+ }
+ count++;
+ }
+
+ add = cmd_parse(values, count, pi->file, pi->line, &cause);
+ if (add == NULL) {
+ pr->status = CMD_PARSE_ERROR;
+ pr->error = cmd_parse_get_error(pi->file, pi->line, cause);
+ free(cause);
+ goto out;
+ }
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ cmd_list_append(pr->cmdlist, add);
+
+out:
+ for (idx = 0; idx < count; idx++)
+ args_free_value(&values[idx]);
+ free(values);
+}
+
+static void
+cmd_parse_build_commands(struct cmd_parse_commands *cmds,
+ struct cmd_parse_input *pi, struct cmd_parse_result *pr)
+{
+ struct cmd_parse_command *cmd;
+ u_int line = UINT_MAX;
+ struct cmd_list *current = NULL, *result;
+ char *s;
+
+ memset(pr, 0, sizeof *pr);
+
+ /* Check for an empty list. */
+ if (TAILQ_EMPTY(cmds)) {
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ return;
+ }
+ cmd_parse_log_commands(cmds, __func__);
+
+ /*
+ * Parse each command into a command list. Create a new command list
+ * for each line (unless the flag is set) so they get a new group (so
+ * the queue knows which ones to remove if a command fails when
+ * executed).
+ */
+ result = cmd_list_new();
+ TAILQ_FOREACH(cmd, cmds, entry) {
+ if (((~pi->flags & CMD_PARSE_ONEGROUP) && cmd->line != line)) {
+ if (current != NULL) {
+ cmd_parse_print_commands(pi, current);
+ cmd_list_move(result, current);
+ cmd_list_free(current);
+ }
+ current = cmd_list_new();
+ }
+ if (current == NULL)
+ current = cmd_list_new();
+ line = pi->line = cmd->line;
+
+ cmd_parse_build_command(cmd, pi, pr);
+ if (pr->status != CMD_PARSE_SUCCESS) {
+ cmd_list_free(result);
+ cmd_list_free(current);
+ return;
+ }
+ cmd_list_append_all(current, pr->cmdlist);
+ cmd_list_free(pr->cmdlist);
+ }
+ if (current != NULL) {
+ cmd_parse_print_commands(pi, current);
+ cmd_list_move(result, current);
+ cmd_list_free(current);
+ }
+
+ s = cmd_list_print(result, 0);
+ log_debug("%s: %s", __func__, s);
+ free(s);
+
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = result;
+}
+
+struct cmd_parse_result *
+cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds;
+ char *cause;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ cmds = cmd_parse_do_file(f, pi, &cause);
+ if (cmds == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cause;
+ return (&pr);
+ }
+ cmd_parse_build_commands(cmds, pi, &pr);
+ cmd_parse_free_commands(cmds);
+ return (&pr);
+
+}
+
+struct cmd_parse_result *
+cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
+{
+ struct cmd_parse_input input;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+
+ /*
+ * When parsing a string, put commands in one group even if there are
+ * multiple lines. This means { a \n b } is identical to "a ; b" when
+ * given as an argument to another command.
+ */
+ pi->flags |= CMD_PARSE_ONEGROUP;
+ return (cmd_parse_from_buffer(s, strlen(s), pi));
+}
+
+enum cmd_parse_status
+cmd_parse_and_insert(const char *s, struct cmd_parse_input *pi,
+ struct cmdq_item *after, struct cmdq_state *state, char **error)
+{
+ struct cmd_parse_result *pr;
+ struct cmdq_item *item;
+
+ pr = cmd_parse_from_string(s, pi);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ if (error != NULL)
+ *error = pr->error;
+ else
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ item = cmdq_get_command(pr->cmdlist, state);
+ cmdq_insert_after(after, item);
+ cmd_list_free(pr->cmdlist);
+ break;
+ }
+ return (pr->status);
+}
+
+enum cmd_parse_status
+cmd_parse_and_append(const char *s, struct cmd_parse_input *pi,
+ struct client *c, struct cmdq_state *state, char **error)
+{
+ struct cmd_parse_result *pr;
+ struct cmdq_item *item;
+
+ pr = cmd_parse_from_string(s, pi);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ if (error != NULL)
+ *error = pr->error;
+ else
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ item = cmdq_get_command(pr->cmdlist, state);
+ cmdq_append(c, item);
+ cmd_list_free(pr->cmdlist);
+ break;
+ }
+ return (pr->status);
+}
+
+struct cmd_parse_result *
+cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds;
+ char *cause;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ if (len == 0) {
+ pr.status = CMD_PARSE_SUCCESS;
+ pr.cmdlist = cmd_list_new();
+ return (&pr);
+ }
+
+ cmds = cmd_parse_do_buffer(buf, len, pi, &cause);
+ if (cmds == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cause;
+ return (&pr);
+ }
+ cmd_parse_build_commands(cmds, pi, &pr);
+ cmd_parse_free_commands(cmds);
+ return (&pr);
+}
+
+struct cmd_parse_result *
+cmd_parse_from_arguments(struct args_value *values, u_int count,
+ struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds;
+ struct cmd_parse_command *cmd;
+ struct cmd_parse_argument *arg;
+ u_int i;
+ char *copy;
+ size_t size;
+ int end;
+
+ /*
+ * The commands are already split up into arguments, so just separate
+ * into a set of commands by ';'.
+ */
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ cmds = cmd_parse_new_commands();
+
+ cmd = xcalloc(1, sizeof *cmd);
+ cmd->line = pi->line;
+ TAILQ_INIT(&cmd->arguments);
+
+ for (i = 0; i < count; i++) {
+ end = 0;
+ if (values[i].type == ARGS_STRING) {
+ copy = xstrdup(values[i].string);
+ size = strlen(copy);
+ if (size != 0 && copy[size - 1] == ';') {
+ copy[--size] = '\0';
+ if (size > 0 && copy[size - 1] == '\\')
+ copy[size - 1] = ';';
+ else
+ end = 1;
+ }
+ if (!end || size != 0) {
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_STRING;
+ arg->string = copy;
+ TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
+ }
+ } else if (values[i].type == ARGS_COMMANDS) {
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_PARSED_COMMANDS;
+ arg->cmdlist = values[i].cmdlist;
+ arg->cmdlist->references++;
+ TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
+ } else
+ fatalx("unknown argument type");
+ if (end) {
+ TAILQ_INSERT_TAIL(cmds, cmd, entry);
+ cmd = xcalloc(1, sizeof *cmd);
+ cmd->line = pi->line;
+ TAILQ_INIT(&cmd->arguments);
+ }
+ }
+ if (!TAILQ_EMPTY(&cmd->arguments))
+ TAILQ_INSERT_TAIL(cmds, cmd, entry);
+ else
+ free(cmd);
+
+ cmd_parse_build_commands(cmds, pi, &pr);
+ cmd_parse_free_commands(cmds);
+ return (&pr);
+}
+
+static int printflike(1, 2)
+yyerror(const char *fmt, ...)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ va_list ap;
+ char *error;
+
+ if (ps->error != NULL)
+ return (0);
+
+ va_start(ap, fmt);
+ xvasprintf(&error, fmt, ap);
+ va_end(ap);
+
+ ps->error = cmd_parse_get_error(pi->file, pi->line, error);
+ free(error);
+ return (0);
+}
+
+static int
+yylex_is_var(char ch, int first)
+{
+ if (ch == '=')
+ return (0);
+ if (first && isdigit((u_char)ch))
+ return (0);
+ return (isalnum((u_char)ch) || ch == '_');
+}
+
+static void
+yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
+{
+ if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
+ fatalx("buffer is too big");
+ *buf = xrealloc(*buf, (*len) + 1 + addlen);
+ memcpy((*buf) + *len, add, addlen);
+ (*len) += addlen;
+}
+
+static void
+yylex_append1(char **buf, size_t *len, char add)
+{
+ yylex_append(buf, len, &add, 1);
+}
+
+static int
+yylex_getc1(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int ch;
+
+ if (ps->f != NULL)
+ ch = getc(ps->f);
+ else {
+ if (ps->off == ps->len)
+ ch = EOF;
+ else
+ ch = ps->buf[ps->off++];
+ }
+ return (ch);
+}
+
+static void
+yylex_ungetc(int ch)
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (ps->f != NULL)
+ ungetc(ch, ps->f);
+ else if (ps->off > 0 && ch != EOF)
+ ps->off--;
+}
+
+static int
+yylex_getc(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int ch;
+
+ if (ps->escapes != 0) {
+ ps->escapes--;
+ return ('\\');
+ }
+ for (;;) {
+ ch = yylex_getc1();
+ if (ch == '\\') {
+ ps->escapes++;
+ continue;
+ }
+ if (ch == '\n' && (ps->escapes % 2) == 1) {
+ ps->input->line++;
+ ps->escapes--;
+ continue;
+ }
+
+ if (ps->escapes != 0) {
+ yylex_ungetc(ch);
+ ps->escapes--;
+ return ('\\');
+ }
+ return (ch);
+ }
+}
+
+static char *
+yylex_get_word(int ch)
+{
+ char *buf;
+ size_t len;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ do
+ yylex_append1(&buf, &len, ch);
+ while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
+ yylex_ungetc(ch);
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+}
+
+static int
+yylex(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ char *token, *cp;
+ int ch, next, condition;
+
+ if (ps->eol)
+ ps->input->line++;
+ ps->eol = 0;
+
+ condition = ps->condition;
+ ps->condition = 0;
+
+ for (;;) {
+ ch = yylex_getc();
+
+ if (ch == EOF) {
+ /*
+ * Ensure every file or string is terminated by a
+ * newline. This keeps the parser simpler and avoids
+ * having to add a newline to each string.
+ */
+ if (ps->eof)
+ break;
+ ps->eof = 1;
+ return ('\n');
+ }
+
+ if (ch == ' ' || ch == '\t') {
+ /*
+ * Ignore whitespace.
+ */
+ continue;
+ }
+
+ if (ch == '\n') {
+ /*
+ * End of line. Update the line number.
+ */
+ ps->eol = 1;
+ return ('\n');
+ }
+
+ if (ch == ';' || ch == '{' || ch == '}') {
+ /*
+ * A semicolon or { or } is itself.
+ */
+ return (ch);
+ }
+
+ if (ch == '#') {
+ /*
+ * #{ after a condition opens a format; anything else
+ * is a comment, ignore up to the end of the line.
+ */
+ next = yylex_getc();
+ if (condition && next == '{') {
+ yylval.token = yylex_format();
+ if (yylval.token == NULL)
+ return (ERROR);
+ return (FORMAT);
+ }
+ while (next != '\n' && next != EOF)
+ next = yylex_getc();
+ if (next == '\n') {
+ ps->input->line++;
+ return ('\n');
+ }
+ continue;
+ }
+
+ if (ch == '%') {
+ /*
+ * % is a condition unless it is all % or all numbers,
+ * then it is a token.
+ */
+ yylval.token = yylex_get_word('%');
+ for (cp = yylval.token; *cp != '\0'; cp++) {
+ if (*cp != '%' && !isdigit((u_char)*cp))
+ break;
+ }
+ if (*cp == '\0')
+ return (TOKEN);
+ ps->condition = 1;
+ if (strcmp(yylval.token, "%hidden") == 0) {
+ free(yylval.token);
+ return (HIDDEN);
+ }
+ if (strcmp(yylval.token, "%if") == 0) {
+ free(yylval.token);
+ return (IF);
+ }
+ if (strcmp(yylval.token, "%else") == 0) {
+ free(yylval.token);
+ return (ELSE);
+ }
+ if (strcmp(yylval.token, "%elif") == 0) {
+ free(yylval.token);
+ return (ELIF);
+ }
+ if (strcmp(yylval.token, "%endif") == 0) {
+ free(yylval.token);
+ return (ENDIF);
+ }
+ free(yylval.token);
+ return (ERROR);
+ }
+
+ /*
+ * Otherwise this is a token.
+ */
+ token = yylex_token(ch);
+ if (token == NULL)
+ return (ERROR);
+ yylval.token = token;
+
+ if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
+ for (cp = token + 1; *cp != '='; cp++) {
+ if (!yylex_is_var(*cp, 0))
+ break;
+ }
+ if (*cp == '=')
+ return (EQUALS);
+ }
+ return (TOKEN);
+ }
+ return (0);
+}
+
+static char *
+yylex_format(void)
+{
+ char *buf;
+ size_t len;
+ int ch, brackets = 1;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ yylex_append(&buf, &len, "#{", 2);
+ for (;;) {
+ if ((ch = yylex_getc()) == EOF || ch == '\n')
+ goto error;
+ if (ch == '#') {
+ if ((ch = yylex_getc()) == EOF || ch == '\n')
+ goto error;
+ if (ch == '{')
+ brackets++;
+ yylex_append1(&buf, &len, '#');
+ } else if (ch == '}') {
+ if (brackets != 0 && --brackets == 0) {
+ yylex_append1(&buf, &len, ch);
+ break;
+ }
+ }
+ yylex_append1(&buf, &len, ch);
+ }
+ if (brackets != 0)
+ goto error;
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+static int
+yylex_token_escape(char **buf, size_t *len)
+{
+ int ch, type, o2, o3, mlen;
+ u_int size, i, tmp;
+ char s[9], m[MB_LEN_MAX];
+
+ ch = yylex_getc();
+
+ if (ch >= '4' && ch <= '7') {
+ yyerror("invalid octal escape");
+ return (0);
+ }
+ if (ch >= '0' && ch <= '3') {
+ o2 = yylex_getc();
+ if (o2 >= '0' && o2 <= '7') {
+ o3 = yylex_getc();
+ if (o3 >= '0' && o3 <= '7') {
+ ch = 64 * (ch - '0') +
+ 8 * (o2 - '0') +
+ (o3 - '0');
+ yylex_append1(buf, len, ch);
+ return (1);
+ }
+ }
+ yyerror("invalid octal escape");
+ return (0);
+ }
+
+ switch (ch) {
+ case EOF:
+ return (0);
+ case 'a':
+ ch = '\a';
+ break;
+ case 'b':
+ ch = '\b';
+ break;
+ case 'e':
+ ch = '\033';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 's':
+ ch = ' ';
+ break;
+ case 'v':
+ ch = '\v';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'u':
+ type = 'u';
+ size = 4;
+ goto unicode;
+ case 'U':
+ type = 'U';
+ size = 8;
+ goto unicode;
+ }
+
+ yylex_append1(buf, len, ch);
+ return (1);
+
+unicode:
+ for (i = 0; i < size; i++) {
+ ch = yylex_getc();
+ if (ch == EOF || ch == '\n')
+ return (0);
+ if (!isxdigit((u_char)ch)) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ s[i] = ch;
+ }
+ s[i] = '\0';
+
+ if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
+ (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ mlen = wctomb(m, tmp);
+ if (mlen <= 0 || mlen > (int)sizeof m) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ yylex_append(buf, len, m, mlen);
+ return (1);
+}
+
+static int
+yylex_token_variable(char **buf, size_t *len)
+{
+ struct environ_entry *envent;
+ int ch, brackets = 0;
+ char name[1024];
+ size_t namelen = 0;
+ const char *value;
+
+ ch = yylex_getc();
+ if (ch == EOF)
+ return (0);
+ if (ch == '{')
+ brackets = 1;
+ else {
+ if (!yylex_is_var(ch, 1)) {
+ yylex_append1(buf, len, '$');
+ yylex_ungetc(ch);
+ return (1);
+ }
+ name[namelen++] = ch;
+ }
+
+ for (;;) {
+ ch = yylex_getc();
+ if (brackets && ch == '}')
+ break;
+ if (ch == EOF || !yylex_is_var(ch, 0)) {
+ if (!brackets) {
+ yylex_ungetc(ch);
+ break;
+ }
+ yyerror("invalid environment variable");
+ return (0);
+ }
+ if (namelen == (sizeof name) - 2) {
+ yyerror("environment variable is too long");
+ return (0);
+ }
+ name[namelen++] = ch;
+ }
+ name[namelen] = '\0';
+
+ envent = environ_find(global_environ, name);
+ if (envent != NULL && envent->value != NULL) {
+ value = envent->value;
+ log_debug("%s: %s -> %s", __func__, name, value);
+ yylex_append(buf, len, value, strlen(value));
+ }
+ return (1);
+}
+
+static int
+yylex_token_tilde(char **buf, size_t *len)
+{
+ struct environ_entry *envent;
+ int ch;
+ char name[1024];
+ size_t namelen = 0;
+ struct passwd *pw;
+ const char *home = NULL;
+
+ for (;;) {
+ ch = yylex_getc();
+ if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
+ yylex_ungetc(ch);
+ break;
+ }
+ if (namelen == (sizeof name) - 2) {
+ yyerror("user name is too long");
+ return (0);
+ }
+ name[namelen++] = ch;
+ }
+ name[namelen] = '\0';
+
+ if (*name == '\0') {
+ envent = environ_find(global_environ, "HOME");
+ if (envent != NULL && *envent->value != '\0')
+ home = envent->value;
+ else if ((pw = getpwuid(getuid())) != NULL)
+ home = pw->pw_dir;
+ } else {
+ if ((pw = getpwnam(name)) != NULL)
+ home = pw->pw_dir;
+ }
+ if (home == NULL)
+ return (0);
+
+ log_debug("%s: ~%s -> %s", __func__, name, home);
+ yylex_append(buf, len, home, strlen(home));
+ return (1);
+}
+
+static char *
+yylex_token(int ch)
+{
+ char *buf;
+ size_t len;
+ enum { START,
+ NONE,
+ DOUBLE_QUOTES,
+ SINGLE_QUOTES } state = NONE, last = START;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ for (;;) {
+ /* EOF or \n are always the end of the token. */
+ if (ch == EOF || (state == NONE && ch == '\n'))
+ break;
+
+ /* Whitespace or ; or } ends a token unless inside quotes. */
+ if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') &&
+ state == NONE)
+ break;
+
+ /*
+ * Spaces and comments inside quotes after \n are removed but
+ * the \n is left.
+ */
+ if (ch == '\n' && state != NONE) {
+ yylex_append1(&buf, &len, '\n');
+ while ((ch = yylex_getc()) == ' ' || ch == '\t')
+ /* nothing */;
+ if (ch != '#')
+ continue;
+ ch = yylex_getc();
+ if (strchr(",#{}:", ch) != NULL) {
+ yylex_ungetc(ch);
+ ch = '#';
+ } else {
+ while ((ch = yylex_getc()) != '\n' && ch != EOF)
+ /* nothing */;
+ }
+ continue;
+ }
+
+ /* \ ~ and $ are expanded except in single quotes. */
+ if (ch == '\\' && state != SINGLE_QUOTES) {
+ if (!yylex_token_escape(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '~' && last != state && state != SINGLE_QUOTES) {
+ if (!yylex_token_tilde(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '$' && state != SINGLE_QUOTES) {
+ if (!yylex_token_variable(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '}' && state == NONE)
+ goto error; /* unmatched (matched ones were handled) */
+
+ /* ' and " starts or end quotes (and is consumed). */
+ if (ch == '\'') {
+ if (state == NONE) {
+ state = SINGLE_QUOTES;
+ goto next;
+ }
+ if (state == SINGLE_QUOTES) {
+ state = NONE;
+ goto next;
+ }
+ }
+ if (ch == '"') {
+ if (state == NONE) {
+ state = DOUBLE_QUOTES;
+ goto next;
+ }
+ if (state == DOUBLE_QUOTES) {
+ state = NONE;
+ goto next;
+ }
+ }
+
+ /* Otherwise add the character to the buffer. */
+ yylex_append1(&buf, &len, ch);
+
+ skip:
+ last = state;
+
+ next:
+ ch = yylex_getc();
+ }
+ yylex_ungetc(ch);
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+
+error:
+ free(buf);
+ return (NULL);
+}
+#line 1458 "cmd-parse.c"
+/* allocate initial stack or double stack size, up to YYMAXDEPTH */
+static int yygrowstack(void)
+{
+ unsigned int newsize;
+ long sslen;
+ short *newss;
+ YYSTYPE *newvs;
+
+ if ((newsize = yystacksize) == 0)
+ newsize = YYINITSTACKSIZE;
+ else if (newsize >= YYMAXDEPTH)
+ return -1;
+ else if ((newsize *= 2) > YYMAXDEPTH)
+ newsize = YYMAXDEPTH;
+ sslen = yyssp - yyss;
+#ifdef SIZE_MAX
+#define YY_SIZE_MAX SIZE_MAX
+#else
+#define YY_SIZE_MAX 0xffffffffU
+#endif
+ if (newsize && YY_SIZE_MAX / newsize < sizeof *newss)
+ goto bail;
+ newss = (short *)realloc(yyss, newsize * sizeof *newss);
+ if (newss == NULL)
+ goto bail;
+ yyss = newss;
+ yyssp = newss + sslen;
+ if (newsize && YY_SIZE_MAX / newsize < sizeof *newvs)
+ goto bail;
+ newvs = (YYSTYPE *)realloc(yyvs, newsize * sizeof *newvs);
+ if (newvs == NULL)
+ goto bail;
+ yyvs = newvs;
+ yyvsp = newvs + sslen;
+ yystacksize = newsize;
+ yysslim = yyss + newsize - 1;
+ return 0;
+bail:
+ if (yyss)
+ free(yyss);
+ if (yyvs)
+ free(yyvs);
+ yyss = yyssp = NULL;
+ yyvs = yyvsp = NULL;
+ yystacksize = 0;
+ return -1;
+}
+
+#define YYABORT goto yyabort
+#define YYREJECT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+int
+yyparse(void)
+{
+ int yym, yyn, yystate;
+#if YYDEBUG
+ const char *yys;
+
+ if ((yys = getenv("YYDEBUG")))
+ {
+ yyn = *yys;
+ if (yyn >= '0' && yyn <= '9')
+ yydebug = yyn - '0';
+ }
+#endif /* YYDEBUG */
+
+ yynerrs = 0;
+ yyerrflag = 0;
+ yychar = (-1);
+
+ if (yyss == NULL && yygrowstack()) goto yyoverflow;
+ yyssp = yyss;
+ yyvsp = yyvs;
+ *yyssp = yystate = 0;
+
+yyloop:
+ if ((yyn = yydefred[yystate]) != 0) goto yyreduce;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("%sdebug: state %d, reading %d (%s)\n",
+ YYPREFIX, yystate, yychar, yys);
+ }
+#endif
+ }
+ if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: state %d, shifting to state %d\n",
+ YYPREFIX, yystate, yytable[yyn]);
+#endif
+ if (yyssp >= yysslim && yygrowstack())
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate = yytable[yyn];
+ *++yyvsp = yylval;
+ yychar = (-1);
+ if (yyerrflag > 0) --yyerrflag;
+ goto yyloop;
+ }
+ if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+ yyn = yytable[yyn];
+ goto yyreduce;
+ }
+ if (yyerrflag) goto yyinrecovery;
+#if defined(__GNUC__)
+ goto yynewerror;
+#endif
+yynewerror:
+ yyerror("syntax error");
+#if defined(__GNUC__)
+ goto yyerrlab;
+#endif
+yyerrlab:
+ ++yynerrs;
+yyinrecovery:
+ if (yyerrflag < 3)
+ {
+ yyerrflag = 3;
+ for (;;)
+ {
+ if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: state %d, error recovery shifting\
+ to state %d\n", YYPREFIX, *yyssp, yytable[yyn]);
+#endif
+ if (yyssp >= yysslim && yygrowstack())
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate = yytable[yyn];
+ *++yyvsp = yylval;
+ goto yyloop;
+ }
+ else
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: error recovery discarding state %d\n",
+ YYPREFIX, *yyssp);
+#endif
+ if (yyssp <= yyss) goto yyabort;
+ --yyssp;
+ --yyvsp;
+ }
+ }
+ }
+ else
+ {
+ if (yychar == 0) goto yyabort;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("%sdebug: state %d, error recovery discards token %d (%s)\n",
+ YYPREFIX, yystate, yychar, yys);
+ }
+#endif
+ yychar = (-1);
+ goto yyloop;
+ }
+yyreduce:
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: state %d, reducing by rule %d (%s)\n",
+ YYPREFIX, yystate, yyn, yyrule[yyn]);
+#endif
+ yym = yylen[yyn];
+ if (yym)
+ yyval = yyvsp[1-yym];
+ else
+ memset(&yyval, 0, sizeof yyval);
+ switch (yyn)
+ {
+case 2:
+#line 136 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ ps->commands = yyvsp[0].commands;
+ }
+break;
+case 3:
+#line 143 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[-1].commands;
+ }
+break;
+case 4:
+#line 147 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[-2].commands;
+ TAILQ_CONCAT(yyval.commands, yyvsp[-1].commands, entry);
+ free(yyvsp[-1].commands);
+ }
+break;
+case 5:
+#line 154 "cmd-parse.y"
+{
+ yyval.commands = xmalloc (sizeof *yyval.commands);
+ TAILQ_INIT(yyval.commands);
+ }
+break;
+case 6:
+#line 159 "cmd-parse.y"
+{
+ yyval.commands = xmalloc (sizeof *yyval.commands);
+ TAILQ_INIT(yyval.commands);
+ }
+break;
+case 7:
+#line 164 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (ps->scope == NULL || ps->scope->flag)
+ yyval.commands = yyvsp[0].commands;
+ else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[0].commands);
+ }
+ }
+break;
+case 8:
+#line 175 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (ps->scope == NULL || ps->scope->flag)
+ yyval.commands = yyvsp[0].commands;
+ else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[0].commands);
+ }
+ }
+break;
+case 9:
+#line 187 "cmd-parse.y"
+{
+ yyval.token = yyvsp[0].token;
+ }
+break;
+case 10:
+#line 191 "cmd-parse.y"
+{
+ yyval.token = yyvsp[0].token;
+ }
+break;
+case 11:
+#line 196 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ struct format_tree *ft;
+ struct client *c = pi->c;
+ struct cmd_find_state *fsp;
+ struct cmd_find_state fs;
+ int flags = FORMAT_NOJOBS;
+
+ if (cmd_find_valid_state(&pi->fs))
+ fsp = &pi->fs;
+ else {
+ cmd_find_from_client(&fs, c, 0);
+ fsp = &fs;
+ }
+ ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
+ format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
+
+ yyval.token = format_expand(ft, yyvsp[0].token);
+ format_free(ft);
+ free(yyvsp[0].token);
+ }
+break;
+case 14:
+#line 223 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int flags = ps->input->flags;
+
+ if ((~flags & CMD_PARSE_PARSEONLY) &&
+ (ps->scope == NULL || ps->scope->flag))
+ environ_put(global_environ, yyvsp[0].token, 0);
+ free(yyvsp[0].token);
+ }
+break;
+case 15:
+#line 234 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int flags = ps->input->flags;
+
+ if ((~flags & CMD_PARSE_PARSEONLY) &&
+ (ps->scope == NULL || ps->scope->flag))
+ environ_put(global_environ, yyvsp[0].token, ENVIRON_HIDDEN);
+ free(yyvsp[0].token);
+ }
+break;
+case 16:
+#line 245 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ yyval.flag = scope->flag = format_true(yyvsp[0].token);
+ free(yyvsp[0].token);
+
+ if (ps->scope != NULL)
+ TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
+ ps->scope = scope;
+ }
+break;
+case 17:
+#line 259 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ scope->flag = !ps->scope->flag;
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+break;
+case 18:
+#line 271 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ yyval.flag = scope->flag = format_true(yyvsp[0].token);
+ free(yyvsp[0].token);
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+break;
+case 19:
+#line 284 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ free(ps->scope);
+ ps->scope = TAILQ_FIRST(&ps->stack);
+ if (ps->scope != NULL)
+ TAILQ_REMOVE(&ps->stack, ps->scope, entry);
+ }
+break;
+case 20:
+#line 294 "cmd-parse.y"
+{
+ if (yyvsp[-3].flag)
+ yyval.commands = yyvsp[-1].commands;
+ else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ }
+ }
+break;
+case 21:
+#line 303 "cmd-parse.y"
+{
+ if (yyvsp[-6].flag) {
+ yyval.commands = yyvsp[-4].commands;
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else {
+ yyval.commands = yyvsp[-1].commands;
+ cmd_parse_free_commands(yyvsp[-4].commands);
+ }
+ }
+break;
+case 22:
+#line 313 "cmd-parse.y"
+{
+ if (yyvsp[-4].flag) {
+ yyval.commands = yyvsp[-2].commands;
+ cmd_parse_free_commands(yyvsp[-1].elif.commands);
+ } else if (yyvsp[-1].elif.flag) {
+ yyval.commands = yyvsp[-1].elif.commands;
+ cmd_parse_free_commands(yyvsp[-2].commands);
+ } else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-2].commands);
+ cmd_parse_free_commands(yyvsp[-1].elif.commands);
+ }
+ }
+break;
+case 23:
+#line 327 "cmd-parse.y"
+{
+ if (yyvsp[-7].flag) {
+ yyval.commands = yyvsp[-5].commands;
+ cmd_parse_free_commands(yyvsp[-4].elif.commands);
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else if (yyvsp[-4].elif.flag) {
+ yyval.commands = yyvsp[-4].elif.commands;
+ cmd_parse_free_commands(yyvsp[-5].commands);
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else {
+ yyval.commands = yyvsp[-1].commands;
+ cmd_parse_free_commands(yyvsp[-5].commands);
+ cmd_parse_free_commands(yyvsp[-4].elif.commands);
+ }
+ }
+break;
+case 24:
+#line 344 "cmd-parse.y"
+{
+ if (yyvsp[-2].flag) {
+ yyval.elif.flag = 1;
+ yyval.elif.commands = yyvsp[0].commands;
+ } else {
+ yyval.elif.flag = 0;
+ yyval.elif.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[0].commands);
+ }
+ }
+break;
+case 25:
+#line 355 "cmd-parse.y"
+{
+ if (yyvsp[-3].flag) {
+ yyval.elif.flag = 1;
+ yyval.elif.commands = yyvsp[-1].commands;
+ cmd_parse_free_commands(yyvsp[0].elif.commands);
+ } else if (yyvsp[0].elif.flag) {
+ yyval.elif.flag = 1;
+ yyval.elif.commands = yyvsp[0].elif.commands;
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else {
+ yyval.elif.flag = 0;
+ yyval.elif.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ cmd_parse_free_commands(yyvsp[0].elif.commands);
+ }
+ }
+break;
+case 26:
+#line 373 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ yyval.commands = cmd_parse_new_commands();
+ if (!TAILQ_EMPTY(&yyvsp[0].command->arguments) &&
+ (ps->scope == NULL || ps->scope->flag))
+ TAILQ_INSERT_TAIL(yyval.commands, yyvsp[0].command, entry);
+ else
+ cmd_parse_free_command(yyvsp[0].command);
+ }
+break;
+case 27:
+#line 384 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[-1].commands;
+ }
+break;
+case 28:
+#line 388 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[-2].commands;
+ TAILQ_CONCAT(yyval.commands, yyvsp[0].commands, entry);
+ free(yyvsp[0].commands);
+ }
+break;
+case 29:
+#line 394 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (!TAILQ_EMPTY(&yyvsp[0].command->arguments) &&
+ (ps->scope == NULL || ps->scope->flag)) {
+ yyval.commands = yyvsp[-2].commands;
+ TAILQ_INSERT_TAIL(yyval.commands, yyvsp[0].command, entry);
+ } else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-2].commands);
+ cmd_parse_free_command(yyvsp[0].command);
+ }
+ }
+break;
+case 30:
+#line 408 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[0].commands;
+ }
+break;
+case 31:
+#line 413 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ yyval.command = xcalloc(1, sizeof *yyval.command);
+ yyval.command->line = ps->input->line;
+ TAILQ_INIT(&yyval.command->arguments);
+ }
+break;
+case 32:
+#line 421 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_argument *arg;
+
+ yyval.command = xcalloc(1, sizeof *yyval.command);
+ yyval.command->line = ps->input->line;
+ TAILQ_INIT(&yyval.command->arguments);
+
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_STRING;
+ arg->string = yyvsp[0].token;
+ TAILQ_INSERT_HEAD(&yyval.command->arguments, arg, entry);
+ }
+break;
+case 33:
+#line 435 "cmd-parse.y"
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_argument *arg;
+
+ yyval.command = xcalloc(1, sizeof *yyval.command);
+ yyval.command->line = ps->input->line;
+ TAILQ_INIT(&yyval.command->arguments);
+
+ TAILQ_CONCAT(&yyval.command->arguments, yyvsp[0].arguments, entry);
+ free(yyvsp[0].arguments);
+
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_STRING;
+ arg->string = yyvsp[-1].token;
+ TAILQ_INSERT_HEAD(&yyval.command->arguments, arg, entry);
+ }
+break;
+case 34:
+#line 453 "cmd-parse.y"
+{
+ if (yyvsp[-2].flag)
+ yyval.commands = yyvsp[-1].commands;
+ else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ }
+ }
+break;
+case 35:
+#line 462 "cmd-parse.y"
+{
+ if (yyvsp[-4].flag) {
+ yyval.commands = yyvsp[-3].commands;
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else {
+ yyval.commands = yyvsp[-1].commands;
+ cmd_parse_free_commands(yyvsp[-3].commands);
+ }
+ }
+break;
+case 36:
+#line 472 "cmd-parse.y"
+{
+ if (yyvsp[-3].flag) {
+ yyval.commands = yyvsp[-2].commands;
+ cmd_parse_free_commands(yyvsp[-1].elif.commands);
+ } else if (yyvsp[-1].elif.flag) {
+ yyval.commands = yyvsp[-1].elif.commands;
+ cmd_parse_free_commands(yyvsp[-2].commands);
+ } else {
+ yyval.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-2].commands);
+ cmd_parse_free_commands(yyvsp[-1].elif.commands);
+ }
+ }
+break;
+case 37:
+#line 486 "cmd-parse.y"
+{
+ if (yyvsp[-5].flag) {
+ yyval.commands = yyvsp[-4].commands;
+ cmd_parse_free_commands(yyvsp[-3].elif.commands);
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else if (yyvsp[-3].elif.flag) {
+ yyval.commands = yyvsp[-3].elif.commands;
+ cmd_parse_free_commands(yyvsp[-4].commands);
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else {
+ yyval.commands = yyvsp[-1].commands;
+ cmd_parse_free_commands(yyvsp[-4].commands);
+ cmd_parse_free_commands(yyvsp[-3].elif.commands);
+ }
+ }
+break;
+case 38:
+#line 503 "cmd-parse.y"
+{
+ if (yyvsp[-1].flag) {
+ yyval.elif.flag = 1;
+ yyval.elif.commands = yyvsp[0].commands;
+ } else {
+ yyval.elif.flag = 0;
+ yyval.elif.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[0].commands);
+ }
+ }
+break;
+case 39:
+#line 514 "cmd-parse.y"
+{
+ if (yyvsp[-2].flag) {
+ yyval.elif.flag = 1;
+ yyval.elif.commands = yyvsp[-1].commands;
+ cmd_parse_free_commands(yyvsp[0].elif.commands);
+ } else if (yyvsp[0].elif.flag) {
+ yyval.elif.flag = 1;
+ yyval.elif.commands = yyvsp[0].elif.commands;
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ } else {
+ yyval.elif.flag = 0;
+ yyval.elif.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands(yyvsp[-1].commands);
+ cmd_parse_free_commands(yyvsp[0].elif.commands);
+ }
+ }
+break;
+case 40:
+#line 532 "cmd-parse.y"
+{
+ yyval.arguments = xcalloc(1, sizeof *yyval.arguments);
+ TAILQ_INIT(yyval.arguments);
+
+ TAILQ_INSERT_HEAD(yyval.arguments, yyvsp[0].argument, entry);
+ }
+break;
+case 41:
+#line 539 "cmd-parse.y"
+{
+ TAILQ_INSERT_HEAD(yyvsp[0].arguments, yyvsp[-1].argument, entry);
+ yyval.arguments = yyvsp[0].arguments;
+ }
+break;
+case 42:
+#line 545 "cmd-parse.y"
+{
+ yyval.argument = xcalloc(1, sizeof *yyval.argument);
+ yyval.argument->type = CMD_PARSE_STRING;
+ yyval.argument->string = yyvsp[0].token;
+ }
+break;
+case 43:
+#line 551 "cmd-parse.y"
+{
+ yyval.argument = xcalloc(1, sizeof *yyval.argument);
+ yyval.argument->type = CMD_PARSE_STRING;
+ yyval.argument->string = yyvsp[0].token;
+ }
+break;
+case 44:
+#line 557 "cmd-parse.y"
+{
+ yyval.argument = xcalloc(1, sizeof *yyval.argument);
+ yyval.argument->type = CMD_PARSE_COMMANDS;
+ yyval.argument->commands = yyvsp[0].commands;
+ }
+break;
+case 45:
+#line 564 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[-1].commands;
+ }
+break;
+case 46:
+#line 568 "cmd-parse.y"
+{
+ yyval.commands = yyvsp[-2].commands;
+ TAILQ_CONCAT(yyval.commands, yyvsp[-1].commands, entry);
+ free(yyvsp[-1].commands);
+ }
+break;
+#line 2152 "cmd-parse.c"
+ }
+ yyssp -= yym;
+ yystate = *yyssp;
+ yyvsp -= yym;
+ yym = yylhs[yyn];
+ if (yystate == 0 && yym == 0)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: after reduction, shifting from state 0 to\
+ state %d\n", YYPREFIX, YYFINAL);
+#endif
+ yystate = YYFINAL;
+ *++yyssp = YYFINAL;
+ *++yyvsp = yyval;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("%sdebug: state %d, reading %d (%s)\n",
+ YYPREFIX, YYFINAL, yychar, yys);
+ }
+#endif
+ }
+ if (yychar == 0) goto yyaccept;
+ goto yyloop;
+ }
+ if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yystate)
+ yystate = yytable[yyn];
+ else
+ yystate = yydgoto[yym];
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: after reduction, shifting from state %d \
+to state %d\n", YYPREFIX, *yyssp, yystate);
+#endif
+ if (yyssp >= yysslim && yygrowstack())
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = yystate;
+ *++yyvsp = yyval;
+ goto yyloop;
+yyoverflow:
+ yyerror("yacc stack overflow");
+yyabort:
+ if (yyss)
+ free(yyss);
+ if (yyvs)
+ free(yyvs);
+ yyss = yyssp = NULL;
+ yyvs = yyvsp = NULL;
+ yystacksize = 0;
+ return (1);
+yyaccept:
+ if (yyss)
+ free(yyss);
+ if (yyvs)
+ free(yyvs);
+ yyss = yyssp = NULL;
+ yyvs = yyvsp = NULL;
+ yystacksize = 0;
+ return (0);
+}
diff --git a/cmd-parse.y b/cmd-parse.y
new file mode 100644
index 0000000..1d69277
--- /dev/null
+++ b/cmd-parse.y
@@ -0,0 +1,1705 @@
+/* $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 <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "tmux.h"
+
+static int yylex(void);
+static int yyparse(void);
+static int printflike(1,2) yyerror(const char *, ...);
+
+static char *yylex_token(int);
+static char *yylex_format(void);
+
+struct cmd_parse_scope {
+ int flag;
+ TAILQ_ENTRY (cmd_parse_scope) entry;
+};
+
+enum cmd_parse_argument_type {
+ CMD_PARSE_STRING,
+ CMD_PARSE_COMMANDS,
+ CMD_PARSE_PARSED_COMMANDS
+};
+
+struct cmd_parse_argument {
+ enum cmd_parse_argument_type type;
+ char *string;
+ struct cmd_parse_commands *commands;
+ struct cmd_list *cmdlist;
+
+ TAILQ_ENTRY(cmd_parse_argument) entry;
+};
+TAILQ_HEAD(cmd_parse_arguments, cmd_parse_argument);
+
+struct cmd_parse_command {
+ u_int line;
+ struct cmd_parse_arguments arguments;
+
+ TAILQ_ENTRY(cmd_parse_command) entry;
+};
+TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
+
+struct cmd_parse_state {
+ FILE *f;
+
+ const char *buf;
+ size_t len;
+ size_t off;
+
+ int condition;
+ int eol;
+ int eof;
+ struct cmd_parse_input *input;
+ u_int escapes;
+
+ char *error;
+ struct cmd_parse_commands *commands;
+
+ struct cmd_parse_scope *scope;
+ TAILQ_HEAD(, cmd_parse_scope) stack;
+};
+static struct cmd_parse_state parse_state;
+
+static char *cmd_parse_get_error(const char *, u_int, const char *);
+static void cmd_parse_free_command(struct cmd_parse_command *);
+static struct cmd_parse_commands *cmd_parse_new_commands(void);
+static void cmd_parse_free_commands(struct cmd_parse_commands *);
+static void cmd_parse_build_commands(struct cmd_parse_commands *,
+ struct cmd_parse_input *, struct cmd_parse_result *);
+static void cmd_parse_print_commands(struct cmd_parse_input *,
+ struct cmd_list *);
+
+%}
+
+%union
+{
+ char *token;
+ struct cmd_parse_arguments *arguments;
+ struct cmd_parse_argument *argument;
+ int flag;
+ struct {
+ int flag;
+ struct cmd_parse_commands *commands;
+ } elif;
+ struct cmd_parse_commands *commands;
+ struct cmd_parse_command *command;
+}
+
+%token ERROR
+%token HIDDEN
+%token IF
+%token ELSE
+%token ELIF
+%token ENDIF
+%token <token> FORMAT TOKEN EQUALS
+
+%type <token> expanded format
+%type <arguments> arguments
+%type <argument> argument
+%type <flag> if_open if_elif
+%type <elif> elif elif1
+%type <commands> argument_statements statements statement
+%type <commands> commands condition condition1
+%type <command> command
+
+%%
+
+lines : /* empty */
+ | statements
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ ps->commands = $1;
+ }
+
+statements : statement '\n'
+ {
+ $$ = $1;
+ }
+ | statements statement '\n'
+ {
+ $$ = $1;
+ TAILQ_CONCAT($$, $2, entry);
+ free($2);
+ }
+
+statement : /* empty */
+ {
+ $$ = xmalloc (sizeof *$$);
+ TAILQ_INIT($$);
+ }
+ | hidden_assignment
+ {
+ $$ = xmalloc (sizeof *$$);
+ TAILQ_INIT($$);
+ }
+ | condition
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (ps->scope == NULL || ps->scope->flag)
+ $$ = $1;
+ else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($1);
+ }
+ }
+ | commands
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (ps->scope == NULL || ps->scope->flag)
+ $$ = $1;
+ else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($1);
+ }
+ }
+
+format : FORMAT
+ {
+ $$ = $1;
+ }
+ | TOKEN
+ {
+ $$ = $1;
+ }
+
+expanded : format
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ struct format_tree *ft;
+ struct client *c = pi->c;
+ struct cmd_find_state *fsp;
+ struct cmd_find_state fs;
+ int flags = FORMAT_NOJOBS;
+
+ if (cmd_find_valid_state(&pi->fs))
+ fsp = &pi->fs;
+ else {
+ cmd_find_from_client(&fs, c, 0);
+ fsp = &fs;
+ }
+ ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
+ format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
+
+ $$ = format_expand(ft, $1);
+ format_free(ft);
+ free($1);
+ }
+
+optional_assignment : /* empty */
+ | assignment
+
+assignment : EQUALS
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ int flags = ps->input->flags;
+
+ if ((~flags & CMD_PARSE_PARSEONLY) &&
+ (ps->scope == NULL || ps->scope->flag))
+ environ_put(global_environ, $1, 0);
+ free($1);
+ }
+
+hidden_assignment : HIDDEN EQUALS
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ int flags = ps->input->flags;
+
+ if ((~flags & CMD_PARSE_PARSEONLY) &&
+ (ps->scope == NULL || ps->scope->flag))
+ environ_put(global_environ, $2, ENVIRON_HIDDEN);
+ free($2);
+ }
+
+if_open : IF expanded
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ $$ = scope->flag = format_true($2);
+ free($2);
+
+ if (ps->scope != NULL)
+ TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
+ ps->scope = scope;
+ }
+
+if_else : ELSE
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ scope->flag = !ps->scope->flag;
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+
+if_elif : ELIF expanded
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ $$ = scope->flag = format_true($2);
+ free($2);
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+
+if_close : ENDIF
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ free(ps->scope);
+ ps->scope = TAILQ_FIRST(&ps->stack);
+ if (ps->scope != NULL)
+ TAILQ_REMOVE(&ps->stack, ps->scope, entry);
+ }
+
+condition : if_open '\n' statements if_close
+ {
+ if ($1)
+ $$ = $3;
+ else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($3);
+ }
+ }
+ | if_open '\n' statements if_else '\n' statements if_close
+ {
+ if ($1) {
+ $$ = $3;
+ cmd_parse_free_commands($6);
+ } else {
+ $$ = $6;
+ cmd_parse_free_commands($3);
+ }
+ }
+ | if_open '\n' statements elif if_close
+ {
+ if ($1) {
+ $$ = $3;
+ cmd_parse_free_commands($4.commands);
+ } else if ($4.flag) {
+ $$ = $4.commands;
+ cmd_parse_free_commands($3);
+ } else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($3);
+ cmd_parse_free_commands($4.commands);
+ }
+ }
+ | if_open '\n' statements elif if_else '\n' statements if_close
+ {
+ if ($1) {
+ $$ = $3;
+ cmd_parse_free_commands($4.commands);
+ cmd_parse_free_commands($7);
+ } else if ($4.flag) {
+ $$ = $4.commands;
+ cmd_parse_free_commands($3);
+ cmd_parse_free_commands($7);
+ } else {
+ $$ = $7;
+ cmd_parse_free_commands($3);
+ cmd_parse_free_commands($4.commands);
+ }
+ }
+
+elif : if_elif '\n' statements
+ {
+ if ($1) {
+ $$.flag = 1;
+ $$.commands = $3;
+ } else {
+ $$.flag = 0;
+ $$.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands($3);
+ }
+ }
+ | if_elif '\n' statements elif
+ {
+ if ($1) {
+ $$.flag = 1;
+ $$.commands = $3;
+ cmd_parse_free_commands($4.commands);
+ } else if ($4.flag) {
+ $$.flag = 1;
+ $$.commands = $4.commands;
+ cmd_parse_free_commands($3);
+ } else {
+ $$.flag = 0;
+ $$.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands($3);
+ cmd_parse_free_commands($4.commands);
+ }
+ }
+
+commands : command
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ $$ = cmd_parse_new_commands();
+ if (!TAILQ_EMPTY(&$1->arguments) &&
+ (ps->scope == NULL || ps->scope->flag))
+ TAILQ_INSERT_TAIL($$, $1, entry);
+ else
+ cmd_parse_free_command($1);
+ }
+ | commands ';'
+ {
+ $$ = $1;
+ }
+ | commands ';' condition1
+ {
+ $$ = $1;
+ TAILQ_CONCAT($$, $3, entry);
+ free($3);
+ }
+ | commands ';' command
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (!TAILQ_EMPTY(&$3->arguments) &&
+ (ps->scope == NULL || ps->scope->flag)) {
+ $$ = $1;
+ TAILQ_INSERT_TAIL($$, $3, entry);
+ } else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($1);
+ cmd_parse_free_command($3);
+ }
+ }
+ | condition1
+ {
+ $$ = $1;
+ }
+
+command : assignment
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ $$ = xcalloc(1, sizeof *$$);
+ $$->line = ps->input->line;
+ TAILQ_INIT(&$$->arguments);
+ }
+ | optional_assignment TOKEN
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_argument *arg;
+
+ $$ = xcalloc(1, sizeof *$$);
+ $$->line = ps->input->line;
+ TAILQ_INIT(&$$->arguments);
+
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_STRING;
+ arg->string = $2;
+ TAILQ_INSERT_HEAD(&$$->arguments, arg, entry);
+ }
+ | optional_assignment TOKEN arguments
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_argument *arg;
+
+ $$ = xcalloc(1, sizeof *$$);
+ $$->line = ps->input->line;
+ TAILQ_INIT(&$$->arguments);
+
+ TAILQ_CONCAT(&$$->arguments, $3, entry);
+ free($3);
+
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_STRING;
+ arg->string = $2;
+ TAILQ_INSERT_HEAD(&$$->arguments, arg, entry);
+ }
+
+condition1 : if_open commands if_close
+ {
+ if ($1)
+ $$ = $2;
+ else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($2);
+ }
+ }
+ | if_open commands if_else commands if_close
+ {
+ if ($1) {
+ $$ = $2;
+ cmd_parse_free_commands($4);
+ } else {
+ $$ = $4;
+ cmd_parse_free_commands($2);
+ }
+ }
+ | if_open commands elif1 if_close
+ {
+ if ($1) {
+ $$ = $2;
+ cmd_parse_free_commands($3.commands);
+ } else if ($3.flag) {
+ $$ = $3.commands;
+ cmd_parse_free_commands($2);
+ } else {
+ $$ = cmd_parse_new_commands();
+ cmd_parse_free_commands($2);
+ cmd_parse_free_commands($3.commands);
+ }
+ }
+ | if_open commands elif1 if_else commands if_close
+ {
+ if ($1) {
+ $$ = $2;
+ cmd_parse_free_commands($3.commands);
+ cmd_parse_free_commands($5);
+ } else if ($3.flag) {
+ $$ = $3.commands;
+ cmd_parse_free_commands($2);
+ cmd_parse_free_commands($5);
+ } else {
+ $$ = $5;
+ cmd_parse_free_commands($2);
+ cmd_parse_free_commands($3.commands);
+ }
+ }
+
+elif1 : if_elif commands
+ {
+ if ($1) {
+ $$.flag = 1;
+ $$.commands = $2;
+ } else {
+ $$.flag = 0;
+ $$.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands($2);
+ }
+ }
+ | if_elif commands elif1
+ {
+ if ($1) {
+ $$.flag = 1;
+ $$.commands = $2;
+ cmd_parse_free_commands($3.commands);
+ } else if ($3.flag) {
+ $$.flag = 1;
+ $$.commands = $3.commands;
+ cmd_parse_free_commands($2);
+ } else {
+ $$.flag = 0;
+ $$.commands = cmd_parse_new_commands();
+ cmd_parse_free_commands($2);
+ cmd_parse_free_commands($3.commands);
+ }
+ }
+
+arguments : argument
+ {
+ $$ = xcalloc(1, sizeof *$$);
+ TAILQ_INIT($$);
+
+ TAILQ_INSERT_HEAD($$, $1, entry);
+ }
+ | argument arguments
+ {
+ TAILQ_INSERT_HEAD($2, $1, entry);
+ $$ = $2;
+ }
+
+argument : TOKEN
+ {
+ $$ = xcalloc(1, sizeof *$$);
+ $$->type = CMD_PARSE_STRING;
+ $$->string = $1;
+ }
+ | EQUALS
+ {
+ $$ = xcalloc(1, sizeof *$$);
+ $$->type = CMD_PARSE_STRING;
+ $$->string = $1;
+ }
+ | '{' argument_statements
+ {
+ $$ = xcalloc(1, sizeof *$$);
+ $$->type = CMD_PARSE_COMMANDS;
+ $$->commands = $2;
+ }
+
+argument_statements : statement '}'
+ {
+ $$ = $1;
+ }
+ | statements statement '}'
+ {
+ $$ = $1;
+ TAILQ_CONCAT($$, $2, entry);
+ free($2);
+ }
+
+%%
+
+static char *
+cmd_parse_get_error(const char *file, u_int line, const char *error)
+{
+ char *s;
+
+ if (file == NULL)
+ s = xstrdup(error);
+ else
+ xasprintf(&s, "%s:%u: %s", file, line, error);
+ return (s);
+}
+
+static void
+cmd_parse_print_commands(struct cmd_parse_input *pi, struct cmd_list *cmdlist)
+{
+ char *s;
+
+ if (pi->item == NULL || (~pi->flags & CMD_PARSE_VERBOSE))
+ return;
+ s = cmd_list_print(cmdlist, 0);
+ if (pi->file != NULL)
+ cmdq_print(pi->item, "%s:%u: %s", pi->file, pi->line, s);
+ else
+ cmdq_print(pi->item, "%u: %s", pi->line, s);
+ free(s);
+}
+
+static void
+cmd_parse_free_argument(struct cmd_parse_argument *arg)
+{
+ switch (arg->type) {
+ case CMD_PARSE_STRING:
+ free(arg->string);
+ break;
+ case CMD_PARSE_COMMANDS:
+ cmd_parse_free_commands(arg->commands);
+ break;
+ case CMD_PARSE_PARSED_COMMANDS:
+ cmd_list_free(arg->cmdlist);
+ break;
+ }
+ free(arg);
+}
+
+static void
+cmd_parse_free_arguments(struct cmd_parse_arguments *args)
+{
+ struct cmd_parse_argument *arg, *arg1;
+
+ TAILQ_FOREACH_SAFE(arg, args, entry, arg1) {
+ TAILQ_REMOVE(args, arg, entry);
+ cmd_parse_free_argument(arg);
+ }
+}
+
+static void
+cmd_parse_free_command(struct cmd_parse_command *cmd)
+{
+ cmd_parse_free_arguments(&cmd->arguments);
+ free(cmd);
+}
+
+static struct cmd_parse_commands *
+cmd_parse_new_commands(void)
+{
+ struct cmd_parse_commands *cmds;
+
+ cmds = xmalloc(sizeof *cmds);
+ TAILQ_INIT(cmds);
+ return (cmds);
+}
+
+static void
+cmd_parse_free_commands(struct cmd_parse_commands *cmds)
+{
+ struct cmd_parse_command *cmd, *cmd1;
+
+ TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
+ TAILQ_REMOVE(cmds, cmd, entry);
+ cmd_parse_free_command(cmd);
+ }
+ free(cmds);
+}
+
+static struct cmd_parse_commands *
+cmd_parse_run_parser(char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope, *scope1;
+ int retval;
+
+ ps->commands = NULL;
+ TAILQ_INIT(&ps->stack);
+
+ retval = yyparse();
+ TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
+ TAILQ_REMOVE(&ps->stack, scope, entry);
+ free(scope);
+ }
+ if (retval != 0) {
+ *cause = ps->error;
+ return (NULL);
+ }
+
+ if (ps->commands == NULL)
+ return (cmd_parse_new_commands());
+ return (ps->commands);
+}
+
+static struct cmd_parse_commands *
+cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ memset(ps, 0, sizeof *ps);
+ ps->input = pi;
+ ps->f = f;
+ return (cmd_parse_run_parser(cause));
+}
+
+static struct cmd_parse_commands *
+cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi,
+ char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ memset(ps, 0, sizeof *ps);
+ ps->input = pi;
+ ps->buf = buf;
+ ps->len = len;
+ return (cmd_parse_run_parser(cause));
+}
+
+static void
+cmd_parse_log_commands(struct cmd_parse_commands *cmds, const char *prefix)
+{
+ struct cmd_parse_command *cmd;
+ struct cmd_parse_argument *arg;
+ u_int i, j;
+ char *s;
+
+ i = 0;
+ TAILQ_FOREACH(cmd, cmds, entry) {
+ j = 0;
+ TAILQ_FOREACH(arg, &cmd->arguments, entry) {
+ switch (arg->type) {
+ case CMD_PARSE_STRING:
+ log_debug("%s %u:%u: %s", prefix, i, j,
+ arg->string);
+ break;
+ case CMD_PARSE_COMMANDS:
+ xasprintf(&s, "%s %u:%u", prefix, i, j);
+ cmd_parse_log_commands(arg->commands, s);
+ free(s);
+ break;
+ case CMD_PARSE_PARSED_COMMANDS:
+ s = cmd_list_print(arg->cmdlist, 0);
+ log_debug("%s %u:%u: %s", prefix, i, j, s);
+ free(s);
+ break;
+ }
+ j++;
+ }
+ i++;
+ }
+}
+
+static int
+cmd_parse_expand_alias(struct cmd_parse_command *cmd,
+ struct cmd_parse_input *pi, struct cmd_parse_result *pr)
+{
+ struct cmd_parse_argument *arg, *arg1, *first;
+ struct cmd_parse_commands *cmds;
+ struct cmd_parse_command *last;
+ char *alias, *name, *cause;
+
+ if (pi->flags & CMD_PARSE_NOALIAS)
+ return (0);
+ memset(pr, 0, sizeof *pr);
+
+ first = TAILQ_FIRST(&cmd->arguments);
+ if (first == NULL || first->type != CMD_PARSE_STRING) {
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ return (1);
+ }
+ name = first->string;
+
+ alias = cmd_get_alias(name);
+ if (alias == NULL)
+ return (0);
+ log_debug("%s: %u alias %s = %s", __func__, pi->line, name, alias);
+
+ cmds = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause);
+ free(alias);
+ if (cmds == NULL) {
+ pr->status = CMD_PARSE_ERROR;
+ pr->error = cause;
+ return (1);
+ }
+
+ last = TAILQ_LAST(cmds, cmd_parse_commands);
+ if (last == NULL) {
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ return (1);
+ }
+
+ TAILQ_REMOVE(&cmd->arguments, first, entry);
+ cmd_parse_free_argument(first);
+
+ TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) {
+ TAILQ_REMOVE(&cmd->arguments, arg, entry);
+ TAILQ_INSERT_TAIL(&last->arguments, arg, entry);
+ }
+ cmd_parse_log_commands(cmds, __func__);
+
+ pi->flags |= CMD_PARSE_NOALIAS;
+ cmd_parse_build_commands(cmds, pi, pr);
+ pi->flags &= ~CMD_PARSE_NOALIAS;
+ return (1);
+}
+
+static void
+cmd_parse_build_command(struct cmd_parse_command *cmd,
+ struct cmd_parse_input *pi, struct cmd_parse_result *pr)
+{
+ struct cmd_parse_argument *arg;
+ struct cmd *add;
+ char *cause;
+ struct args_value *values = NULL;
+ u_int count = 0, idx;
+
+ memset(pr, 0, sizeof *pr);
+
+ if (cmd_parse_expand_alias(cmd, pi, pr))
+ return;
+
+ TAILQ_FOREACH(arg, &cmd->arguments, entry) {
+ values = xrecallocarray(values, count, count + 1,
+ sizeof *values);
+ switch (arg->type) {
+ case CMD_PARSE_STRING:
+ values[count].type = ARGS_STRING;
+ values[count].string = xstrdup(arg->string);
+ break;
+ case CMD_PARSE_COMMANDS:
+ cmd_parse_build_commands(arg->commands, pi, pr);
+ if (pr->status != CMD_PARSE_SUCCESS)
+ goto out;
+ values[count].type = ARGS_COMMANDS;
+ values[count].cmdlist = pr->cmdlist;
+ break;
+ case CMD_PARSE_PARSED_COMMANDS:
+ values[count].type = ARGS_COMMANDS;
+ values[count].cmdlist = arg->cmdlist;
+ values[count].cmdlist->references++;
+ break;
+ }
+ count++;
+ }
+
+ add = cmd_parse(values, count, pi->file, pi->line, &cause);
+ if (add == NULL) {
+ pr->status = CMD_PARSE_ERROR;
+ pr->error = cmd_parse_get_error(pi->file, pi->line, cause);
+ free(cause);
+ goto out;
+ }
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ cmd_list_append(pr->cmdlist, add);
+
+out:
+ for (idx = 0; idx < count; idx++)
+ args_free_value(&values[idx]);
+ free(values);
+}
+
+static void
+cmd_parse_build_commands(struct cmd_parse_commands *cmds,
+ struct cmd_parse_input *pi, struct cmd_parse_result *pr)
+{
+ struct cmd_parse_command *cmd;
+ u_int line = UINT_MAX;
+ struct cmd_list *current = NULL, *result;
+ char *s;
+
+ memset(pr, 0, sizeof *pr);
+
+ /* Check for an empty list. */
+ if (TAILQ_EMPTY(cmds)) {
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = cmd_list_new();
+ return;
+ }
+ cmd_parse_log_commands(cmds, __func__);
+
+ /*
+ * Parse each command into a command list. Create a new command list
+ * for each line (unless the flag is set) so they get a new group (so
+ * the queue knows which ones to remove if a command fails when
+ * executed).
+ */
+ result = cmd_list_new();
+ TAILQ_FOREACH(cmd, cmds, entry) {
+ if (((~pi->flags & CMD_PARSE_ONEGROUP) && cmd->line != line)) {
+ if (current != NULL) {
+ cmd_parse_print_commands(pi, current);
+ cmd_list_move(result, current);
+ cmd_list_free(current);
+ }
+ current = cmd_list_new();
+ }
+ if (current == NULL)
+ current = cmd_list_new();
+ line = pi->line = cmd->line;
+
+ cmd_parse_build_command(cmd, pi, pr);
+ if (pr->status != CMD_PARSE_SUCCESS) {
+ cmd_list_free(result);
+ cmd_list_free(current);
+ return;
+ }
+ cmd_list_append_all(current, pr->cmdlist);
+ cmd_list_free(pr->cmdlist);
+ }
+ if (current != NULL) {
+ cmd_parse_print_commands(pi, current);
+ cmd_list_move(result, current);
+ cmd_list_free(current);
+ }
+
+ s = cmd_list_print(result, 0);
+ log_debug("%s: %s", __func__, s);
+ free(s);
+
+ pr->status = CMD_PARSE_SUCCESS;
+ pr->cmdlist = result;
+}
+
+struct cmd_parse_result *
+cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds;
+ char *cause;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ cmds = cmd_parse_do_file(f, pi, &cause);
+ if (cmds == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cause;
+ return (&pr);
+ }
+ cmd_parse_build_commands(cmds, pi, &pr);
+ cmd_parse_free_commands(cmds);
+ return (&pr);
+
+}
+
+struct cmd_parse_result *
+cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
+{
+ struct cmd_parse_input input;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+
+ /*
+ * When parsing a string, put commands in one group even if there are
+ * multiple lines. This means { a \n b } is identical to "a ; b" when
+ * given as an argument to another command.
+ */
+ pi->flags |= CMD_PARSE_ONEGROUP;
+ return (cmd_parse_from_buffer(s, strlen(s), pi));
+}
+
+enum cmd_parse_status
+cmd_parse_and_insert(const char *s, struct cmd_parse_input *pi,
+ struct cmdq_item *after, struct cmdq_state *state, char **error)
+{
+ struct cmd_parse_result *pr;
+ struct cmdq_item *item;
+
+ pr = cmd_parse_from_string(s, pi);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ if (error != NULL)
+ *error = pr->error;
+ else
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ item = cmdq_get_command(pr->cmdlist, state);
+ cmdq_insert_after(after, item);
+ cmd_list_free(pr->cmdlist);
+ break;
+ }
+ return (pr->status);
+}
+
+enum cmd_parse_status
+cmd_parse_and_append(const char *s, struct cmd_parse_input *pi,
+ struct client *c, struct cmdq_state *state, char **error)
+{
+ struct cmd_parse_result *pr;
+ struct cmdq_item *item;
+
+ pr = cmd_parse_from_string(s, pi);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ if (error != NULL)
+ *error = pr->error;
+ else
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ item = cmdq_get_command(pr->cmdlist, state);
+ cmdq_append(c, item);
+ cmd_list_free(pr->cmdlist);
+ break;
+ }
+ return (pr->status);
+}
+
+struct cmd_parse_result *
+cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds;
+ char *cause;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ if (len == 0) {
+ pr.status = CMD_PARSE_SUCCESS;
+ pr.cmdlist = cmd_list_new();
+ return (&pr);
+ }
+
+ cmds = cmd_parse_do_buffer(buf, len, pi, &cause);
+ if (cmds == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cause;
+ return (&pr);
+ }
+ cmd_parse_build_commands(cmds, pi, &pr);
+ cmd_parse_free_commands(cmds);
+ return (&pr);
+}
+
+struct cmd_parse_result *
+cmd_parse_from_arguments(struct args_value *values, u_int count,
+ struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds;
+ struct cmd_parse_command *cmd;
+ struct cmd_parse_argument *arg;
+ u_int i;
+ char *copy;
+ size_t size;
+ int end;
+
+ /*
+ * The commands are already split up into arguments, so just separate
+ * into a set of commands by ';'.
+ */
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ cmds = cmd_parse_new_commands();
+
+ cmd = xcalloc(1, sizeof *cmd);
+ cmd->line = pi->line;
+ TAILQ_INIT(&cmd->arguments);
+
+ for (i = 0; i < count; i++) {
+ end = 0;
+ if (values[i].type == ARGS_STRING) {
+ copy = xstrdup(values[i].string);
+ size = strlen(copy);
+ if (size != 0 && copy[size - 1] == ';') {
+ copy[--size] = '\0';
+ if (size > 0 && copy[size - 1] == '\\')
+ copy[size - 1] = ';';
+ else
+ end = 1;
+ }
+ if (!end || size != 0) {
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_STRING;
+ arg->string = copy;
+ TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
+ }
+ } else if (values[i].type == ARGS_COMMANDS) {
+ arg = xcalloc(1, sizeof *arg);
+ arg->type = CMD_PARSE_PARSED_COMMANDS;
+ arg->cmdlist = values[i].cmdlist;
+ arg->cmdlist->references++;
+ TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
+ } else
+ fatalx("unknown argument type");
+ if (end) {
+ TAILQ_INSERT_TAIL(cmds, cmd, entry);
+ cmd = xcalloc(1, sizeof *cmd);
+ cmd->line = pi->line;
+ TAILQ_INIT(&cmd->arguments);
+ }
+ }
+ if (!TAILQ_EMPTY(&cmd->arguments))
+ TAILQ_INSERT_TAIL(cmds, cmd, entry);
+ else
+ free(cmd);
+
+ cmd_parse_build_commands(cmds, pi, &pr);
+ cmd_parse_free_commands(cmds);
+ return (&pr);
+}
+
+static int printflike(1, 2)
+yyerror(const char *fmt, ...)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ va_list ap;
+ char *error;
+
+ if (ps->error != NULL)
+ return (0);
+
+ va_start(ap, fmt);
+ xvasprintf(&error, fmt, ap);
+ va_end(ap);
+
+ ps->error = cmd_parse_get_error(pi->file, pi->line, error);
+ free(error);
+ return (0);
+}
+
+static int
+yylex_is_var(char ch, int first)
+{
+ if (ch == '=')
+ return (0);
+ if (first && isdigit((u_char)ch))
+ return (0);
+ return (isalnum((u_char)ch) || ch == '_');
+}
+
+static void
+yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
+{
+ if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
+ fatalx("buffer is too big");
+ *buf = xrealloc(*buf, (*len) + 1 + addlen);
+ memcpy((*buf) + *len, add, addlen);
+ (*len) += addlen;
+}
+
+static void
+yylex_append1(char **buf, size_t *len, char add)
+{
+ yylex_append(buf, len, &add, 1);
+}
+
+static int
+yylex_getc1(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int ch;
+
+ if (ps->f != NULL)
+ ch = getc(ps->f);
+ else {
+ if (ps->off == ps->len)
+ ch = EOF;
+ else
+ ch = ps->buf[ps->off++];
+ }
+ return (ch);
+}
+
+static void
+yylex_ungetc(int ch)
+{
+ struct cmd_parse_state *ps = &parse_state;
+
+ if (ps->f != NULL)
+ ungetc(ch, ps->f);
+ else if (ps->off > 0 && ch != EOF)
+ ps->off--;
+}
+
+static int
+yylex_getc(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int ch;
+
+ if (ps->escapes != 0) {
+ ps->escapes--;
+ return ('\\');
+ }
+ for (;;) {
+ ch = yylex_getc1();
+ if (ch == '\\') {
+ ps->escapes++;
+ continue;
+ }
+ if (ch == '\n' && (ps->escapes % 2) == 1) {
+ ps->input->line++;
+ ps->escapes--;
+ continue;
+ }
+
+ if (ps->escapes != 0) {
+ yylex_ungetc(ch);
+ ps->escapes--;
+ return ('\\');
+ }
+ return (ch);
+ }
+}
+
+static char *
+yylex_get_word(int ch)
+{
+ char *buf;
+ size_t len;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ do
+ yylex_append1(&buf, &len, ch);
+ while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
+ yylex_ungetc(ch);
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+}
+
+static int
+yylex(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ char *token, *cp;
+ int ch, next, condition;
+
+ if (ps->eol)
+ ps->input->line++;
+ ps->eol = 0;
+
+ condition = ps->condition;
+ ps->condition = 0;
+
+ for (;;) {
+ ch = yylex_getc();
+
+ if (ch == EOF) {
+ /*
+ * Ensure every file or string is terminated by a
+ * newline. This keeps the parser simpler and avoids
+ * having to add a newline to each string.
+ */
+ if (ps->eof)
+ break;
+ ps->eof = 1;
+ return ('\n');
+ }
+
+ if (ch == ' ' || ch == '\t') {
+ /*
+ * Ignore whitespace.
+ */
+ continue;
+ }
+
+ if (ch == '\n') {
+ /*
+ * End of line. Update the line number.
+ */
+ ps->eol = 1;
+ return ('\n');
+ }
+
+ if (ch == ';' || ch == '{' || ch == '}') {
+ /*
+ * A semicolon or { or } is itself.
+ */
+ return (ch);
+ }
+
+ if (ch == '#') {
+ /*
+ * #{ after a condition opens a format; anything else
+ * is a comment, ignore up to the end of the line.
+ */
+ next = yylex_getc();
+ if (condition && next == '{') {
+ yylval.token = yylex_format();
+ if (yylval.token == NULL)
+ return (ERROR);
+ return (FORMAT);
+ }
+ while (next != '\n' && next != EOF)
+ next = yylex_getc();
+ if (next == '\n') {
+ ps->input->line++;
+ return ('\n');
+ }
+ continue;
+ }
+
+ if (ch == '%') {
+ /*
+ * % is a condition unless it is all % or all numbers,
+ * then it is a token.
+ */
+ yylval.token = yylex_get_word('%');
+ for (cp = yylval.token; *cp != '\0'; cp++) {
+ if (*cp != '%' && !isdigit((u_char)*cp))
+ break;
+ }
+ if (*cp == '\0')
+ return (TOKEN);
+ ps->condition = 1;
+ if (strcmp(yylval.token, "%hidden") == 0) {
+ free(yylval.token);
+ return (HIDDEN);
+ }
+ if (strcmp(yylval.token, "%if") == 0) {
+ free(yylval.token);
+ return (IF);
+ }
+ if (strcmp(yylval.token, "%else") == 0) {
+ free(yylval.token);
+ return (ELSE);
+ }
+ if (strcmp(yylval.token, "%elif") == 0) {
+ free(yylval.token);
+ return (ELIF);
+ }
+ if (strcmp(yylval.token, "%endif") == 0) {
+ free(yylval.token);
+ return (ENDIF);
+ }
+ free(yylval.token);
+ return (ERROR);
+ }
+
+ /*
+ * Otherwise this is a token.
+ */
+ token = yylex_token(ch);
+ if (token == NULL)
+ return (ERROR);
+ yylval.token = token;
+
+ if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
+ for (cp = token + 1; *cp != '='; cp++) {
+ if (!yylex_is_var(*cp, 0))
+ break;
+ }
+ if (*cp == '=')
+ return (EQUALS);
+ }
+ return (TOKEN);
+ }
+ return (0);
+}
+
+static char *
+yylex_format(void)
+{
+ char *buf;
+ size_t len;
+ int ch, brackets = 1;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ yylex_append(&buf, &len, "#{", 2);
+ for (;;) {
+ if ((ch = yylex_getc()) == EOF || ch == '\n')
+ goto error;
+ if (ch == '#') {
+ if ((ch = yylex_getc()) == EOF || ch == '\n')
+ goto error;
+ if (ch == '{')
+ brackets++;
+ yylex_append1(&buf, &len, '#');
+ } else if (ch == '}') {
+ if (brackets != 0 && --brackets == 0) {
+ yylex_append1(&buf, &len, ch);
+ break;
+ }
+ }
+ yylex_append1(&buf, &len, ch);
+ }
+ if (brackets != 0)
+ goto error;
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+static int
+yylex_token_escape(char **buf, size_t *len)
+{
+ int ch, type, o2, o3, mlen;
+ u_int size, i, tmp;
+ char s[9], m[MB_LEN_MAX];
+
+ ch = yylex_getc();
+
+ if (ch >= '4' && ch <= '7') {
+ yyerror("invalid octal escape");
+ return (0);
+ }
+ if (ch >= '0' && ch <= '3') {
+ o2 = yylex_getc();
+ if (o2 >= '0' && o2 <= '7') {
+ o3 = yylex_getc();
+ if (o3 >= '0' && o3 <= '7') {
+ ch = 64 * (ch - '0') +
+ 8 * (o2 - '0') +
+ (o3 - '0');
+ yylex_append1(buf, len, ch);
+ return (1);
+ }
+ }
+ yyerror("invalid octal escape");
+ return (0);
+ }
+
+ switch (ch) {
+ case EOF:
+ return (0);
+ case 'a':
+ ch = '\a';
+ break;
+ case 'b':
+ ch = '\b';
+ break;
+ case 'e':
+ ch = '\033';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 's':
+ ch = ' ';
+ break;
+ case 'v':
+ ch = '\v';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'u':
+ type = 'u';
+ size = 4;
+ goto unicode;
+ case 'U':
+ type = 'U';
+ size = 8;
+ goto unicode;
+ }
+
+ yylex_append1(buf, len, ch);
+ return (1);
+
+unicode:
+ for (i = 0; i < size; i++) {
+ ch = yylex_getc();
+ if (ch == EOF || ch == '\n')
+ return (0);
+ if (!isxdigit((u_char)ch)) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ s[i] = ch;
+ }
+ s[i] = '\0';
+
+ if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
+ (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ mlen = wctomb(m, tmp);
+ if (mlen <= 0 || mlen > (int)sizeof m) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ yylex_append(buf, len, m, mlen);
+ return (1);
+}
+
+static int
+yylex_token_variable(char **buf, size_t *len)
+{
+ struct environ_entry *envent;
+ int ch, brackets = 0;
+ char name[1024];
+ size_t namelen = 0;
+ const char *value;
+
+ ch = yylex_getc();
+ if (ch == EOF)
+ return (0);
+ if (ch == '{')
+ brackets = 1;
+ else {
+ if (!yylex_is_var(ch, 1)) {
+ yylex_append1(buf, len, '$');
+ yylex_ungetc(ch);
+ return (1);
+ }
+ name[namelen++] = ch;
+ }
+
+ for (;;) {
+ ch = yylex_getc();
+ if (brackets && ch == '}')
+ break;
+ if (ch == EOF || !yylex_is_var(ch, 0)) {
+ if (!brackets) {
+ yylex_ungetc(ch);
+ break;
+ }
+ yyerror("invalid environment variable");
+ return (0);
+ }
+ if (namelen == (sizeof name) - 2) {
+ yyerror("environment variable is too long");
+ return (0);
+ }
+ name[namelen++] = ch;
+ }
+ name[namelen] = '\0';
+
+ envent = environ_find(global_environ, name);
+ if (envent != NULL && envent->value != NULL) {
+ value = envent->value;
+ log_debug("%s: %s -> %s", __func__, name, value);
+ yylex_append(buf, len, value, strlen(value));
+ }
+ return (1);
+}
+
+static int
+yylex_token_tilde(char **buf, size_t *len)
+{
+ struct environ_entry *envent;
+ int ch;
+ char name[1024];
+ size_t namelen = 0;
+ struct passwd *pw;
+ const char *home = NULL;
+
+ for (;;) {
+ ch = yylex_getc();
+ if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
+ yylex_ungetc(ch);
+ break;
+ }
+ if (namelen == (sizeof name) - 2) {
+ yyerror("user name is too long");
+ return (0);
+ }
+ name[namelen++] = ch;
+ }
+ name[namelen] = '\0';
+
+ if (*name == '\0') {
+ envent = environ_find(global_environ, "HOME");
+ if (envent != NULL && *envent->value != '\0')
+ home = envent->value;
+ else if ((pw = getpwuid(getuid())) != NULL)
+ home = pw->pw_dir;
+ } else {
+ if ((pw = getpwnam(name)) != NULL)
+ home = pw->pw_dir;
+ }
+ if (home == NULL)
+ return (0);
+
+ log_debug("%s: ~%s -> %s", __func__, name, home);
+ yylex_append(buf, len, home, strlen(home));
+ return (1);
+}
+
+static char *
+yylex_token(int ch)
+{
+ char *buf;
+ size_t len;
+ enum { START,
+ NONE,
+ DOUBLE_QUOTES,
+ SINGLE_QUOTES } state = NONE, last = START;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ for (;;) {
+ /* EOF or \n are always the end of the token. */
+ if (ch == EOF || (state == NONE && ch == '\n'))
+ break;
+
+ /* Whitespace or ; or } ends a token unless inside quotes. */
+ if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') &&
+ state == NONE)
+ break;
+
+ /*
+ * Spaces and comments inside quotes after \n are removed but
+ * the \n is left.
+ */
+ if (ch == '\n' && state != NONE) {
+ yylex_append1(&buf, &len, '\n');
+ while ((ch = yylex_getc()) == ' ' || ch == '\t')
+ /* nothing */;
+ if (ch != '#')
+ continue;
+ ch = yylex_getc();
+ if (strchr(",#{}:", ch) != NULL) {
+ yylex_ungetc(ch);
+ ch = '#';
+ } else {
+ while ((ch = yylex_getc()) != '\n' && ch != EOF)
+ /* nothing */;
+ }
+ continue;
+ }
+
+ /* \ ~ and $ are expanded except in single quotes. */
+ if (ch == '\\' && state != SINGLE_QUOTES) {
+ if (!yylex_token_escape(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '~' && last != state && state != SINGLE_QUOTES) {
+ if (!yylex_token_tilde(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '$' && state != SINGLE_QUOTES) {
+ if (!yylex_token_variable(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '}' && state == NONE)
+ goto error; /* unmatched (matched ones were handled) */
+
+ /* ' and " starts or end quotes (and is consumed). */
+ if (ch == '\'') {
+ if (state == NONE) {
+ state = SINGLE_QUOTES;
+ goto next;
+ }
+ if (state == SINGLE_QUOTES) {
+ state = NONE;
+ goto next;
+ }
+ }
+ if (ch == '"') {
+ if (state == NONE) {
+ state = DOUBLE_QUOTES;
+ goto next;
+ }
+ if (state == DOUBLE_QUOTES) {
+ state = NONE;
+ goto next;
+ }
+ }
+
+ /* Otherwise add the character to the buffer. */
+ yylex_append1(&buf, &len, ch);
+
+ skip:
+ last = state;
+
+ next:
+ ch = yylex_getc();
+ }
+ yylex_ungetc(ch);
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+
+error:
+ free(buf);
+ return (NULL);
+}
diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c
new file mode 100644
index 0000000..36326e1
--- /dev/null
+++ b/cmd-paste-buffer.c
@@ -0,0 +1,108 @@
+/* $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"
+
+/*
+ * Paste paste buffer if present.
+ */
+
+static enum cmd_retval cmd_paste_buffer_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_paste_buffer_entry = {
+ .name = "paste-buffer",
+ .alias = "pasteb",
+
+ .args = { "db:prs:t:", 0, 0, NULL },
+ .usage = "[-dpr] [-s separator] " CMD_BUFFER_USAGE " "
+ CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_paste_buffer_exec
+};
+
+static enum cmd_retval
+cmd_paste_buffer_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 window_pane *wp = target->wp;
+ struct paste_buffer *pb;
+ const char *sepstr, *bufname, *bufdata, *bufend, *line;
+ size_t seplen, bufsize;
+ int bracket = args_has(args, 'p');
+
+ bufname = NULL;
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
+
+ if (bufname == NULL)
+ pb = paste_get_top(NULL);
+ else {
+ pb = paste_get_name(bufname);
+ if (pb == NULL) {
+ cmdq_error(item, "no buffer %s", bufname);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (pb != NULL && ~wp->flags & PANE_INPUTOFF) {
+ sepstr = args_get(args, 's');
+ if (sepstr == NULL) {
+ if (args_has(args, 'r'))
+ sepstr = "\n";
+ else
+ sepstr = "\r";
+ }
+ seplen = strlen(sepstr);
+
+ if (bracket && (wp->screen->mode & MODE_BRACKETPASTE))
+ bufferevent_write(wp->event, "\033[200~", 6);
+
+ bufdata = paste_buffer_data(pb, &bufsize);
+ bufend = bufdata + bufsize;
+
+ for (;;) {
+ line = memchr(bufdata, '\n', bufend - bufdata);
+ if (line == NULL)
+ break;
+
+ bufferevent_write(wp->event, bufdata, line - bufdata);
+ bufferevent_write(wp->event, sepstr, seplen);
+
+ bufdata = line + 1;
+ }
+ if (bufdata != bufend)
+ bufferevent_write(wp->event, bufdata, bufend - bufdata);
+
+ if (bracket && (wp->screen->mode & MODE_BRACKETPASTE))
+ bufferevent_write(wp->event, "\033[201~", 6);
+ }
+
+ if (pb != NULL && args_has(args, 'd'))
+ paste_free(pb);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c
new file mode 100644
index 0000000..0fa656c
--- /dev/null
+++ b/cmd-pipe-pane.c
@@ -0,0 +1,230 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Open pipe to redirect pane output. If already open, close first.
+ */
+
+static enum cmd_retval cmd_pipe_pane_exec(struct cmd *, struct cmdq_item *);
+
+static void cmd_pipe_pane_read_callback(struct bufferevent *, void *);
+static void cmd_pipe_pane_write_callback(struct bufferevent *, void *);
+static void cmd_pipe_pane_error_callback(struct bufferevent *, short, void *);
+
+const struct cmd_entry cmd_pipe_pane_entry = {
+ .name = "pipe-pane",
+ .alias = "pipep",
+
+ .args = { "IOot:", 0, 1, NULL },
+ .usage = "[-IOo] " CMD_TARGET_PANE_USAGE " [shell-command]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_pipe_pane_exec
+};
+
+static enum cmd_retval
+cmd_pipe_pane_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 client *tc = cmdq_get_target_client(item);
+ struct window_pane *wp = target->wp;
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ struct window_pane_offset *wpo = &wp->pipe_offset;
+ char *cmd;
+ int old_fd, pipe_fd[2], null_fd, in, out;
+ struct format_tree *ft;
+ sigset_t set, oldset;
+
+ /* Do nothing if pane is dead. */
+ if (wp->fd == -1 || (wp->flags & PANE_EXITED)) {
+ cmdq_error(item, "target pane has exited");
+ return (CMD_RETURN_ERROR);
+ }
+
+ /* Destroy the old pipe. */
+ old_fd = wp->pipe_fd;
+ if (wp->pipe_fd != -1) {
+ bufferevent_free(wp->pipe_event);
+ close(wp->pipe_fd);
+ wp->pipe_fd = -1;
+
+ if (window_pane_destroy_ready(wp)) {
+ server_destroy_pane(wp, 1);
+ return (CMD_RETURN_NORMAL);
+ }
+ }
+
+ /* If no pipe command, that is enough. */
+ if (args_count(args) == 0 || *args_string(args, 0) == '\0')
+ return (CMD_RETURN_NORMAL);
+
+ /*
+ * With -o, only open the new pipe if there was no previous one. This
+ * allows a pipe to be toggled with a single key, for example:
+ *
+ * bind ^p pipep -o 'cat >>~/output'
+ */
+ if (args_has(args, 'o') && old_fd != -1)
+ return (CMD_RETURN_NORMAL);
+
+ /* What do we want to do? Neither -I or -O is -O. */
+ if (args_has(args, 'I')) {
+ in = 1;
+ out = args_has(args, 'O');
+ } else {
+ in = 0;
+ out = 1;
+ }
+
+ /* Open the new pipe. */
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fd) != 0) {
+ cmdq_error(item, "socketpair error: %s", strerror(errno));
+ return (CMD_RETURN_ERROR);
+ }
+
+ /* Expand the command. */
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ format_defaults(ft, tc, s, wl, wp);
+ cmd = format_expand_time(ft, args_string(args, 0));
+ format_free(ft);
+
+ /* Fork the child. */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oldset);
+ switch (fork()) {
+ case -1:
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ cmdq_error(item, "fork error: %s", strerror(errno));
+
+ free(cmd);
+ return (CMD_RETURN_ERROR);
+ case 0:
+ /* Child process. */
+ proc_clear_signals(server_proc, 1);
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ close(pipe_fd[0]);
+
+ null_fd = open(_PATH_DEVNULL, O_WRONLY);
+ if (out) {
+ if (dup2(pipe_fd[1], STDIN_FILENO) == -1)
+ _exit(1);
+ } else {
+ if (dup2(null_fd, STDIN_FILENO) == -1)
+ _exit(1);
+ }
+ if (in) {
+ if (dup2(pipe_fd[1], STDOUT_FILENO) == -1)
+ _exit(1);
+ if (pipe_fd[1] != STDOUT_FILENO)
+ close(pipe_fd[1]);
+ } else {
+ if (dup2(null_fd, STDOUT_FILENO) == -1)
+ _exit(1);
+ }
+ if (dup2(null_fd, STDERR_FILENO) == -1)
+ _exit(1);
+ closefrom(STDERR_FILENO + 1);
+
+ execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
+ _exit(1);
+ default:
+ /* Parent process. */
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ close(pipe_fd[1]);
+
+ wp->pipe_fd = pipe_fd[0];
+ memcpy(wpo, &wp->offset, sizeof *wpo);
+
+ setblocking(wp->pipe_fd, 0);
+ wp->pipe_event = bufferevent_new(wp->pipe_fd,
+ cmd_pipe_pane_read_callback,
+ cmd_pipe_pane_write_callback,
+ cmd_pipe_pane_error_callback,
+ wp);
+ if (wp->pipe_event == NULL)
+ fatalx("out of memory");
+ if (out)
+ bufferevent_enable(wp->pipe_event, EV_WRITE);
+ if (in)
+ bufferevent_enable(wp->pipe_event, EV_READ);
+
+ free(cmd);
+ return (CMD_RETURN_NORMAL);
+ }
+}
+
+static void
+cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct window_pane *wp = data;
+ struct evbuffer *evb = wp->pipe_event->input;
+ size_t available;
+
+ available = EVBUFFER_LENGTH(evb);
+ log_debug("%%%u pipe read %zu", wp->id, available);
+
+ bufferevent_write(wp->event, EVBUFFER_DATA(evb), available);
+ evbuffer_drain(evb, available);
+
+ if (window_pane_destroy_ready(wp))
+ server_destroy_pane(wp, 1);
+}
+
+static void
+cmd_pipe_pane_write_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct window_pane *wp = data;
+
+ log_debug("%%%u pipe empty", wp->id);
+
+ if (window_pane_destroy_ready(wp))
+ server_destroy_pane(wp, 1);
+}
+
+static void
+cmd_pipe_pane_error_callback(__unused struct bufferevent *bufev,
+ __unused short what, void *data)
+{
+ struct window_pane *wp = data;
+
+ log_debug("%%%u pipe error", wp->id);
+
+ bufferevent_free(wp->pipe_event);
+ close(wp->pipe_fd);
+ wp->pipe_fd = -1;
+
+ if (window_pane_destroy_ready(wp))
+ server_destroy_pane(wp, 1);
+}
diff --git a/cmd-queue.c b/cmd-queue.c
new file mode 100644
index 0000000..8325e2e
--- /dev/null
+++ b/cmd-queue.c
@@ -0,0 +1,903 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013 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 <ctype.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/* Command queue flags. */
+#define CMDQ_FIRED 0x1
+#define CMDQ_WAITING 0x2
+
+/* Command queue item type. */
+enum cmdq_type {
+ CMDQ_COMMAND,
+ CMDQ_CALLBACK,
+};
+
+/* Command queue item. */
+struct cmdq_item {
+ char *name;
+ struct cmdq_list *queue;
+ struct cmdq_item *next;
+
+ struct client *client;
+ struct client *target_client;
+
+ enum cmdq_type type;
+ u_int group;
+
+ u_int number;
+ time_t time;
+
+ int flags;
+
+ struct cmdq_state *state;
+ struct cmd_find_state source;
+ struct cmd_find_state target;
+
+ struct cmd_list *cmdlist;
+ struct cmd *cmd;
+
+ cmdq_cb cb;
+ void *data;
+
+ TAILQ_ENTRY(cmdq_item) entry;
+};
+TAILQ_HEAD(cmdq_item_list, cmdq_item);
+
+/*
+ * Command queue state. This is the context for commands on the command queue.
+ * It holds information about how the commands were fired (the key and flags),
+ * any additional formats for the commands, and the current default target.
+ * Multiple commands can share the same state and a command may update the
+ * default target.
+ */
+struct cmdq_state {
+ int references;
+ int flags;
+
+ struct format_tree *formats;
+
+ struct key_event event;
+ struct cmd_find_state current;
+};
+
+/* Command queue. */
+struct cmdq_list {
+ struct cmdq_item *item;
+ struct cmdq_item_list list;
+};
+
+/* Get command queue name. */
+static const char *
+cmdq_name(struct client *c)
+{
+ static char s[256];
+
+ if (c == NULL)
+ return ("<global>");
+ if (c->name != NULL)
+ xsnprintf(s, sizeof s, "<%s>", c->name);
+ else
+ xsnprintf(s, sizeof s, "<%p>", c);
+ return (s);
+}
+
+/* Get command queue from client. */
+static struct cmdq_list *
+cmdq_get(struct client *c)
+{
+ static struct cmdq_list *global_queue;
+
+ if (c == NULL) {
+ if (global_queue == NULL)
+ global_queue = cmdq_new();
+ return (global_queue);
+ }
+ return (c->queue);
+}
+
+/* Create a queue. */
+struct cmdq_list *
+cmdq_new(void)
+{
+ struct cmdq_list *queue;
+
+ queue = xcalloc(1, sizeof *queue);
+ TAILQ_INIT (&queue->list);
+ return (queue);
+}
+
+/* Free a queue. */
+void
+cmdq_free(struct cmdq_list *queue)
+{
+ if (!TAILQ_EMPTY(&queue->list))
+ fatalx("queue not empty");
+ free(queue);
+}
+
+/* Get item name. */
+const char *
+cmdq_get_name(struct cmdq_item *item)
+{
+ return (item->name);
+}
+
+/* Get item client. */
+struct client *
+cmdq_get_client(struct cmdq_item *item)
+{
+ return (item->client);
+}
+
+/* Get item target client. */
+struct client *
+cmdq_get_target_client(struct cmdq_item *item)
+{
+ return (item->target_client);
+}
+
+/* Get item state. */
+struct cmdq_state *
+cmdq_get_state(struct cmdq_item *item)
+{
+ return (item->state);
+}
+
+/* Get item target. */
+struct cmd_find_state *
+cmdq_get_target(struct cmdq_item *item)
+{
+ return (&item->target);
+}
+
+/* Get item source. */
+struct cmd_find_state *
+cmdq_get_source(struct cmdq_item *item)
+{
+ return (&item->source);
+}
+
+/* Get state event. */
+struct key_event *
+cmdq_get_event(struct cmdq_item *item)
+{
+ return (&item->state->event);
+}
+
+/* Get state current target. */
+struct cmd_find_state *
+cmdq_get_current(struct cmdq_item *item)
+{
+ return (&item->state->current);
+}
+
+/* Get state flags. */
+int
+cmdq_get_flags(struct cmdq_item *item)
+{
+ return (item->state->flags);
+}
+
+/* Create a new state. */
+struct cmdq_state *
+cmdq_new_state(struct cmd_find_state *current, struct key_event *event,
+ int flags)
+{
+ struct cmdq_state *state;
+
+ state = xcalloc(1, sizeof *state);
+ state->references = 1;
+ state->flags = flags;
+
+ if (event != NULL)
+ memcpy(&state->event, event, sizeof state->event);
+ else
+ state->event.key = KEYC_NONE;
+ if (current != NULL && cmd_find_valid_state(current))
+ cmd_find_copy_state(&state->current, current);
+ else
+ cmd_find_clear_state(&state->current, 0);
+
+ return (state);
+}
+
+/* Add a reference to a state. */
+struct cmdq_state *
+cmdq_link_state(struct cmdq_state *state)
+{
+ state->references++;
+ return (state);
+}
+
+/* Make a copy of a state. */
+struct cmdq_state *
+cmdq_copy_state(struct cmdq_state *state)
+{
+ return (cmdq_new_state(&state->current, &state->event, state->flags));
+}
+
+/* Free a state. */
+void
+cmdq_free_state(struct cmdq_state *state)
+{
+ if (--state->references != 0)
+ return;
+
+ if (state->formats != NULL)
+ format_free(state->formats);
+ free(state);
+}
+
+/* Add a format to command queue. */
+void
+cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...)
+{
+ va_list ap;
+ char *value;
+
+ va_start(ap, fmt);
+ xvasprintf(&value, fmt, ap);
+ va_end(ap);
+
+ if (state->formats == NULL)
+ state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
+ format_add(state->formats, key, "%s", value);
+
+ free(value);
+}
+
+/* Add formats to command queue. */
+void
+cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft)
+{
+ if (state->formats == NULL)
+ state->formats = format_create(NULL, NULL, FORMAT_NONE, 0);
+ format_merge(state->formats, ft);
+}
+
+/* Merge formats from item. */
+void
+cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft)
+{
+ const struct cmd_entry *entry;
+
+ if (item->cmd != NULL) {
+ entry = cmd_get_entry(item->cmd);
+ format_add(ft, "command", "%s", entry->name);
+ }
+ if (item->state->formats != NULL)
+ format_merge(ft, item->state->formats);
+}
+
+/* Append an item. */
+struct cmdq_item *
+cmdq_append(struct client *c, struct cmdq_item *item)
+{
+ struct cmdq_list *queue = cmdq_get(c);
+ struct cmdq_item *next;
+
+ do {
+ next = item->next;
+ item->next = NULL;
+
+ if (c != NULL)
+ c->references++;
+ item->client = c;
+
+ item->queue = queue;
+ TAILQ_INSERT_TAIL(&queue->list, item, entry);
+ log_debug("%s %s: %s", __func__, cmdq_name(c), item->name);
+
+ item = next;
+ } while (item != NULL);
+ return (TAILQ_LAST(&queue->list, cmdq_item_list));
+}
+
+/* Insert an item. */
+struct cmdq_item *
+cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item)
+{
+ struct client *c = after->client;
+ struct cmdq_list *queue = after->queue;
+ struct cmdq_item *next;
+
+ do {
+ next = item->next;
+ item->next = after->next;
+ after->next = item;
+
+ if (c != NULL)
+ c->references++;
+ item->client = c;
+
+ item->queue = queue;
+ TAILQ_INSERT_AFTER(&queue->list, after, item, entry);
+ log_debug("%s %s: %s after %s", __func__, cmdq_name(c),
+ item->name, after->name);
+
+ after = item;
+ item = next;
+ } while (item != NULL);
+ return (after);
+}
+
+/* Insert a hook. */
+void
+cmdq_insert_hook(struct session *s, struct cmdq_item *item,
+ struct cmd_find_state *current, const char *fmt, ...)
+{
+ struct cmdq_state *state = item->state;
+ struct cmd *cmd = item->cmd;
+ struct args *args = cmd_get_args(cmd);
+ struct args_entry *ae;
+ struct args_value *av;
+ struct options *oo;
+ va_list ap;
+ char *name, tmp[32], flag, *arguments;
+ u_int i;
+ const char *value;
+ struct cmdq_item *new_item;
+ struct cmdq_state *new_state;
+ struct options_entry *o;
+ struct options_array_item *a;
+ struct cmd_list *cmdlist;
+
+ if (item->state->flags & CMDQ_STATE_NOHOOKS)
+ return;
+ if (s == NULL)
+ oo = global_s_options;
+ else
+ oo = s->options;
+
+ va_start(ap, fmt);
+ xvasprintf(&name, fmt, ap);
+ va_end(ap);
+
+ o = options_get(oo, name);
+ if (o == NULL) {
+ free(name);
+ return;
+ }
+ log_debug("running hook %s (parent %p)", name, item);
+
+ /*
+ * The hooks get a new state because they should not update the current
+ * target or formats for any subsequent commands.
+ */
+ new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS);
+ cmdq_add_format(new_state, "hook", "%s", name);
+
+ arguments = args_print(args);
+ cmdq_add_format(new_state, "hook_arguments", "%s", arguments);
+ free(arguments);
+
+ for (i = 0; i < args_count(args); i++) {
+ xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i);
+ cmdq_add_format(new_state, tmp, "%s", args_string(args, i));
+ }
+ flag = args_first(args, &ae);
+ while (flag != 0) {
+ value = args_get(args, flag);
+ if (value == NULL) {
+ xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
+ cmdq_add_format(new_state, tmp, "1");
+ } else {
+ xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag);
+ cmdq_add_format(new_state, tmp, "%s", value);
+ }
+
+ i = 0;
+ av = args_first_value(args, flag);
+ while (av != NULL) {
+ xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i);
+ cmdq_add_format(new_state, tmp, "%s", av->string);
+ i++;
+ av = args_next_value(av);
+ }
+
+ flag = args_next(&ae);
+ }
+
+ a = options_array_first(o);
+ while (a != NULL) {
+ cmdlist = options_array_item_value(a)->cmdlist;
+ if (cmdlist != NULL) {
+ new_item = cmdq_get_command(cmdlist, new_state);
+ if (item != NULL)
+ item = cmdq_insert_after(item, new_item);
+ else
+ item = cmdq_append(NULL, new_item);
+ }
+ a = options_array_next(a);
+ }
+
+ cmdq_free_state(new_state);
+ free(name);
+}
+
+/* Continue processing command queue. */
+void
+cmdq_continue(struct cmdq_item *item)
+{
+ item->flags &= ~CMDQ_WAITING;
+}
+
+/* Remove an item. */
+static void
+cmdq_remove(struct cmdq_item *item)
+{
+ if (item->client != NULL)
+ server_client_unref(item->client);
+ if (item->cmdlist != NULL)
+ cmd_list_free(item->cmdlist);
+ cmdq_free_state(item->state);
+
+ TAILQ_REMOVE(&item->queue->list, item, entry);
+
+ free(item->name);
+ free(item);
+}
+
+/* Remove all subsequent items that match this item's group. */
+static void
+cmdq_remove_group(struct cmdq_item *item)
+{
+ struct cmdq_item *this, *next;
+
+ if (item->group == 0)
+ return;
+ this = TAILQ_NEXT(item, entry);
+ while (this != NULL) {
+ next = TAILQ_NEXT(this, entry);
+ if (this->group == item->group)
+ cmdq_remove(this);
+ this = next;
+ }
+}
+
+/* Empty command callback. */
+static enum cmd_retval
+cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
+{
+ return (CMD_RETURN_NORMAL);
+}
+
+/* Get a command for the command queue. */
+struct cmdq_item *
+cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
+{
+ struct cmdq_item *item, *first = NULL, *last = NULL;
+ struct cmd *cmd;
+ const struct cmd_entry *entry;
+ int created = 0;
+
+ if ((cmd = cmd_list_first(cmdlist)) == NULL)
+ return (cmdq_get_callback(cmdq_empty_command, NULL));
+
+ if (state == NULL) {
+ state = cmdq_new_state(NULL, NULL, 0);
+ created = 1;
+ }
+
+ while (cmd != NULL) {
+ entry = cmd_get_entry(cmd);
+
+ item = xcalloc(1, sizeof *item);
+ xasprintf(&item->name, "[%s/%p]", entry->name, item);
+ item->type = CMDQ_COMMAND;
+
+ item->group = cmd_get_group(cmd);
+ item->state = cmdq_link_state(state);
+
+ item->cmdlist = cmdlist;
+ item->cmd = cmd;
+
+ cmdlist->references++;
+ log_debug("%s: %s group %u", __func__, item->name, item->group);
+
+ if (first == NULL)
+ first = item;
+ if (last != NULL)
+ last->next = item;
+ last = item;
+
+ cmd = cmd_list_next(cmd);
+ }
+
+ if (created)
+ cmdq_free_state(state);
+ return (first);
+}
+
+/* Fill in flag for a command. */
+static enum cmd_retval
+cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
+ const struct cmd_entry_flag *flag)
+{
+ const char *value;
+
+ if (flag->flag == 0) {
+ cmd_find_from_client(fs, item->target_client, 0);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ value = args_get(cmd_get_args(item->cmd), flag->flag);
+ if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) {
+ cmd_find_clear_state(fs, 0);
+ return (CMD_RETURN_ERROR);
+ }
+ return (CMD_RETURN_NORMAL);
+}
+
+/* Add message with command. */
+static void
+cmdq_add_message(struct cmdq_item *item)
+{
+ struct client *c = item->client;
+ struct cmdq_state *state = item->state;
+ const char *key;
+ char *tmp;
+ uid_t uid;
+ struct passwd *pw;
+ char *user = NULL;
+
+ tmp = cmd_print(item->cmd);
+ if (c != NULL) {
+ uid = proc_get_peer_uid(c->peer);
+ if (uid != (uid_t)-1 && uid != getuid()) {
+ if ((pw = getpwuid(uid)) != NULL)
+ xasprintf(&user, "[%s]", pw->pw_name);
+ else
+ user = xstrdup("[unknown]");
+ } else
+ user = xstrdup("");
+ if (c->session != NULL && state->event.key != KEYC_NONE) {
+ key = key_string_lookup_key(state->event.key, 0);
+ server_add_message("%s%s key %s: %s", c->name, user,
+ key, tmp);
+ } else {
+ server_add_message("%s%s command: %s", c->name, user,
+ tmp);
+ }
+ free(user);
+ } else
+ server_add_message("command: %s", tmp);
+ free(tmp);
+}
+
+/* Fire command on command queue. */
+static enum cmd_retval
+cmdq_fire_command(struct cmdq_item *item)
+{
+ const char *name = cmdq_name(item->client);
+ struct cmdq_state *state = item->state;
+ struct cmd *cmd = item->cmd;
+ struct args *args = cmd_get_args(cmd);
+ const struct cmd_entry *entry = cmd_get_entry(cmd);
+ struct client *tc, *saved = item->client;
+ enum cmd_retval retval;
+ struct cmd_find_state *fsp, fs;
+ int flags, quiet = 0;
+ char *tmp;
+
+ if (cfg_finished)
+ cmdq_add_message(item);
+ if (log_get_level() > 1) {
+ tmp = cmd_print(cmd);
+ log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
+ free(tmp);
+ }
+
+ flags = !!(state->flags & CMDQ_STATE_CONTROL);
+ cmdq_guard(item, "begin", flags);
+
+ if (item->client == NULL)
+ item->client = cmd_find_client(item, NULL, 1);
+
+ if (entry->flags & CMD_CLIENT_CANFAIL)
+ quiet = 1;
+ if (entry->flags & CMD_CLIENT_CFLAG) {
+ tc = cmd_find_client(item, args_get(args, 'c'), quiet);
+ if (tc == NULL && !quiet) {
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+ } else if (entry->flags & CMD_CLIENT_TFLAG) {
+ tc = cmd_find_client(item, args_get(args, 't'), quiet);
+ if (tc == NULL && !quiet) {
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+ } else
+ tc = cmd_find_client(item, NULL, 1);
+ item->target_client = tc;
+
+ retval = cmdq_find_flag(item, &item->source, &entry->source);
+ if (retval == CMD_RETURN_ERROR)
+ goto out;
+ retval = cmdq_find_flag(item, &item->target, &entry->target);
+ if (retval == CMD_RETURN_ERROR)
+ goto out;
+
+ retval = entry->exec(cmd, item);
+ if (retval == CMD_RETURN_ERROR)
+ goto out;
+
+ if (entry->flags & CMD_AFTERHOOK) {
+ if (cmd_find_valid_state(&item->target))
+ fsp = &item->target;
+ else if (cmd_find_valid_state(&item->state->current))
+ fsp = &item->state->current;
+ else if (cmd_find_from_client(&fs, item->client, 0) == 0)
+ fsp = &fs;
+ else
+ goto out;
+ cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name);
+ }
+
+out:
+ item->client = saved;
+ if (retval == CMD_RETURN_ERROR)
+ cmdq_guard(item, "error", flags);
+ else
+ cmdq_guard(item, "end", flags);
+ return (retval);
+}
+
+/* Get a callback for the command queue. */
+struct cmdq_item *
+cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
+{
+ struct cmdq_item *item;
+
+ item = xcalloc(1, sizeof *item);
+ xasprintf(&item->name, "[%s/%p]", name, item);
+ item->type = CMDQ_CALLBACK;
+
+ item->group = 0;
+ item->state = cmdq_new_state(NULL, NULL, 0);
+
+ item->cb = cb;
+ item->data = data;
+
+ return (item);
+}
+
+/* Generic error callback. */
+static enum cmd_retval
+cmdq_error_callback(struct cmdq_item *item, void *data)
+{
+ char *error = data;
+
+ cmdq_error(item, "%s", error);
+ free(error);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+/* Get an error callback for the command queue. */
+struct cmdq_item *
+cmdq_get_error(const char *error)
+{
+ return (cmdq_get_callback(cmdq_error_callback, xstrdup(error)));
+}
+
+/* Fire callback on callback queue. */
+static enum cmd_retval
+cmdq_fire_callback(struct cmdq_item *item)
+{
+ return (item->cb(item, item->data));
+}
+
+/* Process next item on command queue. */
+u_int
+cmdq_next(struct client *c)
+{
+ struct cmdq_list *queue = cmdq_get(c);
+ const char *name = cmdq_name(c);
+ struct cmdq_item *item;
+ enum cmd_retval retval;
+ u_int items = 0;
+ static u_int number;
+
+ if (TAILQ_EMPTY(&queue->list)) {
+ log_debug("%s %s: empty", __func__, name);
+ return (0);
+ }
+ if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) {
+ log_debug("%s %s: waiting", __func__, name);
+ return (0);
+ }
+
+ log_debug("%s %s: enter", __func__, name);
+ for (;;) {
+ item = queue->item = TAILQ_FIRST(&queue->list);
+ if (item == NULL)
+ break;
+ log_debug("%s %s: %s (%d), flags %x", __func__, name,
+ item->name, item->type, item->flags);
+
+ /*
+ * Any item with the waiting flag set waits until an external
+ * event clears the flag (for example, a job - look at
+ * run-shell).
+ */
+ if (item->flags & CMDQ_WAITING)
+ goto waiting;
+
+ /*
+ * Items are only fired once, once the fired flag is set, a
+ * waiting flag can only be cleared by an external event.
+ */
+ if (~item->flags & CMDQ_FIRED) {
+ item->time = time(NULL);
+ item->number = ++number;
+
+ switch (item->type) {
+ case CMDQ_COMMAND:
+ retval = cmdq_fire_command(item);
+
+ /*
+ * If a command returns an error, remove any
+ * subsequent commands in the same group.
+ */
+ if (retval == CMD_RETURN_ERROR)
+ cmdq_remove_group(item);
+ break;
+ case CMDQ_CALLBACK:
+ retval = cmdq_fire_callback(item);
+ break;
+ default:
+ retval = CMD_RETURN_ERROR;
+ break;
+ }
+ item->flags |= CMDQ_FIRED;
+
+ if (retval == CMD_RETURN_WAIT) {
+ item->flags |= CMDQ_WAITING;
+ goto waiting;
+ }
+ items++;
+ }
+ cmdq_remove(item);
+ }
+ queue->item = NULL;
+
+ log_debug("%s %s: exit (empty)", __func__, name);
+ return (items);
+
+waiting:
+ log_debug("%s %s: exit (wait)", __func__, name);
+ return (items);
+}
+
+/* Get running item if any. */
+struct cmdq_item *
+cmdq_running(struct client *c)
+{
+ struct cmdq_list *queue = cmdq_get(c);
+
+ if (queue->item == NULL)
+ return (NULL);
+ if (queue->item->flags & CMDQ_WAITING)
+ return (NULL);
+ return (queue->item);
+}
+
+/* Print a guard line. */
+void
+cmdq_guard(struct cmdq_item *item, const char *guard, int flags)
+{
+ struct client *c = item->client;
+ long t = item->time;
+ u_int number = item->number;
+
+ if (c != NULL && (c->flags & CLIENT_CONTROL))
+ control_write(c, "%%%s %ld %u %d", guard, t, number, flags);
+}
+
+/* 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_start(ap, fmt);
+ xvasprintf(&msg, 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);
+}
+
+/* Show error from command. */
+void
+cmdq_error(struct cmdq_item *item, const char *fmt, ...)
+{
+ struct client *c = item->client;
+ struct cmd *cmd = item->cmd;
+ va_list ap;
+ char *msg, *tmp;
+ const char *file;
+ u_int line;
+
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ va_end(ap);
+
+ log_debug("%s: %s", __func__, msg);
+
+ if (c == NULL) {
+ cmd_get_source(cmd, &file, &line);
+ cfg_add_cause("%s:%u: %s", file, line, msg);
+ } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
+ server_add_message("%s message: %s", c->name, msg);
+ 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_error(c, "%s\n", msg);
+ c->retval = 1;
+ } else {
+ *msg = toupper((u_char) *msg);
+ status_message_set(c, -1, 1, 0, "%s", msg);
+ }
+
+ free(msg);
+}
diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c
new file mode 100644
index 0000000..6b94728
--- /dev/null
+++ b/cmd-refresh-client.c
@@ -0,0 +1,304 @@
+/* $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"
+
+/*
+ * Refresh client.
+ */
+
+static enum cmd_retval cmd_refresh_client_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_refresh_client_entry = {
+ .name = "refresh-client",
+ .alias = "refresh",
+
+ .args = { "A:B:cC:Df:F:l::LRSt:U", 0, 1, NULL },
+ .usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] "
+ "[-C XxY] [-f flags] " CMD_TARGET_CLIENT_USAGE " [adjustment]",
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG,
+ .exec = cmd_refresh_client_exec
+};
+
+static void
+cmd_refresh_client_update_subscription(struct client *tc, const char *value)
+{
+ char *copy, *split, *name, *what;
+ enum control_sub_type subtype;
+ int subid = -1;
+
+ copy = name = xstrdup(value);
+ if ((split = strchr(copy, ':')) == NULL) {
+ control_remove_sub(tc, copy);
+ goto out;
+ }
+ *split++ = '\0';
+
+ what = split;
+ if ((split = strchr(what, ':')) == NULL)
+ goto out;
+ *split++ = '\0';
+
+ if (strcmp(what, "%*") == 0)
+ subtype = CONTROL_SUB_ALL_PANES;
+ else if (sscanf(what, "%%%d", &subid) == 1 && subid >= 0)
+ subtype = CONTROL_SUB_PANE;
+ else if (strcmp(what, "@*") == 0)
+ subtype = CONTROL_SUB_ALL_WINDOWS;
+ else if (sscanf(what, "@%d", &subid) == 1 && subid >= 0)
+ subtype = CONTROL_SUB_WINDOW;
+ else
+ subtype = CONTROL_SUB_SESSION;
+ control_add_sub(tc, name, subtype, subid, split);
+
+out:
+ free(copy);
+}
+
+static enum cmd_retval
+cmd_refresh_client_control_client_size(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ const char *size = args_get(args, 'C');
+ u_int w, x, y;
+ struct client_window *cw;
+
+ if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) {
+ if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM ||
+ y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) {
+ cmdq_error(item, "size too small or too big");
+ return (CMD_RETURN_ERROR);
+ }
+ log_debug("%s: client %s window @%u: size %ux%u", __func__,
+ tc->name, w, x, y);
+ cw = server_client_add_client_window(tc, w);
+ cw->sx = x;
+ cw->sy = y;
+ tc->flags |= CLIENT_WINDOWSIZECHANGED;
+ recalculate_sizes_now(1);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (sscanf(size, "@%u:", &w) == 1) {
+ cw = server_client_get_client_window(tc, w);
+ if (cw != NULL) {
+ log_debug("%s: client %s window @%u: no size", __func__,
+ tc->name, w);
+ cw->sx = 0;
+ cw->sy = 0;
+ recalculate_sizes_now(1);
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (sscanf(size, "%u,%u", &x, &y) != 2 &&
+ sscanf(size, "%ux%u", &x, &y) != 2) {
+ cmdq_error(item, "bad size argument");
+ return (CMD_RETURN_ERROR);
+ }
+ if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM ||
+ y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) {
+ cmdq_error(item, "size too small or too big");
+ return (CMD_RETURN_ERROR);
+ }
+ tty_set_size(&tc->tty, x, y, 0, 0);
+ tc->flags |= CLIENT_SIZECHANGED;
+ recalculate_sizes_now(1);
+ return (CMD_RETURN_NORMAL);
+}
+
+static void
+cmd_refresh_client_update_offset(struct client *tc, const char *value)
+{
+ struct window_pane *wp;
+ char *copy, *split;
+ u_int pane;
+
+ if (*value != '%')
+ return;
+ copy = xstrdup(value);
+ if ((split = strchr(copy, ':')) == NULL)
+ goto out;
+ *split++ = '\0';
+
+ if (sscanf(copy, "%%%u", &pane) != 1)
+ goto out;
+ wp = window_pane_find_by_id(pane);
+ if (wp == NULL)
+ goto out;
+
+ if (strcmp(split, "on") == 0)
+ control_set_pane_on(tc, wp);
+ else if (strcmp(split, "off") == 0)
+ control_set_pane_off(tc, wp);
+ else if (strcmp(split, "continue") == 0)
+ control_continue_pane(tc, wp);
+ else if (strcmp(split, "pause") == 0)
+ control_pause_pane(tc, wp);
+
+out:
+ free(copy);
+}
+
+static enum cmd_retval
+cmd_refresh_client_clipboard(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ const char *p;
+ u_int i;
+ struct cmd_find_state fs;
+
+ p = args_get(args, 'l');
+ if (p == NULL) {
+ if (tc->flags & CLIENT_CLIPBOARDBUFFER)
+ return (CMD_RETURN_NORMAL);
+ tc->flags |= CLIENT_CLIPBOARDBUFFER;
+ } else {
+ if (cmd_find_target(&fs, item, p, CMD_FIND_PANE, 0) != 0)
+ return (CMD_RETURN_ERROR);
+ for (i = 0; i < tc->clipboard_npanes; i++) {
+ if (tc->clipboard_panes[i] == fs.wp->id)
+ break;
+ }
+ if (i != tc->clipboard_npanes)
+ return (CMD_RETURN_NORMAL);
+ tc->clipboard_panes = xreallocarray(tc->clipboard_panes,
+ tc->clipboard_npanes + 1, sizeof *tc->clipboard_panes);
+ tc->clipboard_panes[tc->clipboard_npanes++] = fs.wp->id;
+ }
+ tty_clipboard_query(&tc->tty);
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ struct tty *tty = &tc->tty;
+ struct window *w;
+ const char *errstr;
+ u_int adjust;
+ struct args_value *av;
+
+ if (args_has(args, 'c') ||
+ args_has(args, 'L') ||
+ args_has(args, 'R') ||
+ args_has(args, 'U') ||
+ args_has(args, 'D'))
+ {
+ if (args_count(args) == 0)
+ adjust = 1;
+ else {
+ adjust = strtonum(args_string(args, 0), 1, INT_MAX,
+ &errstr);
+ if (errstr != NULL) {
+ cmdq_error(item, "adjustment %s", errstr);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (args_has(args, 'c'))
+ tc->pan_window = NULL;
+ else {
+ w = tc->session->curw->window;
+ if (tc->pan_window != w) {
+ tc->pan_window = w;
+ tc->pan_ox = tty->oox;
+ tc->pan_oy = tty->ooy;
+ }
+ if (args_has(args, 'L')) {
+ if (tc->pan_ox > adjust)
+ tc->pan_ox -= adjust;
+ else
+ tc->pan_ox = 0;
+ } else if (args_has(args, 'R')) {
+ tc->pan_ox += adjust;
+ if (tc->pan_ox > w->sx - tty->osx)
+ tc->pan_ox = w->sx - tty->osx;
+ } else if (args_has(args, 'U')) {
+ if (tc->pan_oy > adjust)
+ tc->pan_oy -= adjust;
+ else
+ tc->pan_oy = 0;
+ } else if (args_has(args, 'D')) {
+ tc->pan_oy += adjust;
+ if (tc->pan_oy > w->sy - tty->osy)
+ tc->pan_oy = w->sy - tty->osy;
+ }
+ }
+ tty_update_client_offset(tc);
+ server_redraw_client(tc);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'l'))
+ return (cmd_refresh_client_clipboard(self, item));
+
+ if (args_has(args, 'F')) /* -F is an alias for -f */
+ server_client_set_flags(tc, args_get(args, 'F'));
+ if (args_has(args, 'f'))
+ server_client_set_flags(tc, args_get(args, 'f'));
+
+ if (args_has(args, 'A')) {
+ if (~tc->flags & CLIENT_CONTROL)
+ goto not_control_client;
+ av = args_first_value(args, 'A');
+ while (av != NULL) {
+ cmd_refresh_client_update_offset(tc, av->string);
+ av = args_next_value(av);
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+ if (args_has(args, 'B')) {
+ if (~tc->flags & CLIENT_CONTROL)
+ goto not_control_client;
+ av = args_first_value(args, 'B');
+ while (av != NULL) {
+ cmd_refresh_client_update_subscription(tc, av->string);
+ av = args_next_value(av);
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+ if (args_has(args, 'C')) {
+ if (~tc->flags & CLIENT_CONTROL)
+ goto not_control_client;
+ return (cmd_refresh_client_control_client_size(self, item));
+ }
+
+ if (args_has(args, 'S')) {
+ tc->flags |= CLIENT_STATUSFORCE;
+ server_status_client(tc);
+ } else {
+ tc->flags |= CLIENT_STATUSFORCE;
+ server_redraw_client(tc);
+ }
+ return (CMD_RETURN_NORMAL);
+
+not_control_client:
+ cmdq_error(item, "not a control client");
+ return (CMD_RETURN_ERROR);
+}
diff --git a/cmd-rename-session.c b/cmd-rename-session.c
new file mode 100644
index 0000000..694f3c9
--- /dev/null
+++ b/cmd-rename-session.c
@@ -0,0 +1,81 @@
+/* $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"
+
+/*
+ * Change session name.
+ */
+
+static enum cmd_retval cmd_rename_session_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_rename_session_entry = {
+ .name = "rename-session",
+ .alias = "rename",
+
+ .args = { "t:", 1, 1, NULL },
+ .usage = CMD_TARGET_SESSION_USAGE " new-name",
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_rename_session_exec
+};
+
+static enum cmd_retval
+cmd_rename_session_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 session *s = target->s;
+ char *newname, *tmp;
+
+ tmp = format_single_from_target(item, args_string(args, 0));
+ newname = session_check_name(tmp);
+ if (newname == NULL) {
+ cmdq_error(item, "invalid session: %s", tmp);
+ free(tmp);
+ return (CMD_RETURN_ERROR);
+ }
+ free(tmp);
+ if (strcmp(newname, s->name) == 0) {
+ free(newname);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (session_find(newname) != NULL) {
+ cmdq_error(item, "duplicate session: %s", newname);
+ free(newname);
+ return (CMD_RETURN_ERROR);
+ }
+
+ RB_REMOVE(sessions, &sessions, s);
+ free(s->name);
+ s->name = newname;
+ RB_INSERT(sessions, &sessions, s);
+
+ server_status_session(s);
+ notify_session("session-renamed", s);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-rename-window.c b/cmd-rename-window.c
new file mode 100644
index 0000000..472b571
--- /dev/null
+++ b/cmd-rename-window.c
@@ -0,0 +1,62 @@
+/* $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 "tmux.h"
+
+/*
+ * Rename a window.
+ */
+
+static enum cmd_retval cmd_rename_window_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_rename_window_entry = {
+ .name = "rename-window",
+ .alias = "renamew",
+
+ .args = { "t:", 1, 1, NULL },
+ .usage = CMD_TARGET_WINDOW_USAGE " new-name",
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_rename_window_exec
+};
+
+static enum cmd_retval
+cmd_rename_window_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 winlink *wl = target->wl;
+ char *newname;
+
+ newname = format_single_from_target(item, args_string(args, 0));
+ window_set_name(wl->window, newname);
+ options_set_number(wl->window->options, "automatic-rename", 0);
+
+ server_redraw_window_borders(wl->window);
+ server_status_window(wl->window);
+ free(newname);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c
new file mode 100644
index 0000000..c943944
--- /dev/null
+++ b/cmd-resize-pane.c
@@ -0,0 +1,215 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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"
+
+/*
+ * Increase or decrease pane size.
+ */
+
+static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *);
+
+static void cmd_resize_pane_mouse_update(struct client *,
+ struct mouse_event *);
+
+const struct cmd_entry cmd_resize_pane_entry = {
+ .name = "resize-pane",
+ .alias = "resizep",
+
+ .args = { "DLMRTt:Ux:y:Z", 0, 1, NULL },
+ .usage = "[-DLMRTUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " "
+ "[adjustment]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_resize_pane_exec
+};
+
+static enum cmd_retval
+cmd_resize_pane_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 key_event *event = cmdq_get_event(item);
+ struct window_pane *wp = target->wp;
+ struct winlink *wl = target->wl;
+ struct window *w = wl->window;
+ struct client *c = cmdq_get_client(item);
+ struct session *s = target->s;
+ const char *errstr;
+ char *cause;
+ u_int adjust;
+ int x, y, status;
+ struct grid *gd = wp->base.grid;
+
+ if (args_has(args, 'T')) {
+ if (!TAILQ_EMPTY(&wp->modes))
+ return (CMD_RETURN_NORMAL);
+ adjust = screen_size_y(&wp->base) - 1 - wp->base.cy;
+ if (adjust > gd->hsize)
+ adjust = gd->hsize;
+ grid_remove_history(gd, adjust);
+ wp->base.cy += adjust;
+ wp->flags |= PANE_REDRAW;
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'M')) {
+ if (!event->m.valid || cmd_mouse_window(&event->m, &s) == NULL)
+ return (CMD_RETURN_NORMAL);
+ if (c == NULL || c->session != s)
+ return (CMD_RETURN_NORMAL);
+ c->tty.mouse_drag_update = cmd_resize_pane_mouse_update;
+ cmd_resize_pane_mouse_update(c, &event->m);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'Z')) {
+ if (w->flags & WINDOW_ZOOMED)
+ window_unzoom(w);
+ else
+ window_zoom(wp);
+ server_redraw_window(w);
+ return (CMD_RETURN_NORMAL);
+ }
+ server_unzoom_window(w);
+
+ if (args_count(args) == 0)
+ adjust = 1;
+ else {
+ adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ cmdq_error(item, "adjustment %s", errstr);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (args_has(args, 'x')) {
+ x = args_percentage(args, 'x', 0, INT_MAX, w->sx, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "width %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x);
+ }
+ if (args_has(args, 'y')) {
+ y = args_percentage(args, 'y', 0, INT_MAX, w->sy, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "height %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ status = options_get_number(w->options, "pane-border-status");
+ switch (status) {
+ case PANE_STATUS_TOP:
+ if (y != INT_MAX && wp->yoff == 1)
+ y++;
+ break;
+ case PANE_STATUS_BOTTOM:
+ if (y != INT_MAX && wp->yoff + wp->sy == w->sy - 1)
+ y++;
+ break;
+ }
+ layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y);
+ }
+
+ if (args_has(args, 'L'))
+ layout_resize_pane(wp, LAYOUT_LEFTRIGHT, -adjust, 1);
+ else if (args_has(args, 'R'))
+ layout_resize_pane(wp, LAYOUT_LEFTRIGHT, adjust, 1);
+ else if (args_has(args, 'U'))
+ layout_resize_pane(wp, LAYOUT_TOPBOTTOM, -adjust, 1);
+ else if (args_has(args, 'D'))
+ layout_resize_pane(wp, LAYOUT_TOPBOTTOM, adjust, 1);
+ server_redraw_window(wl->window);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static void
+cmd_resize_pane_mouse_update(struct client *c, struct mouse_event *m)
+{
+ struct winlink *wl;
+ struct window *w;
+ u_int y, ly, x, lx;
+ static const int offsets[][2] = {
+ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 },
+ };
+ struct layout_cell *cells[nitems(offsets)], *lc;
+ u_int ncells = 0, i, j, resizes = 0;
+ enum layout_type type;
+
+ wl = cmd_mouse_window(m, NULL);
+ if (wl == NULL) {
+ c->tty.mouse_drag_update = NULL;
+ return;
+ }
+ w = wl->window;
+
+ y = m->y + m->oy; x = m->x + m->ox;
+ if (m->statusat == 0 && y >= m->statuslines)
+ y -= m->statuslines;
+ else if (m->statusat > 0 && y >= (u_int)m->statusat)
+ y = m->statusat - 1;
+ ly = m->ly + m->oy; lx = m->lx + m->ox;
+ if (m->statusat == 0 && ly >= m->statuslines)
+ ly -= m->statuslines;
+ else if (m->statusat > 0 && ly >= (u_int)m->statusat)
+ ly = m->statusat - 1;
+
+ for (i = 0; i < nitems(cells); i++) {
+ lc = layout_search_by_border(w->layout_root, lx + offsets[i][0],
+ ly + offsets[i][1]);
+ if (lc == NULL)
+ continue;
+
+ for (j = 0; j < ncells; j++) {
+ if (cells[j] == lc) {
+ lc = NULL;
+ break;
+ }
+ }
+ if (lc == NULL)
+ continue;
+
+ cells[ncells] = lc;
+ ncells++;
+ }
+ if (ncells == 0)
+ return;
+
+ for (i = 0; i < ncells; i++) {
+ type = cells[i]->parent->type;
+ if (y != ly && type == LAYOUT_TOPBOTTOM) {
+ layout_resize_layout(w, cells[i], type, y - ly, 0);
+ resizes++;
+ } else if (x != lx && type == LAYOUT_LEFTRIGHT) {
+ layout_resize_layout(w, cells[i], type, x - lx, 0);
+ resizes++;
+ }
+ }
+ if (resizes != 0)
+ server_redraw_window(w);
+}
diff --git a/cmd-resize-window.c b/cmd-resize-window.c
new file mode 100644
index 0000000..ad73916
--- /dev/null
+++ b/cmd-resize-window.c
@@ -0,0 +1,116 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2018 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 "tmux.h"
+
+/*
+ * Increase or decrease window size.
+ */
+
+static enum cmd_retval cmd_resize_window_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_resize_window_entry = {
+ .name = "resize-window",
+ .alias = "resizew",
+
+ .args = { "aADLRt:Ux:y:", 0, 1, NULL },
+ .usage = "[-aADLRU] [-x width] [-y height] " CMD_TARGET_WINDOW_USAGE " "
+ "[adjustment]",
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_resize_window_exec
+};
+
+static enum cmd_retval
+cmd_resize_window_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 winlink *wl = target->wl;
+ struct window *w = wl->window;
+ struct session *s = target->s;
+ const char *errstr;
+ char *cause;
+ u_int adjust, sx, sy;
+ int xpixel = -1, ypixel = -1;
+
+ if (args_count(args) == 0)
+ adjust = 1;
+ else {
+ adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ cmdq_error(item, "adjustment %s", errstr);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ sx = w->sx;
+ sy = w->sy;
+
+ if (args_has(args, 'x')) {
+ sx = args_strtonum(args, 'x', WINDOW_MINIMUM, WINDOW_MAXIMUM,
+ &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "width %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+ if (args_has(args, 'y')) {
+ sy = args_strtonum(args, 'y', WINDOW_MINIMUM, WINDOW_MAXIMUM,
+ &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "height %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (args_has(args, 'L')) {
+ if (sx >= adjust)
+ sx -= adjust;
+ } else if (args_has(args, 'R'))
+ sx += adjust;
+ else if (args_has(args, 'U')) {
+ if (sy >= adjust)
+ sy -= adjust;
+ } else if (args_has(args, 'D'))
+ sy += adjust;
+
+ if (args_has(args, 'A')) {
+ default_window_size(NULL, s, w, &sx, &sy, &xpixel, &ypixel,
+ WINDOW_SIZE_LARGEST);
+ } else if (args_has(args, 'a')) {
+ default_window_size(NULL, s, w, &sx, &sy, &xpixel, &ypixel,
+ WINDOW_SIZE_SMALLEST);
+ }
+
+ options_set_number(w->options, "window-size", WINDOW_SIZE_MANUAL);
+ w->manual_sx = sx;
+ w->manual_sy = sy;
+ recalculate_size(w, 1);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c
new file mode 100644
index 0000000..6d60002
--- /dev/null
+++ b/cmd-respawn-pane.c
@@ -0,0 +1,98 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2011 Marcel P. Partap <mpartap@gmx.net>
+ *
+ * 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"
+
+/*
+ * Respawn a pane (restart the command). Kill existing if -k given.
+ */
+
+static enum cmd_retval cmd_respawn_pane_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_respawn_pane_entry = {
+ .name = "respawn-pane",
+ .alias = "respawnp",
+
+ .args = { "c:e:kt:", 0, -1, NULL },
+ .usage = "[-k] [-c start-directory] [-e environment] "
+ CMD_TARGET_PANE_USAGE " [shell-command]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_respawn_pane_exec
+};
+
+static enum cmd_retval
+cmd_respawn_pane_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 spawn_context sc = { 0 };
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ struct window_pane *wp = target->wp;
+ char *cause = NULL;
+ struct args_value *av;
+
+ sc.item = item;
+ sc.s = s;
+ sc.wl = wl;
+
+ sc.wp0 = wp;
+
+ args_to_vector(args, &sc.argc, &sc.argv);
+ sc.environ = environ_create();
+
+ av = args_first_value(args, 'e');
+ while (av != NULL) {
+ environ_put(sc.environ, av->string, 0);
+ av = args_next_value(av);
+ }
+
+ sc.idx = -1;
+ sc.cwd = args_get(args, 'c');
+
+ sc.flags = SPAWN_RESPAWN;
+ if (args_has(args, 'k'))
+ sc.flags |= SPAWN_KILL;
+
+ if (spawn_pane(&sc, &cause) == NULL) {
+ cmdq_error(item, "respawn pane failed: %s", cause);
+ free(cause);
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_ERROR);
+ }
+
+ wp->flags |= PANE_REDRAW;
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c
new file mode 100644
index 0000000..9a1a02c
--- /dev/null
+++ b/cmd-respawn-window.c
@@ -0,0 +1,95 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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"
+
+/*
+ * Respawn a window (restart the command). Kill existing if -k given.
+ */
+
+static enum cmd_retval cmd_respawn_window_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_respawn_window_entry = {
+ .name = "respawn-window",
+ .alias = "respawnw",
+
+ .args = { "c:e:kt:", 0, -1, NULL },
+ .usage = "[-k] [-c start-directory] [-e environment] "
+ CMD_TARGET_WINDOW_USAGE " [shell-command]",
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_respawn_window_exec
+};
+
+static enum cmd_retval
+cmd_respawn_window_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 spawn_context sc = { 0 };
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ char *cause = NULL;
+ struct args_value *av;
+
+ sc.item = item;
+ sc.s = s;
+ sc.wl = wl;
+ sc.tc = tc;
+
+ args_to_vector(args, &sc.argc, &sc.argv);
+ sc.environ = environ_create();
+
+ av = args_first_value(args, 'e');
+ while (av != NULL) {
+ environ_put(sc.environ, av->string, 0);
+ av = args_next_value(av);
+ }
+
+ sc.idx = -1;
+ sc.cwd = args_get(args, 'c');
+
+ sc.flags = SPAWN_RESPAWN;
+ if (args_has(args, 'k'))
+ sc.flags |= SPAWN_KILL;
+
+ if (spawn_window(&sc, &cause) == NULL) {
+ cmdq_error(item, "respawn window failed: %s", cause);
+ free(cause);
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_ERROR);
+ }
+
+ server_redraw_window(wl->window);
+
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c
new file mode 100644
index 0000000..0e2ed85
--- /dev/null
+++ b/cmd-rotate-window.c
@@ -0,0 +1,115 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * Rotate the panes in a window.
+ */
+
+static enum cmd_retval cmd_rotate_window_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_rotate_window_entry = {
+ .name = "rotate-window",
+ .alias = "rotatew",
+
+ .args = { "Dt:UZ", 0, 0, NULL },
+ .usage = "[-DUZ] " CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_rotate_window_exec
+};
+
+static enum cmd_retval
+cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct winlink *wl = target->wl;
+ struct window *w = wl->window;
+ struct window_pane *wp, *wp2;
+ struct layout_cell *lc;
+ u_int sx, sy, xoff, yoff;
+
+ window_push_zoom(w, 0, args_has(args, 'Z'));
+
+ if (args_has(args, 'D')) {
+ wp = TAILQ_LAST(&w->panes, window_panes);
+ TAILQ_REMOVE(&w->panes, wp, entry);
+ TAILQ_INSERT_HEAD(&w->panes, wp, entry);
+
+ lc = wp->layout_cell;
+ xoff = wp->xoff; yoff = wp->yoff;
+ sx = wp->sx; sy = wp->sy;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if ((wp2 = TAILQ_NEXT(wp, entry)) == NULL)
+ break;
+ wp->layout_cell = wp2->layout_cell;
+ if (wp->layout_cell != NULL)
+ wp->layout_cell->wp = wp;
+ wp->xoff = wp2->xoff; wp->yoff = wp2->yoff;
+ window_pane_resize(wp, wp2->sx, wp2->sy);
+ }
+ wp->layout_cell = lc;
+ if (wp->layout_cell != NULL)
+ wp->layout_cell->wp = wp;
+ wp->xoff = xoff; wp->yoff = yoff;
+ window_pane_resize(wp, sx, sy);
+
+ if ((wp = TAILQ_PREV(w->active, window_panes, entry)) == NULL)
+ wp = TAILQ_LAST(&w->panes, window_panes);
+ } else {
+ wp = TAILQ_FIRST(&w->panes);
+ TAILQ_REMOVE(&w->panes, wp, entry);
+ TAILQ_INSERT_TAIL(&w->panes, wp, entry);
+
+ lc = wp->layout_cell;
+ xoff = wp->xoff; yoff = wp->yoff;
+ sx = wp->sx; sy = wp->sy;
+ TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) {
+ if ((wp2 = TAILQ_PREV(wp, window_panes, entry)) == NULL)
+ break;
+ wp->layout_cell = wp2->layout_cell;
+ if (wp->layout_cell != NULL)
+ wp->layout_cell->wp = wp;
+ wp->xoff = wp2->xoff; wp->yoff = wp2->yoff;
+ window_pane_resize(wp, wp2->sx, wp2->sy);
+ }
+ wp->layout_cell = lc;
+ if (wp->layout_cell != NULL)
+ wp->layout_cell->wp = wp;
+ wp->xoff = xoff; wp->yoff = yoff;
+ window_pane_resize(wp, sx, sy);
+
+ if ((wp = TAILQ_NEXT(w->active, entry)) == NULL)
+ wp = TAILQ_FIRST(&w->panes);
+ }
+
+ window_set_active_pane(w, wp, 1);
+ cmd_find_from_winlink_pane(current, wl, wp, 0);
+ window_pop_zoom(w);
+ server_redraw_window(w);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-run-shell.c b/cmd-run-shell.c
new file mode 100644
index 0000000..560efac
--- /dev/null
+++ b/cmd-run-shell.c
@@ -0,0 +1,280 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
+ * Copyright (c) 2009 Nicholas Marriott <nicm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Runs a command without a window.
+ */
+
+static enum args_parse_type cmd_run_shell_args_parse(struct args *, u_int,
+ char **);
+static enum cmd_retval cmd_run_shell_exec(struct cmd *,
+ struct cmdq_item *);
+
+static void cmd_run_shell_timer(int, short, void *);
+static void cmd_run_shell_callback(struct job *);
+static void cmd_run_shell_free(void *);
+static void cmd_run_shell_print(struct job *, const char *);
+
+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]",
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = 0,
+ .exec = cmd_run_shell_exec
+};
+
+struct cmd_run_shell_data {
+ struct client *client;
+ char *cmd;
+ struct args_command_state *state;
+ char *cwd;
+ struct cmdq_item *item;
+ struct session *s;
+ int wp_id;
+ struct event timer;
+ int flags;
+};
+
+static enum args_parse_type
+cmd_run_shell_args_parse(struct args *args, __unused u_int idx,
+ __unused char **cause)
+{
+ if (args_has(args, 'C'))
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+ return (ARGS_PARSE_STRING);
+}
+
+static void
+cmd_run_shell_print(struct job *job, const char *msg)
+{
+ struct cmd_run_shell_data *cdata = job_get_data(job);
+ struct window_pane *wp = NULL;
+ struct cmd_find_state fs;
+ struct window_mode_entry *wme;
+
+ if (cdata->wp_id != -1)
+ wp = window_pane_find_by_id(cdata->wp_id);
+ if (wp == NULL && cdata->item != NULL && cdata->client != NULL)
+ wp = server_client_get_pane(cdata->client);
+ if (wp == NULL && cmd_find_from_nothing(&fs, 0) == 0)
+ wp = fs.wp;
+ if (wp == NULL)
+ return;
+
+ 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, 1, "%s", msg);
+}
+
+static enum cmd_retval
+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 *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct window_pane *wp = target->wp;
+ const char *delay, *cmd;
+ double d;
+ struct timeval tv;
+ char *end;
+ int wait = !args_has(args, 'b');
+
+ if ((delay = args_get(args, 'd')) != NULL) {
+ d = strtod(delay, &end);
+ if (*end != '\0') {
+ cmdq_error(item, "invalid delay time: %s", delay);
+ return (CMD_RETURN_ERROR);
+ }
+ } else if (args_count(args) == 0)
+ return (CMD_RETURN_NORMAL);
+
+ cdata = xcalloc(1, sizeof *cdata);
+ if (!args_has(args, 'C')) {
+ cmd = args_string(args, 0);
+ if (cmd != NULL)
+ cdata->cmd = format_single_from_target(item, cmd);
+ } else {
+ cdata->state = args_make_commands_prepare(self, item, 0, NULL,
+ wait, 1);
+ }
+
+ if (args_has(args, 't') && wp != NULL)
+ cdata->wp_id = wp->id;
+ else
+ cdata->wp_id = -1;
+
+ if (wait) {
+ cdata->client = cmdq_get_client(item);
+ cdata->item = item;
+ } else {
+ cdata->client = tc;
+ cdata->flags |= JOB_NOWAIT;
+ }
+ if (cdata->client != NULL)
+ cdata->client->references++;
+
+ cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s));
+
+ cdata->s = s;
+ if (s != NULL)
+ session_add_ref(s, __func__);
+
+ evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
+ if (delay != NULL) {
+ timerclear(&tv);
+ tv.tv_sec = (time_t)d;
+ tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
+ evtimer_add(&cdata->timer, &tv);
+ } else
+ event_active(&cdata->timer, EV_TIMEOUT, 1);
+
+ if (!wait)
+ return (CMD_RETURN_NORMAL);
+ return (CMD_RETURN_WAIT);
+}
+
+static void
+cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
+{
+ struct cmd_run_shell_data *cdata = arg;
+ struct client *c = cdata->client;
+ const char *cmd = cdata->cmd;
+ struct cmdq_item *item = cdata->item, *new_item;
+ struct cmd_list *cmdlist;
+ char *error;
+
+ if (cdata->state == NULL) {
+ if (cmd == NULL) {
+ if (cdata->item != NULL)
+ cmdq_continue(cdata->item);
+ cmd_run_shell_free(cdata);
+ return;
+ }
+ if (job_run(cmd, 0, NULL, NULL, cdata->s, cdata->cwd, NULL,
+ cmd_run_shell_callback, cmd_run_shell_free, cdata,
+ cdata->flags, -1, -1) == NULL)
+ cmd_run_shell_free(cdata);
+ return;
+ }
+
+ cmdlist = args_make_commands(cdata->state, 0, NULL, &error);
+ if (cmdlist == NULL) {
+ if (cdata->item == NULL) {
+ *error = toupper((u_char)*error);
+ status_message_set(c, -1, 1, 0, "%s", error);
+ } else
+ cmdq_error(cdata->item, "%s", error);
+ free(error);
+ } else if (item == NULL) {
+ new_item = cmdq_get_command(cmdlist, NULL);
+ cmdq_append(c, new_item);
+ } else {
+ new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
+ cmdq_insert_after(item, new_item);
+ }
+
+ if (cdata->item != NULL)
+ cmdq_continue(cdata->item);
+ cmd_run_shell_free(cdata);
+}
+
+static void
+cmd_run_shell_callback(struct job *job)
+{
+ struct cmd_run_shell_data *cdata = job_get_data(job);
+ struct bufferevent *event = job_get_event(job);
+ struct cmdq_item *item = cdata->item;
+ char *cmd = cdata->cmd, *msg = NULL, *line;
+ size_t size;
+ int retcode, status;
+
+ do {
+ line = evbuffer_readln(event->input, NULL, EVBUFFER_EOL_LF);
+ if (line != NULL) {
+ cmd_run_shell_print(job, line);
+ free(line);
+ }
+ } while (line != NULL);
+
+ size = EVBUFFER_LENGTH(event->input);
+ if (size != 0) {
+ line = xmalloc(size + 1);
+ memcpy(line, EVBUFFER_DATA(event->input), size);
+ line[size] = '\0';
+
+ cmd_run_shell_print(job, line);
+
+ free(line);
+ }
+
+ status = job_get_status(job);
+ if (WIFEXITED(status)) {
+ if ((retcode = WEXITSTATUS(status)) != 0)
+ xasprintf(&msg, "'%s' returned %d", cmd, retcode);
+ } else if (WIFSIGNALED(status)) {
+ retcode = WTERMSIG(status);
+ xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode);
+ retcode += 128;
+ } else
+ retcode = 0;
+ if (msg != NULL)
+ cmd_run_shell_print(job, msg);
+ free(msg);
+
+ if (item != NULL) {
+ if (cmdq_get_client(item) != NULL &&
+ cmdq_get_client(item)->session == NULL)
+ cmdq_get_client(item)->retval = retcode;
+ cmdq_continue(item);
+ }
+}
+
+static void
+cmd_run_shell_free(void *data)
+{
+ struct cmd_run_shell_data *cdata = data;
+
+ evtimer_del(&cdata->timer);
+ if (cdata->s != NULL)
+ session_remove_ref(cdata->s, __func__);
+ if (cdata->client != NULL)
+ server_client_unref(cdata->client);
+ if (cdata->state != NULL)
+ args_make_commands_free(cdata->state);
+ free(cdata->cwd);
+ free(cdata->cmd);
+ free(cdata);
+}
diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c
new file mode 100644
index 0000000..7d67837
--- /dev/null
+++ b/cmd-save-buffer.c
@@ -0,0 +1,117 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Saves a paste buffer to a file.
+ */
+
+static enum cmd_retval cmd_save_buffer_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_save_buffer_entry = {
+ .name = "save-buffer",
+ .alias = "saveb",
+
+ .args = { "ab:", 1, 1, NULL },
+ .usage = "[-a] " CMD_BUFFER_USAGE " path",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_save_buffer_exec
+};
+
+const struct cmd_entry cmd_show_buffer_entry = {
+ .name = "show-buffer",
+ .alias = "showb",
+
+ .args = { "b:", 0, 0, NULL },
+ .usage = CMD_BUFFER_USAGE,
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_save_buffer_exec
+};
+
+static void
+cmd_save_buffer_done(__unused struct client *c, const char *path, int error,
+ __unused int closed, __unused struct evbuffer *buffer, void *data)
+{
+ struct cmdq_item *item = data;
+
+ if (!closed)
+ return;
+
+ if (error != 0)
+ cmdq_error(item, "%s: %s", path, strerror(error));
+ cmdq_continue(item);
+}
+
+static enum cmd_retval
+cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *c = cmdq_get_client(item);
+ struct paste_buffer *pb;
+ int flags;
+ const char *bufname = args_get(args, 'b'), *bufdata;
+ size_t bufsize;
+ char *path, *tmp;
+
+ if (bufname == NULL) {
+ if ((pb = paste_get_top(NULL)) == NULL) {
+ cmdq_error(item, "no buffers");
+ return (CMD_RETURN_ERROR);
+ }
+ } else {
+ pb = paste_get_name(bufname);
+ if (pb == NULL) {
+ cmdq_error(item, "no buffer %s", bufname);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+ bufdata = paste_buffer_data(pb, &bufsize);
+
+ 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);
+ return (CMD_RETURN_NORMAL);
+ }
+ path = xstrdup("-");
+ } else
+ path = format_single_from_target(item, args_string(args, 0));
+ if (args_has(args, 'a'))
+ flags = O_APPEND;
+ else
+ flags = O_TRUNC;
+ file_write(cmdq_get_client(item), path, flags, bufdata, bufsize,
+ cmd_save_buffer_done, item);
+ free(path);
+
+ return (CMD_RETURN_WAIT);
+}
diff --git a/cmd-select-layout.c b/cmd-select-layout.c
new file mode 100644
index 0000000..6dfe2b6
--- /dev/null
+++ b/cmd-select-layout.c
@@ -0,0 +1,149 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * Switch window to selected layout.
+ */
+
+static enum cmd_retval cmd_select_layout_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_select_layout_entry = {
+ .name = "select-layout",
+ .alias = "selectl",
+
+ .args = { "Enopt:", 0, 1, NULL },
+ .usage = "[-Enop] " CMD_TARGET_PANE_USAGE " [layout-name]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_select_layout_exec
+};
+
+const struct cmd_entry cmd_next_layout_entry = {
+ .name = "next-layout",
+ .alias = "nextl",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_select_layout_exec
+};
+
+const struct cmd_entry cmd_previous_layout_entry = {
+ .name = "previous-layout",
+ .alias = "prevl",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_select_layout_exec
+};
+
+static enum cmd_retval
+cmd_select_layout_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 winlink *wl = target->wl;
+ struct window *w = wl->window;
+ struct window_pane *wp = target->wp;
+ const char *layoutname;
+ char *oldlayout, *cause;
+ int next, previous, layout;
+
+ server_unzoom_window(w);
+
+ next = (cmd_get_entry(self) == &cmd_next_layout_entry);
+ if (args_has(args, 'n'))
+ next = 1;
+ previous = (cmd_get_entry(self) == &cmd_previous_layout_entry);
+ if (args_has(args, 'p'))
+ previous = 1;
+
+ oldlayout = w->old_layout;
+ w->old_layout = layout_dump(w->layout_root);
+
+ if (next || previous) {
+ if (next)
+ layout_set_next(w);
+ else
+ layout_set_previous(w);
+ goto changed;
+ }
+
+ if (args_has(args, 'E')) {
+ layout_spread_out(wp);
+ goto changed;
+ }
+
+ if (args_count(args) != 0)
+ layoutname = args_string(args, 0);
+ else if (args_has(args, 'o'))
+ layoutname = oldlayout;
+ else
+ layoutname = NULL;
+
+ if (!args_has(args, 'o')) {
+ if (layoutname == NULL)
+ layout = w->lastlayout;
+ else
+ layout = layout_set_lookup(layoutname);
+ if (layout != -1) {
+ layout_set_select(w, layout);
+ goto changed;
+ }
+ }
+
+ if (layoutname != NULL) {
+ if (layout_parse(w, layoutname, &cause) == -1) {
+ cmdq_error(item, "%s: %s", cause, layoutname);
+ free(cause);
+ goto error;
+ }
+ goto changed;
+ }
+
+ free(oldlayout);
+ return (CMD_RETURN_NORMAL);
+
+changed:
+ free(oldlayout);
+ recalculate_sizes();
+ server_redraw_window(w);
+ notify_window("window-layout-changed", w);
+ return (CMD_RETURN_NORMAL);
+
+error:
+ free(w->old_layout);
+ w->old_layout = oldlayout;
+ return (CMD_RETURN_ERROR);
+}
diff --git a/cmd-select-pane.c b/cmd-select-pane.c
new file mode 100644
index 0000000..ae21d4c
--- /dev/null
+++ b/cmd-select-pane.c
@@ -0,0 +1,238 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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"
+
+/*
+ * Select pane.
+ */
+
+static enum cmd_retval cmd_select_pane_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_select_pane_entry = {
+ .name = "select-pane",
+ .alias = "selectp",
+
+ .args = { "DdegLlMmP:RT:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */
+ .usage = "[-DdeLlMmRUZ] [-T title] " CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_select_pane_exec
+};
+
+const struct cmd_entry cmd_last_pane_entry = {
+ .name = "last-pane",
+ .alias = "lastp",
+
+ .args = { "det:Z", 0, 0, NULL },
+ .usage = "[-deZ] " CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_select_pane_exec
+};
+
+static void
+cmd_select_pane_redraw(struct window *w)
+{
+ struct client *c;
+
+ /*
+ * Redraw entire window if it is bigger than the client (the
+ * offset may change), otherwise just draw borders.
+ */
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == NULL || (c->flags & CLIENT_CONTROL))
+ continue;
+ if (c->session->curw->window == w && tty_window_bigger(&c->tty))
+ server_redraw_client(c);
+ else {
+ if (c->session->curw->window == w)
+ c->flags |= CLIENT_REDRAWBORDERS;
+ if (session_has(c->session, w))
+ c->flags |= CLIENT_REDRAWSTATUS;
+ }
+
+ }
+}
+
+static enum cmd_retval
+cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ const struct cmd_entry *entry = cmd_get_entry(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *c = cmdq_get_client(item);
+ struct winlink *wl = target->wl;
+ struct window *w = wl->window;
+ struct session *s = target->s;
+ struct window_pane *wp = target->wp, *activewp, *lastwp, *markedwp;
+ struct options *oo = wp->options;
+ char *title;
+ const char *style;
+ struct options_entry *o;
+
+ if (entry == &cmd_last_pane_entry || args_has(args, 'l')) {
+ lastwp = w->last;
+ if (lastwp == NULL && window_count_panes(w) == 2) {
+ lastwp = TAILQ_PREV(w->active, window_panes, entry);
+ if (lastwp == NULL)
+ lastwp = TAILQ_NEXT(w->active, entry);
+ }
+ if (lastwp == NULL) {
+ cmdq_error(item, "no last pane");
+ return (CMD_RETURN_ERROR);
+ }
+ if (args_has(args, 'e')) {
+ lastwp->flags &= ~PANE_INPUTOFF;
+ server_redraw_window_borders(lastwp->window);
+ server_status_window(lastwp->window);
+ } else if (args_has(args, 'd')) {
+ lastwp->flags |= PANE_INPUTOFF;
+ server_redraw_window_borders(lastwp->window);
+ server_status_window(lastwp->window);
+ } else {
+ if (window_push_zoom(w, 0, args_has(args, 'Z')))
+ server_redraw_window(w);
+ window_redraw_active_switch(w, lastwp);
+ if (window_set_active_pane(w, lastwp, 1)) {
+ cmd_find_from_winlink(current, wl, 0);
+ cmd_select_pane_redraw(w);
+ }
+ if (window_pop_zoom(w))
+ server_redraw_window(w);
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'm') || args_has(args, 'M')) {
+ if (args_has(args, 'm') && !window_pane_visible(wp))
+ return (CMD_RETURN_NORMAL);
+ if (server_check_marked())
+ lastwp = marked_pane.wp;
+ else
+ lastwp = NULL;
+
+ if (args_has(args, 'M') || server_is_marked(s, wl, wp))
+ server_clear_marked();
+ else
+ server_set_marked(s, wl, wp);
+ markedwp = marked_pane.wp;
+
+ if (lastwp != NULL) {
+ lastwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED);
+ server_redraw_window_borders(lastwp->window);
+ server_status_window(lastwp->window);
+ }
+ if (markedwp != NULL) {
+ markedwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED);
+ server_redraw_window_borders(markedwp->window);
+ server_status_window(markedwp->window);
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+
+ style = args_get(args, 'P');
+ if (style != NULL) {
+ o = options_set_string(oo, "window-style", 0, "%s", style);
+ if (o == NULL) {
+ cmdq_error(item, "bad style: %s", style);
+ return (CMD_RETURN_ERROR);
+ }
+ options_set_string(oo, "window-active-style", 0, "%s", style);
+ wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED);
+ }
+ if (args_has(args, 'g')) {
+ cmdq_print(item, "%s", options_get_string(oo, "window-style"));
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'L')) {
+ window_push_zoom(w, 0, 1);
+ wp = window_pane_find_left(wp);
+ window_pop_zoom(w);
+ } else if (args_has(args, 'R')) {
+ window_push_zoom(w, 0, 1);
+ wp = window_pane_find_right(wp);
+ window_pop_zoom(w);
+ } else if (args_has(args, 'U')) {
+ window_push_zoom(w, 0, 1);
+ wp = window_pane_find_up(wp);
+ window_pop_zoom(w);
+ } else if (args_has(args, 'D')) {
+ window_push_zoom(w, 0, 1);
+ wp = window_pane_find_down(wp);
+ window_pop_zoom(w);
+ }
+ if (wp == NULL)
+ return (CMD_RETURN_NORMAL);
+
+ if (args_has(args, 'e')) {
+ wp->flags &= ~PANE_INPUTOFF;
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (args_has(args, 'd')) {
+ wp->flags |= PANE_INPUTOFF;
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'T')) {
+ title = format_single_from_target(item, args_get(args, 'T'));
+ if (screen_set_title(&wp->base, title)) {
+ notify_pane("pane-title-changed", wp);
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ }
+ free(title);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE))
+ activewp = server_client_get_pane(c);
+ else
+ activewp = w->active;
+ if (wp == activewp)
+ return (CMD_RETURN_NORMAL);
+ if (window_push_zoom(w, 0, args_has(args, 'Z')))
+ server_redraw_window(w);
+ window_redraw_active_switch(w, wp);
+ if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE))
+ server_client_set_pane(c, wp);
+ else if (window_set_active_pane(w, wp, 1))
+ cmd_find_from_winlink_pane(current, wl, wp, 0);
+ cmdq_insert_hook(s, item, current, "after-select-pane");
+ cmd_select_pane_redraw(w);
+ if (window_pop_zoom(w))
+ server_redraw_window(w);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-select-window.c b/cmd-select-window.c
new file mode 100644
index 0000000..4aca3e6
--- /dev/null
+++ b/cmd-select-window.c
@@ -0,0 +1,150 @@
+/* $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 "tmux.h"
+
+/*
+ * Select window by index.
+ */
+
+static enum cmd_retval cmd_select_window_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_select_window_entry = {
+ .name = "select-window",
+ .alias = "selectw",
+
+ .args = { "lnpTt:", 0, 0, NULL },
+ .usage = "[-lnpT] " CMD_TARGET_WINDOW_USAGE,
+
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_select_window_exec
+};
+
+const struct cmd_entry cmd_next_window_entry = {
+ .name = "next-window",
+ .alias = "next",
+
+ .args = { "at:", 0, 0, NULL },
+ .usage = "[-a] " CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = 0,
+ .exec = cmd_select_window_exec
+};
+
+const struct cmd_entry cmd_previous_window_entry = {
+ .name = "previous-window",
+ .alias = "prev",
+
+ .args = { "at:", 0, 0, NULL },
+ .usage = "[-a] " CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = 0,
+ .exec = cmd_select_window_exec
+};
+
+const struct cmd_entry cmd_last_window_entry = {
+ .name = "last-window",
+ .alias = "last",
+
+ .args = { "t:", 0, 0, NULL },
+ .usage = CMD_TARGET_SESSION_USAGE,
+
+ .target = { 't', CMD_FIND_SESSION, 0 },
+
+ .flags = 0,
+ .exec = cmd_select_window_exec
+};
+
+static enum cmd_retval
+cmd_select_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *c = cmdq_get_client(item);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct winlink *wl = target->wl;
+ struct session *s = target->s;
+ int next, previous, last, activity;
+
+ next = (cmd_get_entry(self) == &cmd_next_window_entry);
+ if (args_has(args, 'n'))
+ next = 1;
+ previous = (cmd_get_entry(self) == &cmd_previous_window_entry);
+ if (args_has(args, 'p'))
+ previous = 1;
+ last = (cmd_get_entry(self) == &cmd_last_window_entry);
+ if (args_has(args, 'l'))
+ last = 1;
+
+ if (next || previous || last) {
+ activity = args_has(args, 'a');
+ if (next) {
+ if (session_next(s, activity) != 0) {
+ cmdq_error(item, "no next window");
+ return (CMD_RETURN_ERROR);
+ }
+ } else if (previous) {
+ if (session_previous(s, activity) != 0) {
+ cmdq_error(item, "no previous window");
+ return (CMD_RETURN_ERROR);
+ }
+ } else {
+ if (session_last(s) != 0) {
+ cmdq_error(item, "no last window");
+ return (CMD_RETURN_ERROR);
+ }
+ }
+ cmd_find_from_session(current, s, 0);
+ server_redraw_session(s);
+ cmdq_insert_hook(s, item, current, "after-select-window");
+ } else {
+ /*
+ * If -T and select-window is invoked on same window as
+ * current, switch to previous window.
+ */
+ if (args_has(args, 'T') && wl == s->curw) {
+ if (session_last(s) != 0) {
+ cmdq_error(item, "no last window");
+ return (-1);
+ }
+ if (current->s == s)
+ cmd_find_from_session(current, s, 0);
+ server_redraw_session(s);
+ } else if (session_select(s, wl->idx) == 0) {
+ cmd_find_from_session(current, s, 0);
+ server_redraw_session(s);
+ }
+ cmdq_insert_hook(s, item, current, "after-select-window");
+ }
+ if (c != NULL && c->session != NULL)
+ s->curw->window->latest = c;
+ recalculate_sizes();
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-send-keys.c b/cmd-send-keys.c
new file mode 100644
index 0000000..d6a9543
--- /dev/null
+++ b/cmd-send-keys.c
@@ -0,0 +1,221 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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"
+
+/*
+ * Send keys to client.
+ */
+
+static enum cmd_retval cmd_send_keys_exec(struct cmd *, struct cmdq_item *);
+
+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 ...",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_send_keys_exec
+};
+
+const struct cmd_entry cmd_send_prefix_entry = {
+ .name = "send-prefix",
+ .alias = NULL,
+
+ .args = { "2t:", 0, 0, NULL },
+ .usage = "[-2] " CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_send_keys_exec
+};
+
+static struct cmdq_item *
+cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
+ key_code key)
+{
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ struct window_pane *wp = target->wp;
+ struct window_mode_entry *wme;
+ struct key_table *table;
+ struct key_binding *bd;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL || wme->mode->key_table == NULL) {
+ if (window_pane_key(wp, tc, s, wl, key, NULL) != 0)
+ return (NULL);
+ return (item);
+ }
+ table = key_bindings_get_table(wme->mode->key_table(wme), 1);
+
+ bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
+ if (bd != NULL) {
+ table->references++;
+ after = key_bindings_dispatch(bd, after, tc, NULL, target);
+ key_bindings_unref_table(table);
+ }
+ return (after);
+}
+
+static struct cmdq_item *
+cmd_send_keys_inject_string(struct cmdq_item *item, struct cmdq_item *after,
+ struct args *args, int i)
+{
+ const char *s = args_string(args, i);
+ struct utf8_data *ud, *loop;
+ utf8_char uc;
+ key_code key;
+ char *endptr;
+ long n;
+ int literal;
+
+ if (args_has(args, 'H')) {
+ 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));
+ }
+
+ 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);
+ if (after != NULL)
+ return (after);
+ }
+ literal = 1;
+ }
+ if (literal) {
+ ud = utf8_fromcstr(s);
+ for (loop = ud; loop->size != 0; loop++) {
+ if (loop->size == 1 && loop->data[0] <= 0x7f)
+ key = loop->data[0];
+ else {
+ if (utf8_from_data(loop, &uc) != UTF8_DONE)
+ continue;
+ key = uc;
+ }
+ after = cmd_send_keys_inject_key(item, after, key);
+ }
+ free(ud);
+ }
+ return (after);
+}
+
+static enum cmd_retval
+cmd_send_keys_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 client *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ struct window_pane *wp = target->wp;
+ struct key_event *event = cmdq_get_event(item);
+ struct mouse_event *m = &event->m;
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct cmdq_item *after = item;
+ key_code key;
+ u_int i, np = 1;
+ u_int count = args_count(args);
+ char *cause = NULL;
+
+ if (args_has(args, 'N')) {
+ np = args_strtonum(args, 'N', 1, UINT_MAX, &cause);
+ if (cause != NULL) {
+ cmdq_error(item, "repeat count %s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ if (wme != NULL && (args_has(args, 'X') || count == 0)) {
+ if (wme->mode->command == NULL) {
+ cmdq_error(item, "not in a mode");
+ return (CMD_RETURN_ERROR);
+ }
+ wme->prefix = np;
+ }
+ }
+
+ if (args_has(args, 'X')) {
+ if (wme == NULL || wme->mode->command == NULL) {
+ cmdq_error(item, "not in a mode");
+ return (CMD_RETURN_ERROR);
+ }
+ if (!m->valid)
+ m = NULL;
+ wme->mode->command(wme, tc, s, wl, args, m);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'M')) {
+ wp = cmd_mouse_pane(m, &s, NULL);
+ if (wp == NULL) {
+ cmdq_error(item, "no mouse target");
+ return (CMD_RETURN_ERROR);
+ }
+ window_pane_key(wp, tc, s, wl, m->key, m);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (cmd_get_entry(self) == &cmd_send_prefix_entry) {
+ if (args_has(args, '2'))
+ key = options_get_number(s->options, "prefix2");
+ else
+ key = options_get_number(s->options, "prefix");
+ cmd_send_keys_inject_key(item, item, key);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'R')) {
+ colour_palette_clear(&wp->palette);
+ input_reset(wp->ictx, 1);
+ wp->flags |= (PANE_STYLECHANGED|PANE_REDRAW);
+ }
+
+ if (count == 0) {
+ if (args_has(args, 'N') || args_has(args, 'R'))
+ return (CMD_RETURN_NORMAL);
+ for (; np != 0; np--)
+ cmd_send_keys_inject_key(item, NULL, event->key);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ for (; np != 0; np--) {
+ for (i = 0; i < count; i++) {
+ after = cmd_send_keys_inject_string(item, after, args,
+ i);
+ }
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-server-access.c b/cmd-server-access.c
new file mode 100644
index 0000000..b2b718b
--- /dev/null
+++ b/cmd-server-access.c
@@ -0,0 +1,147 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Controls access to session.
+ */
+
+static enum cmd_retval cmd_server_access_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_server_access_entry = {
+ .name = "server-access",
+ .alias = NULL,
+
+ .args = { "adlrw", 0, 1, NULL },
+ .usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]",
+
+ .flags = CMD_CLIENT_CANFAIL,
+ .exec = cmd_server_access_exec
+};
+
+static enum cmd_retval
+cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw)
+{
+ struct client *loop;
+ struct server_acl_user *user;
+ uid_t uid;
+
+ if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
+ cmdq_error(item, "user %s not found", pw->pw_name);
+ return (CMD_RETURN_ERROR);
+ }
+ TAILQ_FOREACH(loop, &clients, entry) {
+ uid = proc_get_peer_uid(loop->peer);
+ if (uid == server_acl_get_uid(user)) {
+ loop->exit_message = xstrdup("access not allowed");
+ loop->flags |= CLIENT_EXIT;
+ }
+ }
+ server_acl_user_deny(pw->pw_uid);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
+{
+
+ struct args *args = cmd_get_args(self);
+ struct client *c = cmdq_get_target_client(item);
+ char *name;
+ struct passwd *pw = NULL;
+
+ if (args_has(args, 'l')) {
+ server_acl_display(item);
+ return (CMD_RETURN_NORMAL);
+ }
+ if (args_count(args) == 0) {
+ cmdq_error(item, "missing user argument");
+ return (CMD_RETURN_ERROR);
+ }
+
+ name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
+ if (*name != '\0')
+ pw = getpwnam(name);
+ if (pw == NULL) {
+ cmdq_error(item, "unknown user: %s", name);
+ return (CMD_RETURN_ERROR);
+ }
+ free(name);
+
+ if (pw->pw_uid == 0 || pw->pw_uid == getuid()) {
+ cmdq_error(item, "%s owns the server, can't change access",
+ pw->pw_name);
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'a') && args_has(args, 'd')) {
+ cmdq_error(item, "-a and -d cannot be used together");
+ return (CMD_RETURN_ERROR);
+ }
+ if (args_has(args, 'w') && args_has(args, 'r')) {
+ cmdq_error(item, "-r and -w cannot be used together");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'd'))
+ return (cmd_server_access_deny(item, pw));
+ if (args_has(args, 'a')) {
+ if (server_acl_user_find(pw->pw_uid) != NULL) {
+ cmdq_error(item, "user %s is already added",
+ pw->pw_name);
+ return (CMD_RETURN_ERROR);
+ }
+ server_acl_user_allow(pw->pw_uid);
+ /* Do not return - allow -r or -w with -a. */
+ } else if (args_has(args, 'r') || args_has(args, 'w')) {
+ /* -r or -w implies -a if user does not exist. */
+ if (server_acl_user_find(pw->pw_uid) == NULL)
+ server_acl_user_allow(pw->pw_uid);
+ }
+
+ if (args_has(args, 'w')) {
+ if (server_acl_user_find(pw->pw_uid) == NULL) {
+ cmdq_error(item, "user %s not found", pw->pw_name);
+ return (CMD_RETURN_ERROR);
+ }
+ server_acl_user_allow_write(pw->pw_uid);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'r')) {
+ if (server_acl_user_find(pw->pw_uid) == NULL) {
+ cmdq_error(item, "user %s not found", pw->pw_name);
+ return (CMD_RETURN_ERROR);
+ }
+ server_acl_user_deny_write(pw->pw_uid);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c
new file mode 100644
index 0000000..9112683
--- /dev/null
+++ b/cmd-set-buffer.c
@@ -0,0 +1,127 @@
+/* $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"
+
+/*
+ * Add, set, append to or delete a paste buffer.
+ */
+
+static enum cmd_retval cmd_set_buffer_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_set_buffer_entry = {
+ .name = "set-buffer",
+ .alias = "setb",
+
+ .args = { "ab:t:n:w", 0, 1, NULL },
+ .usage = "[-aw] " CMD_BUFFER_USAGE " [-n new-buffer-name] "
+ CMD_TARGET_CLIENT_USAGE " data",
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG|CMD_CLIENT_CANFAIL,
+ .exec = cmd_set_buffer_exec
+};
+
+const struct cmd_entry cmd_delete_buffer_entry = {
+ .name = "delete-buffer",
+ .alias = "deleteb",
+
+ .args = { "b:", 0, 0, NULL },
+ .usage = CMD_BUFFER_USAGE,
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_set_buffer_exec
+};
+
+static enum cmd_retval
+cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ struct paste_buffer *pb;
+ char *bufdata, *cause;
+ const char *bufname, *olddata;
+ size_t bufsize, newsize;
+
+ bufname = args_get(args, 'b');
+ if (bufname == NULL)
+ pb = NULL;
+ else
+ pb = paste_get_name(bufname);
+
+ if (cmd_get_entry(self) == &cmd_delete_buffer_entry) {
+ if (pb == NULL)
+ pb = paste_get_top(&bufname);
+ if (pb == NULL) {
+ cmdq_error(item, "no buffer");
+ return (CMD_RETURN_ERROR);
+ }
+ paste_free(pb);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'n')) {
+ if (pb == NULL)
+ pb = paste_get_top(&bufname);
+ if (pb == NULL) {
+ cmdq_error(item, "no buffer");
+ return (CMD_RETURN_ERROR);
+ }
+ if (paste_rename(bufname, args_get(args, 'n'), &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_count(args) != 1) {
+ cmdq_error(item, "no data specified");
+ return (CMD_RETURN_ERROR);
+ }
+ if ((newsize = strlen(args_string(args, 0))) == 0)
+ return (CMD_RETURN_NORMAL);
+
+ bufsize = 0;
+ bufdata = NULL;
+
+ if (args_has(args, 'a') && pb != NULL) {
+ olddata = paste_buffer_data(pb, &bufsize);
+ bufdata = xmalloc(bufsize);
+ memcpy(bufdata, olddata, bufsize);
+ }
+
+ bufdata = xrealloc(bufdata, bufsize + newsize);
+ memcpy(bufdata + bufsize, args_string(args, 0), newsize);
+ bufsize += newsize;
+
+ if (paste_set(bufdata, bufsize, bufname, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(bufdata);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ if (args_has(args, 'w') && tc != NULL)
+ tty_set_selection(&tc->tty, bufdata, bufsize);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-set-environment.c b/cmd-set-environment.c
new file mode 100644
index 0000000..cec1f3e
--- /dev/null
+++ b/cmd-set-environment.c
@@ -0,0 +1,119 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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"
+
+/*
+ * Set an environment variable.
+ */
+
+static enum cmd_retval cmd_set_environment_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_set_environment_entry = {
+ .name = "set-environment",
+ .alias = "setenv",
+
+ .args = { "Fhgrt:u", 1, 2, NULL },
+ .usage = "[-Fhgru] " CMD_TARGET_SESSION_USAGE " name [value]",
+
+ .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_set_environment_exec
+};
+
+static enum cmd_retval
+cmd_set_environment_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 environ *env;
+ const char *name = args_string(args, 0), *value;
+ const char *tflag;
+ char *expanded = NULL;
+ enum cmd_retval retval = CMD_RETURN_NORMAL;
+
+ if (*name == '\0') {
+ cmdq_error(item, "empty variable name");
+ return (CMD_RETURN_ERROR);
+ }
+ if (strchr(name, '=') != NULL) {
+ cmdq_error(item, "variable name contains =");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_count(args) < 2)
+ value = NULL;
+ else
+ value = args_string(args, 1);
+ if (value != NULL && args_has(args, 'F')) {
+ expanded = format_single_from_target(item, value);
+ value = expanded;
+ }
+ if (args_has(args, 'g'))
+ env = global_environ;
+ else {
+ if (target->s == NULL) {
+ tflag = args_get(args, 't');
+ if (tflag != NULL)
+ cmdq_error(item, "no such session: %s", tflag);
+ else
+ cmdq_error(item, "no current session");
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+ env = target->s->environ;
+ }
+
+ if (args_has(args, 'u')) {
+ if (value != NULL) {
+ cmdq_error(item, "can't specify a value with -u");
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+ environ_unset(env, name);
+ } else if (args_has(args, 'r')) {
+ if (value != NULL) {
+ cmdq_error(item, "can't specify a value with -r");
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+ environ_clear(env, name);
+ } else {
+ if (value == NULL) {
+ cmdq_error(item, "no value specified");
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+
+ if (args_has(args, 'h'))
+ environ_set(env, name, ENVIRON_HIDDEN, "%s", value);
+ else
+ environ_set(env, name, 0, "%s", value);
+ }
+
+out:
+ free(expanded);
+ return (retval);
+}
diff --git a/cmd-set-option.c b/cmd-set-option.c
new file mode 100644
index 0000000..fbe32cf
--- /dev/null
+++ b/cmd-set-option.c
@@ -0,0 +1,239 @@
+/* $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"
+
+/*
+ * Set an option.
+ */
+
+static enum args_parse_type cmd_set_option_args_parse(struct args *,
+ u_int, char **);
+static enum cmd_retval cmd_set_option_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_set_option_entry = {
+ .name = "set-option",
+ .alias = "set",
+
+ .args = { "aFgopqst:uUw", 1, 2, cmd_set_option_args_parse },
+ .usage = "[-aFgopqsuUw] " CMD_TARGET_PANE_USAGE " option [value]",
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_set_option_exec
+};
+
+const struct cmd_entry cmd_set_window_option_entry = {
+ .name = "set-window-option",
+ .alias = "setw",
+
+ .args = { "aFgoqt:u", 1, 2, cmd_set_option_args_parse },
+ .usage = "[-aFgoqu] " CMD_TARGET_WINDOW_USAGE " option [value]",
+
+ .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_set_option_exec
+};
+
+const struct cmd_entry cmd_set_hook_entry = {
+ .name = "set-hook",
+ .alias = NULL,
+
+ .args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse },
+ .usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]",
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_set_option_exec
+};
+
+static enum args_parse_type
+cmd_set_option_args_parse(__unused struct args *args, u_int idx,
+ __unused char **cause)
+{
+ if (idx == 1)
+ return (ARGS_PARSE_COMMANDS_OR_STRING);
+ return (ARGS_PARSE_STRING);
+}
+
+static enum cmd_retval
+cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ int append = args_has(args, 'a');
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct window_pane *loop;
+ struct options *oo;
+ struct options_entry *parent, *o, *po;
+ char *name, *argument, *expanded = NULL;
+ char *cause;
+ const char *value;
+ int window, idx, already, error, ambiguous;
+ int scope;
+
+ window = (cmd_get_entry(self) == &cmd_set_window_option_entry);
+
+ /* Expand argument. */
+ argument = format_single_from_target(item, args_string(args, 0));
+
+ /* If set-hook -R, fire the hook straight away. */
+ if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) {
+ notify_hook(item, argument);
+ free(argument);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ /* Parse option name and index. */
+ name = options_match(argument, &idx, &ambiguous);
+ if (name == NULL) {
+ if (args_has(args, 'q'))
+ goto out;
+ if (ambiguous)
+ cmdq_error(item, "ambiguous option: %s", argument);
+ else
+ cmdq_error(item, "invalid option: %s", argument);
+ goto fail;
+ }
+ if (args_count(args) < 2)
+ value = NULL;
+ else
+ value = args_string(args, 1);
+ if (value != NULL && args_has(args, 'F')) {
+ expanded = format_single_from_target(item, value);
+ value = expanded;
+ }
+
+ /* Get the scope and table for the option .*/
+ scope = options_scope_from_name(args, window, name, target, &oo,
+ &cause);
+ if (scope == OPTIONS_TABLE_NONE) {
+ if (args_has(args, 'q'))
+ goto out;
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ o = options_get_only(oo, name);
+ parent = options_get(oo, name);
+
+ /* Check that array options and indexes match up. */
+ if (idx != -1 && (*name == '@' || !options_is_array(parent))) {
+ cmdq_error(item, "not an array: %s", argument);
+ goto fail;
+ }
+
+ /* With -o, check this option is not already set. */
+ if (!args_has(args, 'u') && args_has(args, 'o')) {
+ if (idx == -1)
+ already = (o != NULL);
+ else {
+ if (o == NULL)
+ already = 0;
+ else
+ already = (options_array_get(o, idx) != NULL);
+ }
+ if (already) {
+ if (args_has(args, 'q'))
+ goto out;
+ cmdq_error(item, "already set: %s", argument);
+ goto fail;
+ }
+ }
+
+ /* Change the option. */
+ if (args_has(args, 'U') && scope == OPTIONS_TABLE_WINDOW) {
+ TAILQ_FOREACH(loop, &target->w->panes, entry) {
+ po = options_get_only(loop->options, name);
+ if (po == NULL)
+ continue;
+ if (options_remove_or_default(po, idx, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ }
+ }
+ if (args_has(args, 'u') || args_has(args, 'U')) {
+ if (o == NULL)
+ goto out;
+ if (options_remove_or_default(o, idx, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ } else if (*name == '@') {
+ if (value == NULL) {
+ cmdq_error(item, "empty value");
+ goto fail;
+ }
+ options_set_string(oo, name, append, "%s", value);
+ } else if (idx == -1 && !options_is_array(parent)) {
+ error = options_from_string(oo, options_table_entry(parent),
+ options_table_entry(parent)->name, value,
+ args_has(args, 'a'), &cause);
+ if (error != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ } else {
+ if (value == NULL) {
+ cmdq_error(item, "empty value");
+ goto fail;
+ }
+ if (o == NULL)
+ o = options_empty(oo, options_table_entry(parent));
+ if (idx == -1) {
+ if (!append)
+ options_array_clear(o);
+ if (options_array_assign(o, value, &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ } else if (options_array_set(o, idx, value, append,
+ &cause) != 0) {
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ }
+
+ options_push_changes(name);
+
+out:
+ free(argument);
+ free(expanded);
+ free(name);
+ return (CMD_RETURN_NORMAL);
+
+fail:
+ free(argument);
+ free(expanded);
+ free(name);
+ return (CMD_RETURN_ERROR);
+}
diff --git a/cmd-show-environment.c b/cmd-show-environment.c
new file mode 100644
index 0000000..b52db36
--- /dev/null
+++ b/cmd-show-environment.c
@@ -0,0 +1,143 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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"
+
+/*
+ * Show environment.
+ */
+
+static enum cmd_retval cmd_show_environment_exec(struct cmd *,
+ struct cmdq_item *);
+
+static char *cmd_show_environment_escape(struct environ_entry *);
+static void cmd_show_environment_print(struct cmd *, struct cmdq_item *,
+ struct environ_entry *);
+
+const struct cmd_entry cmd_show_environment_entry = {
+ .name = "show-environment",
+ .alias = "showenv",
+
+ .args = { "hgst:", 0, 1, NULL },
+ .usage = "[-hgs] " CMD_TARGET_SESSION_USAGE " [name]",
+
+ .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_show_environment_exec
+};
+
+static char *
+cmd_show_environment_escape(struct environ_entry *envent)
+{
+ const char *value = envent->value;
+ char c, *out, *ret;
+
+ out = ret = xmalloc(strlen(value) * 2 + 1); /* at most twice the size */
+ while ((c = *value++) != '\0') {
+ /* POSIX interprets $ ` " and \ in double quotes. */
+ if (c == '$' || c == '`' || c == '"' || c == '\\')
+ *out++ = '\\';
+ *out++ = c;
+ }
+ *out = '\0';
+
+ return (ret);
+}
+
+static void
+cmd_show_environment_print(struct cmd *self, struct cmdq_item *item,
+ struct environ_entry *envent)
+{
+ struct args *args = cmd_get_args(self);
+ char *escaped;
+
+ if (!args_has(args, 'h') && (envent->flags & ENVIRON_HIDDEN))
+ return;
+ if (args_has(args, 'h') && (~envent->flags & ENVIRON_HIDDEN))
+ return;
+
+ if (!args_has(args, 's')) {
+ if (envent->value != NULL)
+ cmdq_print(item, "%s=%s", envent->name, envent->value);
+ else
+ cmdq_print(item, "-%s", envent->name);
+ return;
+ }
+
+ if (envent->value != NULL) {
+ escaped = cmd_show_environment_escape(envent);
+ cmdq_print(item, "%s=\"%s\"; export %s;", envent->name, escaped,
+ envent->name);
+ free(escaped);
+ } else
+ cmdq_print(item, "unset %s;", envent->name);
+}
+
+static enum cmd_retval
+cmd_show_environment_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 environ *env;
+ struct environ_entry *envent;
+ const char *tflag, *name = args_string(args, 0);
+
+ if ((tflag = args_get(args, 't')) != NULL) {
+ if (target->s == NULL) {
+ cmdq_error(item, "no such session: %s", tflag);
+ return (CMD_RETURN_ERROR);
+ }
+ }
+
+ if (args_has(args, 'g'))
+ env = global_environ;
+ else {
+ if (target->s == NULL) {
+ tflag = args_get(args, 't');
+ if (tflag != NULL)
+ cmdq_error(item, "no such session: %s", tflag);
+ else
+ cmdq_error(item, "no current session");
+ return (CMD_RETURN_ERROR);
+ }
+ env = target->s->environ;
+ }
+
+ if (name != NULL) {
+ envent = environ_find(env, name);
+ if (envent == NULL) {
+ cmdq_error(item, "unknown variable: %s", name);
+ return (CMD_RETURN_ERROR);
+ }
+ cmd_show_environment_print(self, item, envent);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ envent = environ_first(env);
+ while (envent != NULL) {
+ cmd_show_environment_print(self, item, envent);
+ envent = environ_next(envent);
+ }
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-show-messages.c b/cmd-show-messages.c
new file mode 100644
index 0000000..dca3ab3
--- /dev/null
+++ b/cmd-show-messages.c
@@ -0,0 +1,107 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Show message log.
+ */
+
+#define SHOW_MESSAGES_TEMPLATE \
+ "#{t/p:message_time}: #{message_text}"
+
+static enum cmd_retval cmd_show_messages_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_show_messages_entry = {
+ .name = "show-messages",
+ .alias = "showmsgs",
+
+ .args = { "JTt:", 0, 0, NULL },
+ .usage = "[-JT] " CMD_TARGET_CLIENT_USAGE,
+
+ .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG,
+ .exec = cmd_show_messages_exec
+};
+
+static int
+cmd_show_messages_terminals(struct cmd *self, struct cmdq_item *item, int blank)
+{
+ struct args *args = cmd_get_args(self);
+ struct client *tc = cmdq_get_target_client(item);
+ struct tty_term *term;
+ u_int i, n;
+
+ n = 0;
+ LIST_FOREACH(term, &tty_terms, entry) {
+ if (args_has(args, 't') && term != tc->tty.term)
+ continue;
+ if (blank) {
+ cmdq_print(item, "%s", "");
+ blank = 0;
+ }
+ cmdq_print(item, "Terminal %u: %s for %s, flags=0x%x:", n,
+ term->name, term->tty->client->name, term->flags);
+ n++;
+ for (i = 0; i < tty_term_ncodes(); i++)
+ cmdq_print(item, "%s", tty_term_describe(term, i));
+ }
+ return (n != 0);
+}
+
+static enum cmd_retval
+cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct message_entry *msg;
+ char *s;
+ int done, blank;
+ struct format_tree *ft;
+
+ done = blank = 0;
+ if (args_has(args, 'T')) {
+ blank = cmd_show_messages_terminals(self, item, blank);
+ done = 1;
+ }
+ if (args_has(args, 'J')) {
+ job_print_summary(item, blank);
+ done = 1;
+ }
+ if (done)
+ return (CMD_RETURN_NORMAL);
+
+ ft = format_create_from_target(item);
+ TAILQ_FOREACH_REVERSE(msg, &message_log, message_list, entry) {
+ format_add(ft, "message_text", "%s", msg->msg);
+ format_add(ft, "message_number", "%u", msg->msg_num);
+ format_add_tv(ft, "message_time", &msg->msg_time);
+
+ s = format_expand(ft, SHOW_MESSAGES_TEMPLATE);
+ cmdq_print(item, "%s", s);
+ free(s);
+ }
+ format_free(ft);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-show-options.c b/cmd-show-options.c
new file mode 100644
index 0000000..252a33c
--- /dev/null
+++ b/cmd-show-options.c
@@ -0,0 +1,260 @@
+/* $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"
+
+/*
+ * Show options.
+ */
+
+static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *);
+
+static void cmd_show_options_print(struct cmd *, struct cmdq_item *,
+ struct options_entry *, int, int);
+static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *,
+ int, struct options *);
+
+const struct cmd_entry cmd_show_options_entry = {
+ .name = "show-options",
+ .alias = "show",
+
+ .args = { "AgHpqst:vw", 0, 1, NULL },
+ .usage = "[-AgHpqsvw] " CMD_TARGET_PANE_USAGE " [option]",
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_show_options_exec
+};
+
+const struct cmd_entry cmd_show_window_options_entry = {
+ .name = "show-window-options",
+ .alias = "showw",
+
+ .args = { "gvt:", 0, 1, NULL },
+ .usage = "[-gv] " CMD_TARGET_WINDOW_USAGE " [option]",
+
+ .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_show_options_exec
+};
+
+const struct cmd_entry cmd_show_hooks_entry = {
+ .name = "show-hooks",
+ .alias = NULL,
+
+ .args = { "gpt:w", 0, 1, NULL },
+ .usage = "[-gpw] " CMD_TARGET_PANE_USAGE,
+
+ .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_show_options_exec
+};
+
+static enum cmd_retval
+cmd_show_options_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 options *oo;
+ char *argument, *name = NULL, *cause;
+ int window, idx, ambiguous, parent, scope;
+ struct options_entry *o;
+
+ window = (cmd_get_entry(self) == &cmd_show_window_options_entry);
+
+ if (args_count(args) == 0) {
+ scope = options_scope_from_flags(args, window, target, &oo,
+ &cause);
+ if (scope == OPTIONS_TABLE_NONE) {
+ if (args_has(args, 'q'))
+ return (CMD_RETURN_NORMAL);
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
+ return (cmd_show_options_all(self, item, scope, oo));
+ }
+ argument = format_single_from_target(item, args_string(args, 0));
+
+ name = options_match(argument, &idx, &ambiguous);
+ if (name == NULL) {
+ if (args_has(args, 'q'))
+ goto out;
+ if (ambiguous)
+ cmdq_error(item, "ambiguous option: %s", argument);
+ else
+ cmdq_error(item, "invalid option: %s", argument);
+ goto fail;
+ }
+ scope = options_scope_from_name(args, window, name, target, &oo,
+ &cause);
+ if (scope == OPTIONS_TABLE_NONE) {
+ if (args_has(args, 'q'))
+ goto out;
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ goto fail;
+ }
+ o = options_get_only(oo, name);
+ if (args_has(args, 'A') && o == NULL) {
+ o = options_get(oo, name);
+ parent = 1;
+ } else
+ parent = 0;
+ if (o != NULL)
+ cmd_show_options_print(self, item, o, idx, parent);
+ else if (*name == '@') {
+ if (args_has(args, 'q'))
+ goto out;
+ cmdq_error(item, "invalid option: %s", argument);
+ goto fail;
+ }
+
+out:
+ free(name);
+ free(argument);
+ return (CMD_RETURN_NORMAL);
+
+fail:
+ free(name);
+ free(argument);
+ return (CMD_RETURN_ERROR);
+}
+
+static void
+cmd_show_options_print(struct cmd *self, struct cmdq_item *item,
+ struct options_entry *o, int idx, int parent)
+{
+ struct args *args = cmd_get_args(self);
+ struct options_array_item *a;
+ const char *name = options_name(o);
+ char *value, *tmp = NULL, *escaped;
+
+ if (idx != -1) {
+ xasprintf(&tmp, "%s[%d]", name, idx);
+ name = tmp;
+ } else {
+ if (options_is_array(o)) {
+ a = options_array_first(o);
+ if (a == NULL) {
+ if (!args_has(args, 'v'))
+ cmdq_print(item, "%s", name);
+ return;
+ }
+ while (a != NULL) {
+ idx = options_array_item_index(a);
+ cmd_show_options_print(self, item, o, idx,
+ parent);
+ a = options_array_next(a);
+ }
+ return;
+ }
+ }
+
+ value = options_to_string(o, idx, 0);
+ if (args_has(args, 'v'))
+ cmdq_print(item, "%s", value);
+ else if (options_is_string(o)) {
+ escaped = args_escape(value);
+ if (parent)
+ cmdq_print(item, "%s* %s", name, escaped);
+ else
+ cmdq_print(item, "%s %s", name, escaped);
+ free(escaped);
+ } else {
+ if (parent)
+ cmdq_print(item, "%s* %s", name, value);
+ else
+ cmdq_print(item, "%s %s", name, value);
+ }
+ free(value);
+
+ free(tmp);
+}
+
+static enum cmd_retval
+cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
+ struct options *oo)
+{
+ struct args *args = cmd_get_args(self);
+ const struct options_table_entry *oe;
+ struct options_entry *o;
+ struct options_array_item *a;
+ const char *name;
+ u_int idx;
+ int parent;
+
+ if (cmd_get_entry(self) != &cmd_show_hooks_entry) {
+ o = options_first(oo);
+ while (o != NULL) {
+ if (options_table_entry(o) == NULL)
+ cmd_show_options_print(self, item, o, -1, 0);
+ o = options_next(o);
+ }
+ }
+ for (oe = options_table; oe->name != NULL; oe++) {
+ if (~oe->scope & scope)
+ continue;
+
+ if ((cmd_get_entry(self) != &cmd_show_hooks_entry &&
+ !args_has(args, 'H') &&
+ (oe->flags & OPTIONS_TABLE_IS_HOOK)) ||
+ (cmd_get_entry(self) == &cmd_show_hooks_entry &&
+ (~oe->flags & OPTIONS_TABLE_IS_HOOK)))
+ continue;
+
+ o = options_get_only(oo, oe->name);
+ if (o == NULL) {
+ if (!args_has(args, 'A'))
+ continue;
+ o = options_get(oo, oe->name);
+ if (o == NULL)
+ continue;
+ parent = 1;
+ } else
+ parent = 0;
+
+ if (!options_is_array(o))
+ cmd_show_options_print(self, item, o, -1, parent);
+ else if ((a = options_array_first(o)) == NULL) {
+ if (!args_has(args, 'v')) {
+ name = options_name(o);
+ if (parent)
+ cmdq_print(item, "%s*", name);
+ else
+ cmdq_print(item, "%s", name);
+ }
+ } else {
+ while (a != NULL) {
+ idx = options_array_item_index(a);
+ cmd_show_options_print(self, item, o, idx,
+ parent);
+ a = options_array_next(a);
+ }
+ }
+ }
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c
new file mode 100644
index 0000000..c85950b
--- /dev/null
+++ b/cmd-show-prompt-history.c
@@ -0,0 +1,108 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2021 Anindya Mukherjee <anindya49@hotmail.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 "tmux.h"
+
+#include <stdlib.h>
+
+/*
+ * Show or clear prompt history.
+ */
+
+static enum cmd_retval cmd_show_prompt_history_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_show_prompt_history_entry = {
+ .name = "show-prompt-history",
+ .alias = "showphist",
+
+ .args = { "T:", 0, 0, NULL },
+ .usage = "[-T type]",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_show_prompt_history_exec
+};
+
+const struct cmd_entry cmd_clear_prompt_history_entry = {
+ .name = "clear-prompt-history",
+ .alias = "clearphist",
+
+ .args = { "T:", 0, 0, NULL },
+ .usage = "[-T type]",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_show_prompt_history_exec
+};
+
+static enum cmd_retval
+cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ const char *typestr = args_get(args, 'T');
+ enum prompt_type type;
+ u_int tidx, hidx;
+
+ if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) {
+ if (typestr == NULL) {
+ for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) {
+ free(status_prompt_hlist[tidx]);
+ status_prompt_hlist[tidx] = NULL;
+ status_prompt_hsize[tidx] = 0;
+ }
+ } else {
+ type = status_prompt_type(typestr);
+ if (type == PROMPT_TYPE_INVALID) {
+ cmdq_error(item, "invalid type: %s", typestr);
+ return (CMD_RETURN_ERROR);
+ }
+ free(status_prompt_hlist[type]);
+ status_prompt_hlist[type] = NULL;
+ status_prompt_hsize[type] = 0;
+ }
+
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (typestr == NULL) {
+ for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) {
+ cmdq_print(item, "History for %s:\n",
+ status_prompt_type_string(tidx));
+ for (hidx = 0; hidx < status_prompt_hsize[tidx];
+ hidx++) {
+ cmdq_print(item, "%d: %s", hidx + 1,
+ status_prompt_hlist[tidx][hidx]);
+ }
+ cmdq_print(item, "%s", "");
+ }
+ } else {
+ type = status_prompt_type(typestr);
+ if (type == PROMPT_TYPE_INVALID) {
+ cmdq_error(item, "invalid type: %s", typestr);
+ return (CMD_RETURN_ERROR);
+ }
+ cmdq_print(item, "History for %s:\n",
+ status_prompt_type_string(type));
+ for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) {
+ cmdq_print(item, "%d: %s", hidx + 1,
+ status_prompt_hlist[type][hidx]);
+ }
+ cmdq_print(item, "%s", "");
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-source-file.c b/cmd-source-file.c
new file mode 100644
index 0000000..255d443
--- /dev/null
+++ b/cmd-source-file.c
@@ -0,0 +1,205 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
+ *
+ * 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 <errno.h>
+#include <glob.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Sources a configuration file.
+ */
+
+static enum cmd_retval cmd_source_file_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_source_file_entry = {
+ .name = "source-file",
+ .alias = "source",
+
+ .args = { "Fnqv", 1, -1, NULL },
+ .usage = "[-Fnqv] path ...",
+
+ .flags = 0,
+ .exec = cmd_source_file_exec
+};
+
+struct cmd_source_file_data {
+ struct cmdq_item *item;
+ int flags;
+
+ struct cmdq_item *after;
+ enum cmd_retval retval;
+
+ u_int current;
+ char **files;
+ u_int nfiles;
+};
+
+static enum cmd_retval
+cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
+{
+ cfg_print_causes(item);
+ return (CMD_RETURN_NORMAL);
+}
+
+static void
+cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata)
+{
+ struct cmdq_item *new_item;
+ u_int i;
+
+ if (cfg_finished) {
+ if (cdata->retval == CMD_RETURN_ERROR &&
+ c != NULL &&
+ c->session == NULL)
+ c->retval = 1;
+ new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
+ cmdq_insert_after(cdata->after, new_item);
+ }
+
+ for (i = 0; i < cdata->nfiles; i++)
+ free(cdata->files[i]);
+ free(cdata->files);
+ free(cdata);
+}
+
+static void
+cmd_source_file_done(struct client *c, const char *path, int error,
+ int closed, struct evbuffer *buffer, void *data)
+{
+ struct cmd_source_file_data *cdata = data;
+ struct cmdq_item *item = cdata->item;
+ void *bdata = EVBUFFER_DATA(buffer);
+ size_t bsize = EVBUFFER_LENGTH(buffer);
+ u_int n;
+ struct cmdq_item *new_item;
+
+ if (!closed)
+ return;
+
+ if (error != 0)
+ 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)
+ cdata->retval = CMD_RETURN_ERROR;
+ else if (new_item != NULL)
+ cdata->after = new_item;
+ }
+
+ n = ++cdata->current;
+ if (n < cdata->nfiles)
+ file_read(c, cdata->files[n], cmd_source_file_done, cdata);
+ else {
+ cmd_source_file_complete(c, cdata);
+ cmdq_continue(item);
+ }
+}
+
+static void
+cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path)
+{
+ log_debug("%s: %s", __func__, path);
+ cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1,
+ sizeof *cdata->files);
+ cdata->files[cdata->nfiles++] = xstrdup(path);
+}
+
+static enum cmd_retval
+cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_source_file_data *cdata;
+ struct client *c = cmdq_get_client(item);
+ enum cmd_retval retval = CMD_RETURN_NORMAL;
+ char *pattern, *cwd, *expanded = NULL;
+ const char *path, *error;
+ glob_t g;
+ int result;
+ u_int i, j;
+
+ cdata = xcalloc(1, sizeof *cdata);
+ cdata->item = item;
+
+ if (args_has(args, 'q'))
+ cdata->flags |= CMD_PARSE_QUIET;
+ if (args_has(args, 'n'))
+ cdata->flags |= CMD_PARSE_PARSEONLY;
+ if (args_has(args, 'v'))
+ cdata->flags |= CMD_PARSE_VERBOSE;
+
+ utf8_stravis(&cwd, server_client_get_cwd(c, NULL), VIS_GLOB);
+
+ for (i = 0; i < args_count(args); i++) {
+ path = args_string(args, i);
+ if (args_has(args, 'F')) {
+ free(expanded);
+ expanded = format_single_from_target(item, path);
+ path = expanded;
+ }
+ if (strcmp(path, "-") == 0) {
+ cmd_source_file_add(cdata, "-");
+ continue;
+ }
+
+ if (*path == '/')
+ pattern = xstrdup(path);
+ else
+ xasprintf(&pattern, "%s/%s", cwd, path);
+ log_debug("%s: %s", __func__, pattern);
+
+ if ((result = glob(pattern, 0, NULL, &g)) != 0) {
+ if (result != GLOB_NOMATCH ||
+ (~cdata->flags & CMD_PARSE_QUIET)) {
+ if (result == GLOB_NOMATCH)
+ error = strerror(ENOENT);
+ else if (result == GLOB_NOSPACE)
+ error = strerror(ENOMEM);
+ else
+ error = strerror(EINVAL);
+ cmdq_error(item, "%s: %s", path, error);
+ retval = CMD_RETURN_ERROR;
+ }
+ globfree(&g);
+ free(pattern);
+ continue;
+ }
+ free(pattern);
+
+ for (j = 0; j < g.gl_pathc; j++)
+ cmd_source_file_add(cdata, g.gl_pathv[j]);
+ globfree(&g);
+ }
+ free(expanded);
+
+ cdata->after = item;
+ cdata->retval = retval;
+
+ if (cdata->nfiles != 0) {
+ file_read(c, cdata->files[0], cmd_source_file_done, cdata);
+ retval = CMD_RETURN_WAIT;
+ } else
+ cmd_source_file_complete(c, cdata);
+
+ free(cwd);
+ return (retval);
+}
diff --git a/cmd-split-window.c b/cmd-split-window.c
new file mode 100644
index 0000000..9947dfd
--- /dev/null
+++ b/cmd-split-window.c
@@ -0,0 +1,220 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Split a window (add a new pane).
+ */
+
+#define SPLIT_WINDOW_TEMPLATE "#{session_name}:#{window_index}.#{pane_index}"
+
+static enum cmd_retval cmd_split_window_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_split_window_entry = {
+ .name = "split-window",
+ .alias = "splitw",
+
+ .args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL },
+ .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] "
+ "[-F format] [-l size] " CMD_TARGET_PANE_USAGE
+ "[shell-command]",
+
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_split_window_exec
+};
+
+static enum cmd_retval
+cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct spawn_context sc = { 0 };
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s = target->s;
+ struct winlink *wl = target->wl;
+ struct window *w = wl->window;
+ struct window_pane *wp = target->wp, *new_wp;
+ enum layout_type type;
+ struct layout_cell *lc;
+ struct cmd_find_state fs;
+ int size, percentage, flags, input;
+ const char *template, *errstr, *p;
+ char *cause, *cp, *copy;
+ size_t plen;
+ struct args_value *av;
+ u_int count = args_count(args);
+
+ 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 (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 = -1;
+
+ window_push_zoom(wp->window, 1, args_has(args, 'Z'));
+ input = (args_has(args, 'I') && count == 0);
+
+ flags = 0;
+ if (args_has(args, 'b'))
+ flags |= SPAWN_BEFORE;
+ if (args_has(args, 'f'))
+ flags |= SPAWN_FULLSIZE;
+ if (input || (count == 1 && *args_string(args, 0) == '\0'))
+ flags |= SPAWN_EMPTY;
+
+ lc = layout_split_pane(wp, type, size, flags);
+ if (lc == NULL) {
+ cmdq_error(item, "no space for new pane");
+ return (CMD_RETURN_ERROR);
+ }
+
+ sc.item = item;
+ sc.s = s;
+ sc.wl = wl;
+
+ sc.wp0 = wp;
+ sc.lc = lc;
+
+ args_to_vector(args, &sc.argc, &sc.argv);
+ sc.environ = environ_create();
+
+ av = args_first_value(args, 'e');
+ while (av != NULL) {
+ environ_put(sc.environ, av->string, 0);
+ av = args_next_value(av);
+ }
+
+ sc.idx = -1;
+ sc.cwd = args_get(args, 'c');
+
+ sc.flags = flags;
+ if (args_has(args, 'd'))
+ sc.flags |= SPAWN_DETACHED;
+ if (args_has(args, 'Z'))
+ sc.flags |= SPAWN_ZOOM;
+
+ if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
+ cmdq_error(item, "create pane failed: %s", cause);
+ free(cause);
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_ERROR);
+ }
+ if (input) {
+ switch (window_pane_start_input(new_wp, item, &cause)) {
+ case -1:
+ server_client_remove_pane(new_wp);
+ layout_close_pane(new_wp);
+ window_remove_pane(wp->window, new_wp);
+ cmdq_error(item, "%s", cause);
+ free(cause);
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ return (CMD_RETURN_ERROR);
+ case 1:
+ input = 0;
+ break;
+ }
+ }
+ if (!args_has(args, 'd'))
+ cmd_find_from_winlink_pane(current, wl, new_wp, 0);
+ window_pop_zoom(wp->window);
+ server_redraw_window(wp->window);
+ server_status_session(s);
+
+ if (args_has(args, 'P')) {
+ if ((template = args_get(args, 'F')) == NULL)
+ template = SPLIT_WINDOW_TEMPLATE;
+ cp = format_single(item, template, tc, s, wl, new_wp);
+ cmdq_print(item, "%s", cp);
+ free(cp);
+ }
+
+ cmd_find_from_winlink_pane(&fs, wl, new_wp, 0);
+ cmdq_insert_hook(s, item, &fs, "after-split-window");
+
+ if (sc.argv != NULL)
+ cmd_free_argv(sc.argc, sc.argv);
+ environ_free(sc.environ);
+ if (input)
+ return (CMD_RETURN_WAIT);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c
new file mode 100644
index 0000000..4191b89
--- /dev/null
+++ b/cmd-swap-pane.c
@@ -0,0 +1,148 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+/*
+ * Swap two panes.
+ */
+
+static enum cmd_retval cmd_swap_pane_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_swap_pane_entry = {
+ .name = "swap-pane",
+ .alias = "swapp",
+
+ .args = { "dDs:t:UZ", 0, 0, NULL },
+ .usage = "[-dDUZ] " CMD_SRCDST_PANE_USAGE,
+
+ .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED },
+ .target = { 't', CMD_FIND_PANE, 0 },
+
+ .flags = 0,
+ .exec = cmd_swap_pane_exec
+};
+
+static enum cmd_retval
+cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct window *src_w, *dst_w;
+ struct window_pane *tmp_wp, *src_wp, *dst_wp;
+ struct layout_cell *src_lc, *dst_lc;
+ u_int sx, sy, xoff, yoff;
+
+ dst_w = target->wl->window;
+ dst_wp = target->wp;
+ src_w = source->wl->window;
+ src_wp = source->wp;
+
+ if (window_push_zoom(dst_w, 0, args_has(args, 'Z')))
+ server_redraw_window(dst_w);
+
+ if (args_has(args, 'D')) {
+ src_w = dst_w;
+ src_wp = TAILQ_NEXT(dst_wp, entry);
+ if (src_wp == NULL)
+ src_wp = TAILQ_FIRST(&dst_w->panes);
+ } else if (args_has(args, 'U')) {
+ src_w = dst_w;
+ src_wp = TAILQ_PREV(dst_wp, window_panes, entry);
+ if (src_wp == NULL)
+ src_wp = TAILQ_LAST(&dst_w->panes, window_panes);
+ }
+
+ if (src_w != dst_w && window_push_zoom(src_w, 0, args_has(args, 'Z')))
+ server_redraw_window(src_w);
+
+ if (src_wp == dst_wp)
+ goto out;
+
+ server_client_remove_pane(src_wp);
+ server_client_remove_pane(dst_wp);
+
+ tmp_wp = TAILQ_PREV(dst_wp, window_panes, entry);
+ TAILQ_REMOVE(&dst_w->panes, dst_wp, entry);
+ TAILQ_REPLACE(&src_w->panes, src_wp, dst_wp, entry);
+ if (tmp_wp == src_wp)
+ tmp_wp = dst_wp;
+ if (tmp_wp == NULL)
+ TAILQ_INSERT_HEAD(&dst_w->panes, src_wp, entry);
+ else
+ TAILQ_INSERT_AFTER(&dst_w->panes, tmp_wp, src_wp, entry);
+
+ src_lc = src_wp->layout_cell;
+ dst_lc = dst_wp->layout_cell;
+ src_lc->wp = dst_wp;
+ dst_wp->layout_cell = src_lc;
+ dst_lc->wp = src_wp;
+ src_wp->layout_cell = dst_lc;
+
+ src_wp->window = dst_w;
+ options_set_parent(src_wp->options, dst_w->options);
+ src_wp->flags |= PANE_STYLECHANGED;
+ dst_wp->window = src_w;
+ options_set_parent(dst_wp->options, src_w->options);
+ dst_wp->flags |= PANE_STYLECHANGED;
+
+ sx = src_wp->sx; sy = src_wp->sy;
+ xoff = src_wp->xoff; yoff = src_wp->yoff;
+ src_wp->xoff = dst_wp->xoff; src_wp->yoff = dst_wp->yoff;
+ window_pane_resize(src_wp, dst_wp->sx, dst_wp->sy);
+ dst_wp->xoff = xoff; dst_wp->yoff = yoff;
+ window_pane_resize(dst_wp, sx, sy);
+
+ if (!args_has(args, 'd')) {
+ if (src_w != dst_w) {
+ window_set_active_pane(src_w, dst_wp, 1);
+ window_set_active_pane(dst_w, src_wp, 1);
+ } else {
+ tmp_wp = dst_wp;
+ window_set_active_pane(src_w, tmp_wp, 1);
+ }
+ } else {
+ if (src_w->active == src_wp)
+ window_set_active_pane(src_w, dst_wp, 1);
+ if (dst_w->active == dst_wp)
+ 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;
+ }
+ server_redraw_window(src_w);
+ server_redraw_window(dst_w);
+ notify_window("window-layout-changed", src_w);
+ if (src_w != dst_w)
+ notify_window("window-layout-changed", dst_w);
+
+out:
+ if (window_pop_zoom(src_w))
+ server_redraw_window(src_w);
+ if (src_w != dst_w && window_pop_zoom(dst_w))
+ server_redraw_window(dst_w);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-swap-window.c b/cmd-swap-window.c
new file mode 100644
index 0000000..b765112
--- /dev/null
+++ b/cmd-swap-window.c
@@ -0,0 +1,94 @@
+/* $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 "tmux.h"
+
+/*
+ * Swap one window with another.
+ */
+
+static enum cmd_retval cmd_swap_window_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_swap_window_entry = {
+ .name = "swap-window",
+ .alias = "swapw",
+
+ .args = { "ds:t:", 0, 0, NULL },
+ .usage = "[-d] " CMD_SRCDST_WINDOW_USAGE,
+
+ .source = { 's', CMD_FIND_WINDOW, CMD_FIND_DEFAULT_MARKED },
+ .target = { 't', CMD_FIND_WINDOW, 0 },
+
+ .flags = 0,
+ .exec = cmd_swap_window_exec
+};
+
+static enum cmd_retval
+cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *source = cmdq_get_source(item);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct session *src = source->s, *dst = target->s;
+ struct session_group *sg_src, *sg_dst;
+ struct winlink *wl_src = source->wl, *wl_dst = target->wl;
+ struct window *w_src, *w_dst;
+
+ sg_src = session_group_contains(src);
+ sg_dst = session_group_contains(dst);
+
+ if (src != dst &&
+ sg_src != NULL &&
+ sg_dst != NULL &&
+ sg_src == sg_dst) {
+ cmdq_error(item, "can't move window, sessions are grouped");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (wl_dst->window == wl_src->window)
+ return (CMD_RETURN_NORMAL);
+
+ w_dst = wl_dst->window;
+ TAILQ_REMOVE(&w_dst->winlinks, wl_dst, wentry);
+ w_src = wl_src->window;
+ TAILQ_REMOVE(&w_src->winlinks, wl_src, wentry);
+
+ wl_dst->window = w_src;
+ TAILQ_INSERT_TAIL(&w_src->winlinks, wl_dst, wentry);
+ wl_src->window = w_dst;
+ TAILQ_INSERT_TAIL(&w_dst->winlinks, wl_src, wentry);
+
+ if (args_has(args, 'd')) {
+ session_select(dst, wl_dst->idx);
+ if (src != dst)
+ session_select(src, wl_src->idx);
+ }
+ session_group_synchronize_from(src);
+ server_redraw_session_group(src);
+ if (src != dst) {
+ session_group_synchronize_from(dst);
+ server_redraw_session_group(dst);
+ }
+ recalculate_sizes();
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-switch-client.c b/cmd-switch-client.c
new file mode 100644
index 0000000..dc1b621
--- /dev/null
+++ b/cmd-switch-client.c
@@ -0,0 +1,142 @@
+/* $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"
+
+/*
+ * Switch client to a different session.
+ */
+
+static enum cmd_retval cmd_switch_client_exec(struct cmd *,
+ struct cmdq_item *);
+
+const struct cmd_entry cmd_switch_client_entry = {
+ .name = "switch-client",
+ .alias = "switchc",
+
+ .args = { "lc:EFnpt:rT:Z", 0, 0, NULL },
+ .usage = "[-ElnprZ] [-c target-client] [-t target-session] "
+ "[-T key-table]",
+
+ /* -t is special */
+
+ .flags = CMD_READONLY|CMD_CLIENT_CFLAG,
+ .exec = cmd_switch_client_exec
+};
+
+static enum cmd_retval
+cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *current = cmdq_get_current(item);
+ struct cmd_find_state target;
+ const char *tflag = args_get(args, 't');
+ enum cmd_find_type type;
+ int flags;
+ struct client *tc = cmdq_get_target_client(item);
+ struct session *s;
+ struct winlink *wl;
+ struct window *w;
+ struct window_pane *wp;
+ const char *tablename;
+ struct key_table *table;
+
+ if (tflag != NULL && tflag[strcspn(tflag, ":.%")] != '\0') {
+ type = CMD_FIND_PANE;
+ flags = 0;
+ } else {
+ type = CMD_FIND_SESSION;
+ flags = CMD_FIND_PREFER_UNATTACHED;
+ }
+ if (cmd_find_target(&target, item, tflag, type, flags) != 0)
+ return (CMD_RETURN_ERROR);
+ s = target.s;
+ wl = target.wl;
+ wp = target.wp;
+
+ if (args_has(args, 'r')) {
+ if (tc->flags & CLIENT_READONLY)
+ tc->flags &= ~(CLIENT_READONLY|CLIENT_IGNORESIZE);
+ else
+ tc->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE);
+ }
+
+ tablename = args_get(args, 'T');
+ if (tablename != NULL) {
+ table = key_bindings_get_table(tablename, 0);
+ if (table == NULL) {
+ cmdq_error(item, "table %s doesn't exist", tablename);
+ return (CMD_RETURN_ERROR);
+ }
+ table->references++;
+ key_bindings_unref_table(tc->keytable);
+ tc->keytable = table;
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 'n')) {
+ if ((s = session_next_session(tc->session)) == NULL) {
+ cmdq_error(item, "can't find next session");
+ return (CMD_RETURN_ERROR);
+ }
+ } else if (args_has(args, 'p')) {
+ if ((s = session_previous_session(tc->session)) == NULL) {
+ cmdq_error(item, "can't find previous session");
+ return (CMD_RETURN_ERROR);
+ }
+ } else if (args_has(args, 'l')) {
+ if (tc->last_session != NULL && session_alive(tc->last_session))
+ s = tc->last_session;
+ else
+ s = NULL;
+ if (s == NULL) {
+ cmdq_error(item, "can't find last session");
+ return (CMD_RETURN_ERROR);
+ }
+ } else {
+ if (cmdq_get_client(item) == NULL)
+ return (CMD_RETURN_NORMAL);
+ if (wl != NULL && wp != NULL && wp != wl->window->active) {
+ w = wl->window;
+ if (window_push_zoom(w, 0, args_has(args, 'Z')))
+ server_redraw_window(w);
+ window_redraw_active_switch(w, wp);
+ window_set_active_pane(w, wp, 1);
+ if (window_pop_zoom(w))
+ server_redraw_window(w);
+ }
+ if (wl != NULL) {
+ session_set_current(s, wl);
+ cmd_find_from_session(current, s, 0);
+ }
+ }
+
+ if (!args_has(args, 'E'))
+ environ_update(s->options, tc->environ, s->environ);
+
+ server_client_set_session(tc, s);
+ if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT)
+ server_client_set_key_table(tc, NULL);
+
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-unbind-key.c b/cmd-unbind-key.c
new file mode 100644
index 0000000..6d91d7c
--- /dev/null
+++ b/cmd-unbind-key.c
@@ -0,0 +1,104 @@
+/* $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 "tmux.h"
+
+/*
+ * Unbind key from command.
+ */
+
+static enum cmd_retval cmd_unbind_key_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_unbind_key_entry = {
+ .name = "unbind-key",
+ .alias = "unbind",
+
+ .args = { "anqT:", 0, 1, NULL },
+ .usage = "[-anq] [-T key-table] key",
+
+ .flags = CMD_AFTERHOOK,
+ .exec = cmd_unbind_key_exec
+};
+
+static enum cmd_retval
+cmd_unbind_key_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ key_code key;
+ const char *tablename, *keystr = args_string(args, 0);
+ int quiet = args_has(args, 'q');
+
+ if (args_has(args, 'a')) {
+ if (keystr != NULL) {
+ if (!quiet)
+ cmdq_error(item, "key given with -a");
+ return (CMD_RETURN_ERROR);
+ }
+
+ tablename = args_get(args, 'T');
+ if (tablename == NULL) {
+ if (args_has(args, 'n'))
+ tablename = "root";
+ else
+ tablename = "prefix";
+ }
+ if (key_bindings_get_table(tablename, 0) == NULL) {
+ if (!quiet) {
+ cmdq_error(item, "table %s doesn't exist" ,
+ tablename);
+ }
+ return (CMD_RETURN_ERROR);
+ }
+
+ key_bindings_remove_table(tablename);
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (keystr == NULL) {
+ if (!quiet)
+ cmdq_error(item, "missing key");
+ return (CMD_RETURN_ERROR);
+ }
+
+ key = key_string_lookup_string(keystr);
+ if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
+ if (!quiet)
+ cmdq_error(item, "unknown key: %s", keystr);
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'T')) {
+ tablename = args_get(args, 'T');
+ if (key_bindings_get_table(tablename, 0) == NULL) {
+ if (!quiet) {
+ cmdq_error(item, "table %s doesn't exist" ,
+ tablename);
+ }
+ return (CMD_RETURN_ERROR);
+ }
+ } else if (args_has(args, 'n'))
+ tablename = "root";
+ else
+ tablename = "prefix";
+ key_bindings_remove(tablename, key);
+ return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd-wait-for.c b/cmd-wait-for.c
new file mode 100644
index 0000000..8a6aa25
--- /dev/null
+++ b/cmd-wait-for.c
@@ -0,0 +1,264 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2013 Thiago de Arruda <tpadilha84@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"
+
+/*
+ * Block or wake a client on a named wait channel.
+ */
+
+static enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmdq_item *);
+
+const struct cmd_entry cmd_wait_for_entry = {
+ .name = "wait-for",
+ .alias = "wait",
+
+ .args = { "LSU", 1, 1, NULL },
+ .usage = "[-L|-S|-U] channel",
+
+ .flags = 0,
+ .exec = cmd_wait_for_exec
+};
+
+struct wait_item {
+ struct cmdq_item *item;
+ TAILQ_ENTRY(wait_item) entry;
+};
+
+struct wait_channel {
+ const char *name;
+ int locked;
+ int woken;
+
+ TAILQ_HEAD(, wait_item) waiters;
+ TAILQ_HEAD(, wait_item) lockers;
+
+ RB_ENTRY(wait_channel) entry;
+};
+RB_HEAD(wait_channels, wait_channel);
+static struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
+
+static int wait_channel_cmp(struct wait_channel *, struct wait_channel *);
+RB_GENERATE_STATIC(wait_channels, wait_channel, entry, wait_channel_cmp);
+
+static int
+wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
+{
+ return (strcmp(wc1->name, wc2->name));
+}
+
+static enum cmd_retval cmd_wait_for_signal(struct cmdq_item *, const char *,
+ struct wait_channel *);
+static enum cmd_retval cmd_wait_for_wait(struct cmdq_item *, const char *,
+ struct wait_channel *);
+static enum cmd_retval cmd_wait_for_lock(struct cmdq_item *, const char *,
+ struct wait_channel *);
+static enum cmd_retval cmd_wait_for_unlock(struct cmdq_item *, const char *,
+ struct wait_channel *);
+
+static struct wait_channel *cmd_wait_for_add(const char *);
+static void cmd_wait_for_remove(struct wait_channel *);
+
+static struct wait_channel *
+cmd_wait_for_add(const char *name)
+{
+ struct wait_channel *wc;
+
+ wc = xmalloc(sizeof *wc);
+ wc->name = xstrdup(name);
+
+ wc->locked = 0;
+ wc->woken = 0;
+
+ TAILQ_INIT(&wc->waiters);
+ TAILQ_INIT(&wc->lockers);
+
+ RB_INSERT(wait_channels, &wait_channels, wc);
+
+ log_debug("add wait channel %s", wc->name);
+
+ return (wc);
+}
+
+static void
+cmd_wait_for_remove(struct wait_channel *wc)
+{
+ if (wc->locked)
+ return;
+ if (!TAILQ_EMPTY(&wc->waiters) || !wc->woken)
+ return;
+
+ log_debug("remove wait channel %s", wc->name);
+
+ RB_REMOVE(wait_channels, &wait_channels, wc);
+
+ free((void *)wc->name);
+ free(wc);
+}
+
+static enum cmd_retval
+cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item)
+{
+ struct args *args = cmd_get_args(self);
+ const char *name = args_string(args, 0);
+ struct wait_channel *wc, find;
+
+ find.name = name;
+ wc = RB_FIND(wait_channels, &wait_channels, &find);
+
+ if (args_has(args, 'S'))
+ return (cmd_wait_for_signal(item, name, wc));
+ if (args_has(args, 'L'))
+ return (cmd_wait_for_lock(item, name, wc));
+ if (args_has(args, 'U'))
+ return (cmd_wait_for_unlock(item, name, wc));
+ return (cmd_wait_for_wait(item, name, wc));
+}
+
+static enum cmd_retval
+cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name,
+ struct wait_channel *wc)
+{
+ struct wait_item *wi, *wi1;
+
+ if (wc == NULL)
+ wc = cmd_wait_for_add(name);
+
+ if (TAILQ_EMPTY(&wc->waiters) && !wc->woken) {
+ log_debug("signal wait channel %s, no waiters", wc->name);
+ wc->woken = 1;
+ return (CMD_RETURN_NORMAL);
+ }
+ log_debug("signal wait channel %s, with waiters", wc->name);
+
+ TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
+ cmdq_continue(wi->item);
+
+ TAILQ_REMOVE(&wc->waiters, wi, entry);
+ free(wi);
+ }
+
+ cmd_wait_for_remove(wc);
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cmd_wait_for_wait(struct cmdq_item *item, const char *name,
+ struct wait_channel *wc)
+{
+ struct client *c = cmdq_get_client(item);
+ struct wait_item *wi;
+
+ if (c == NULL) {
+ cmdq_error(item, "not able to wait");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (wc == NULL)
+ wc = cmd_wait_for_add(name);
+
+ if (wc->woken) {
+ log_debug("wait channel %s already woken (%p)", wc->name, c);
+ cmd_wait_for_remove(wc);
+ return (CMD_RETURN_NORMAL);
+ }
+ log_debug("wait channel %s not woken (%p)", wc->name, c);
+
+ wi = xcalloc(1, sizeof *wi);
+ wi->item = item;
+ TAILQ_INSERT_TAIL(&wc->waiters, wi, entry);
+
+ return (CMD_RETURN_WAIT);
+}
+
+static enum cmd_retval
+cmd_wait_for_lock(struct cmdq_item *item, const char *name,
+ struct wait_channel *wc)
+{
+ struct wait_item *wi;
+
+ if (cmdq_get_client(item) == NULL) {
+ cmdq_error(item, "not able to lock");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (wc == NULL)
+ wc = cmd_wait_for_add(name);
+
+ if (wc->locked) {
+ wi = xcalloc(1, sizeof *wi);
+ wi->item = item;
+ TAILQ_INSERT_TAIL(&wc->lockers, wi, entry);
+ return (CMD_RETURN_WAIT);
+ }
+ wc->locked = 1;
+
+ return (CMD_RETURN_NORMAL);
+}
+
+static enum cmd_retval
+cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
+ struct wait_channel *wc)
+{
+ struct wait_item *wi;
+
+ if (wc == NULL || !wc->locked) {
+ cmdq_error(item, "channel %s not locked", name);
+ return (CMD_RETURN_ERROR);
+ }
+
+ if ((wi = TAILQ_FIRST(&wc->lockers)) != NULL) {
+ cmdq_continue(wi->item);
+ TAILQ_REMOVE(&wc->lockers, wi, entry);
+ free(wi);
+ } else {
+ wc->locked = 0;
+ cmd_wait_for_remove(wc);
+ }
+
+ return (CMD_RETURN_NORMAL);
+}
+
+void
+cmd_wait_for_flush(void)
+{
+ struct wait_channel *wc, *wc1;
+ struct wait_item *wi, *wi1;
+
+ RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
+ TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
+ cmdq_continue(wi->item);
+ TAILQ_REMOVE(&wc->waiters, wi, entry);
+ free(wi);
+ }
+ wc->woken = 1;
+ TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
+ cmdq_continue(wi->item);
+ TAILQ_REMOVE(&wc->lockers, wi, entry);
+ free(wi);
+ }
+ wc->locked = 0;
+ cmd_wait_for_remove(wc);
+ }
+}
diff --git a/cmd.c b/cmd.c
new file mode 100644
index 0000000..ed4f994
--- /dev/null
+++ b/cmd.c
@@ -0,0 +1,872 @@
+/* $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 <sys/time.h>
+
+#include <fnmatch.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+extern const struct cmd_entry cmd_attach_session_entry;
+extern const struct cmd_entry cmd_bind_key_entry;
+extern const struct cmd_entry cmd_break_pane_entry;
+extern const struct cmd_entry cmd_capture_pane_entry;
+extern const struct cmd_entry cmd_choose_buffer_entry;
+extern const struct cmd_entry cmd_choose_client_entry;
+extern const struct cmd_entry cmd_choose_tree_entry;
+extern const struct cmd_entry cmd_clear_history_entry;
+extern const struct cmd_entry cmd_clear_prompt_history_entry;
+extern const struct cmd_entry cmd_clock_mode_entry;
+extern const struct cmd_entry cmd_command_prompt_entry;
+extern const struct cmd_entry cmd_confirm_before_entry;
+extern const struct cmd_entry cmd_copy_mode_entry;
+extern const struct cmd_entry cmd_customize_mode_entry;
+extern const struct cmd_entry cmd_delete_buffer_entry;
+extern const struct cmd_entry cmd_detach_client_entry;
+extern const struct cmd_entry cmd_display_menu_entry;
+extern const struct cmd_entry cmd_display_message_entry;
+extern const struct cmd_entry cmd_display_popup_entry;
+extern const struct cmd_entry cmd_display_panes_entry;
+extern const struct cmd_entry cmd_down_pane_entry;
+extern const struct cmd_entry cmd_find_window_entry;
+extern const struct cmd_entry cmd_has_session_entry;
+extern const struct cmd_entry cmd_if_shell_entry;
+extern const struct cmd_entry cmd_join_pane_entry;
+extern const struct cmd_entry cmd_kill_pane_entry;
+extern const struct cmd_entry cmd_kill_server_entry;
+extern const struct cmd_entry cmd_kill_session_entry;
+extern const struct cmd_entry cmd_kill_window_entry;
+extern const struct cmd_entry cmd_last_pane_entry;
+extern const struct cmd_entry cmd_last_window_entry;
+extern const struct cmd_entry cmd_link_window_entry;
+extern const struct cmd_entry cmd_list_buffers_entry;
+extern const struct cmd_entry cmd_list_clients_entry;
+extern const struct cmd_entry cmd_list_commands_entry;
+extern const struct cmd_entry cmd_list_keys_entry;
+extern const struct cmd_entry cmd_list_panes_entry;
+extern const struct cmd_entry cmd_list_sessions_entry;
+extern const struct cmd_entry cmd_list_windows_entry;
+extern const struct cmd_entry cmd_load_buffer_entry;
+extern const struct cmd_entry cmd_lock_client_entry;
+extern const struct cmd_entry cmd_lock_server_entry;
+extern const struct cmd_entry cmd_lock_session_entry;
+extern const struct cmd_entry cmd_move_pane_entry;
+extern const struct cmd_entry cmd_move_window_entry;
+extern const struct cmd_entry cmd_new_session_entry;
+extern const struct cmd_entry cmd_new_window_entry;
+extern const struct cmd_entry cmd_next_layout_entry;
+extern const struct cmd_entry cmd_next_window_entry;
+extern const struct cmd_entry cmd_paste_buffer_entry;
+extern const struct cmd_entry cmd_pipe_pane_entry;
+extern const struct cmd_entry cmd_previous_layout_entry;
+extern const struct cmd_entry cmd_previous_window_entry;
+extern const struct cmd_entry cmd_refresh_client_entry;
+extern const struct cmd_entry cmd_rename_session_entry;
+extern const struct cmd_entry cmd_rename_window_entry;
+extern const struct cmd_entry cmd_resize_pane_entry;
+extern const struct cmd_entry cmd_resize_window_entry;
+extern const struct cmd_entry cmd_respawn_pane_entry;
+extern const struct cmd_entry cmd_respawn_window_entry;
+extern const struct cmd_entry cmd_rotate_window_entry;
+extern const struct cmd_entry cmd_run_shell_entry;
+extern const struct cmd_entry cmd_save_buffer_entry;
+extern const struct cmd_entry cmd_select_layout_entry;
+extern const struct cmd_entry cmd_select_pane_entry;
+extern const struct cmd_entry cmd_select_window_entry;
+extern const struct cmd_entry cmd_send_keys_entry;
+extern const struct cmd_entry cmd_send_prefix_entry;
+extern const struct cmd_entry cmd_server_access_entry;
+extern const struct cmd_entry cmd_set_buffer_entry;
+extern const struct cmd_entry cmd_set_environment_entry;
+extern const struct cmd_entry cmd_set_hook_entry;
+extern const struct cmd_entry cmd_set_option_entry;
+extern const struct cmd_entry cmd_set_window_option_entry;
+extern const struct cmd_entry cmd_show_buffer_entry;
+extern const struct cmd_entry cmd_show_environment_entry;
+extern const struct cmd_entry cmd_show_hooks_entry;
+extern const struct cmd_entry cmd_show_messages_entry;
+extern const struct cmd_entry cmd_show_options_entry;
+extern const struct cmd_entry cmd_show_prompt_history_entry;
+extern const struct cmd_entry cmd_show_window_options_entry;
+extern const struct cmd_entry cmd_source_file_entry;
+extern const struct cmd_entry cmd_split_window_entry;
+extern const struct cmd_entry cmd_start_server_entry;
+extern const struct cmd_entry cmd_suspend_client_entry;
+extern const struct cmd_entry cmd_swap_pane_entry;
+extern const struct cmd_entry cmd_swap_window_entry;
+extern const struct cmd_entry cmd_switch_client_entry;
+extern const struct cmd_entry cmd_unbind_key_entry;
+extern const struct cmd_entry cmd_unlink_window_entry;
+extern const struct cmd_entry cmd_up_pane_entry;
+extern const struct cmd_entry cmd_wait_for_entry;
+
+const struct cmd_entry *cmd_table[] = {
+ &cmd_attach_session_entry,
+ &cmd_bind_key_entry,
+ &cmd_break_pane_entry,
+ &cmd_capture_pane_entry,
+ &cmd_choose_buffer_entry,
+ &cmd_choose_client_entry,
+ &cmd_choose_tree_entry,
+ &cmd_clear_history_entry,
+ &cmd_clear_prompt_history_entry,
+ &cmd_clock_mode_entry,
+ &cmd_command_prompt_entry,
+ &cmd_confirm_before_entry,
+ &cmd_copy_mode_entry,
+ &cmd_customize_mode_entry,
+ &cmd_delete_buffer_entry,
+ &cmd_detach_client_entry,
+ &cmd_display_menu_entry,
+ &cmd_display_message_entry,
+ &cmd_display_popup_entry,
+ &cmd_display_panes_entry,
+ &cmd_find_window_entry,
+ &cmd_has_session_entry,
+ &cmd_if_shell_entry,
+ &cmd_join_pane_entry,
+ &cmd_kill_pane_entry,
+ &cmd_kill_server_entry,
+ &cmd_kill_session_entry,
+ &cmd_kill_window_entry,
+ &cmd_last_pane_entry,
+ &cmd_last_window_entry,
+ &cmd_link_window_entry,
+ &cmd_list_buffers_entry,
+ &cmd_list_clients_entry,
+ &cmd_list_commands_entry,
+ &cmd_list_keys_entry,
+ &cmd_list_panes_entry,
+ &cmd_list_sessions_entry,
+ &cmd_list_windows_entry,
+ &cmd_load_buffer_entry,
+ &cmd_lock_client_entry,
+ &cmd_lock_server_entry,
+ &cmd_lock_session_entry,
+ &cmd_move_pane_entry,
+ &cmd_move_window_entry,
+ &cmd_new_session_entry,
+ &cmd_new_window_entry,
+ &cmd_next_layout_entry,
+ &cmd_next_window_entry,
+ &cmd_paste_buffer_entry,
+ &cmd_pipe_pane_entry,
+ &cmd_previous_layout_entry,
+ &cmd_previous_window_entry,
+ &cmd_refresh_client_entry,
+ &cmd_rename_session_entry,
+ &cmd_rename_window_entry,
+ &cmd_resize_pane_entry,
+ &cmd_resize_window_entry,
+ &cmd_respawn_pane_entry,
+ &cmd_respawn_window_entry,
+ &cmd_rotate_window_entry,
+ &cmd_run_shell_entry,
+ &cmd_save_buffer_entry,
+ &cmd_select_layout_entry,
+ &cmd_select_pane_entry,
+ &cmd_select_window_entry,
+ &cmd_send_keys_entry,
+ &cmd_send_prefix_entry,
+ &cmd_server_access_entry,
+ &cmd_set_buffer_entry,
+ &cmd_set_environment_entry,
+ &cmd_set_hook_entry,
+ &cmd_set_option_entry,
+ &cmd_set_window_option_entry,
+ &cmd_show_buffer_entry,
+ &cmd_show_environment_entry,
+ &cmd_show_hooks_entry,
+ &cmd_show_messages_entry,
+ &cmd_show_options_entry,
+ &cmd_show_prompt_history_entry,
+ &cmd_show_window_options_entry,
+ &cmd_source_file_entry,
+ &cmd_split_window_entry,
+ &cmd_start_server_entry,
+ &cmd_suspend_client_entry,
+ &cmd_swap_pane_entry,
+ &cmd_swap_window_entry,
+ &cmd_switch_client_entry,
+ &cmd_unbind_key_entry,
+ &cmd_unlink_window_entry,
+ &cmd_wait_for_entry,
+ NULL
+};
+
+/* Instance of a command. */
+struct cmd {
+ const struct cmd_entry *entry;
+ struct args *args;
+ u_int group;
+
+ char *file;
+ u_int line;
+
+ TAILQ_ENTRY(cmd) qentry;
+};
+TAILQ_HEAD(cmds, cmd);
+
+/* Next group number for new command list. */
+static u_int cmd_list_next_group = 1;
+
+/* Log an argument vector. */
+void printflike(3, 4)
+cmd_log_argv(int argc, char **argv, const char *fmt, ...)
+{
+ char *prefix;
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ xvasprintf(&prefix, fmt, ap);
+ va_end(ap);
+
+ for (i = 0; i < argc; i++)
+ log_debug("%s: argv[%d]=%s", prefix, i, argv[i]);
+ free(prefix);
+}
+
+/* Prepend to an argument vector. */
+void
+cmd_prepend_argv(int *argc, char ***argv, const char *arg)
+{
+ char **new_argv;
+ int i;
+
+ new_argv = xreallocarray(NULL, (*argc) + 1, sizeof *new_argv);
+ new_argv[0] = xstrdup(arg);
+ for (i = 0; i < *argc; i++)
+ new_argv[1 + i] = (*argv)[i];
+
+ free(*argv);
+ *argv = new_argv;
+ (*argc)++;
+}
+
+/* Append to an argument vector. */
+void
+cmd_append_argv(int *argc, char ***argv, const char *arg)
+{
+ *argv = xreallocarray(*argv, (*argc) + 1, sizeof **argv);
+ (*argv)[(*argc)++] = xstrdup(arg);
+}
+
+/* Pack an argument vector up into a buffer. */
+int
+cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
+{
+ size_t arglen;
+ int i;
+
+ if (argc == 0)
+ return (0);
+ cmd_log_argv(argc, argv, "%s", __func__);
+
+ *buf = '\0';
+ for (i = 0; i < argc; i++) {
+ if (strlcpy(buf, argv[i], len) >= len)
+ return (-1);
+ arglen = strlen(argv[i]) + 1;
+ buf += arglen;
+ len -= arglen;
+ }
+
+ return (0);
+}
+
+/* Unpack an argument vector from a packed buffer. */
+int
+cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
+{
+ int i;
+ size_t arglen;
+
+ if (argc == 0)
+ return (0);
+ *argv = xcalloc(argc, sizeof **argv);
+
+ buf[len - 1] = '\0';
+ for (i = 0; i < argc; i++) {
+ if (len == 0) {
+ cmd_free_argv(argc, *argv);
+ return (-1);
+ }
+
+ arglen = strlen(buf) + 1;
+ (*argv)[i] = xstrdup(buf);
+
+ buf += arglen;
+ len -= arglen;
+ }
+ cmd_log_argv(argc, *argv, "%s", __func__);
+
+ return (0);
+}
+
+/* Copy an argument vector, ensuring it is terminated by NULL. */
+char **
+cmd_copy_argv(int argc, char **argv)
+{
+ char **new_argv;
+ int i;
+
+ if (argc == 0)
+ return (NULL);
+ new_argv = xcalloc(argc + 1, sizeof *new_argv);
+ for (i = 0; i < argc; i++) {
+ if (argv[i] != NULL)
+ new_argv[i] = xstrdup(argv[i]);
+ }
+ return (new_argv);
+}
+
+/* Free an argument vector. */
+void
+cmd_free_argv(int argc, char **argv)
+{
+ int i;
+
+ if (argc == 0)
+ return;
+ for (i = 0; i < argc; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+/* Convert argument vector to a string. */
+char *
+cmd_stringify_argv(int argc, char **argv)
+{
+ char *buf = NULL, *s;
+ size_t len = 0;
+ int i;
+
+ if (argc == 0)
+ return (xstrdup(""));
+
+ for (i = 0; i < argc; i++) {
+ s = args_escape(argv[i]);
+ log_debug("%s: %u %s = %s", __func__, i, argv[i], s);
+
+ len += strlen(s) + 1;
+ buf = xrealloc(buf, len);
+
+ if (i == 0)
+ *buf = '\0';
+ else
+ strlcat(buf, " ", len);
+ strlcat(buf, s, len);
+
+ free(s);
+ }
+ return (buf);
+}
+
+/* Get entry for command. */
+const struct cmd_entry *
+cmd_get_entry(struct cmd *cmd)
+{
+ return (cmd->entry);
+}
+
+/* Get arguments for command. */
+struct args *
+cmd_get_args(struct cmd *cmd)
+{
+ return (cmd->args);
+}
+
+/* Get group for command. */
+u_int
+cmd_get_group(struct cmd *cmd)
+{
+ return (cmd->group);
+}
+
+/* Get file and line for command. */
+void
+cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
+{
+ if (file != NULL)
+ *file = cmd->file;
+ if (line != NULL)
+ *line = cmd->line;
+}
+
+/* Look for an alias for a command. */
+char *
+cmd_get_alias(const char *name)
+{
+ struct options_entry *o;
+ struct options_array_item *a;
+ union options_value *ov;
+ size_t wanted, n;
+ const char *equals;
+
+ o = options_get_only(global_options, "command-alias");
+ if (o == NULL)
+ return (NULL);
+ wanted = strlen(name);
+
+ a = options_array_first(o);
+ while (a != NULL) {
+ ov = options_array_item_value(a);
+
+ equals = strchr(ov->string, '=');
+ if (equals != NULL) {
+ n = equals - ov->string;
+ if (n == wanted && strncmp(name, ov->string, n) == 0)
+ return (xstrdup(equals + 1));
+ }
+
+ a = options_array_next(a);
+ }
+ return (NULL);
+}
+
+/* Look up a command entry by name. */
+static const struct cmd_entry *
+cmd_find(const char *name, char **cause)
+{
+ const struct cmd_entry **loop, *entry, *found = NULL;
+ int ambiguous;
+ char s[8192];
+
+ ambiguous = 0;
+ for (loop = cmd_table; *loop != NULL; loop++) {
+ entry = *loop;
+ if (entry->alias != NULL && strcmp(entry->alias, name) == 0) {
+ ambiguous = 0;
+ found = entry;
+ break;
+ }
+
+ if (strncmp(entry->name, name, strlen(name)) != 0)
+ continue;
+ if (found != NULL)
+ ambiguous = 1;
+ found = entry;
+
+ if (strcmp(entry->name, name) == 0)
+ break;
+ }
+ if (ambiguous)
+ goto ambiguous;
+ if (found == NULL) {
+ xasprintf(cause, "unknown command: %s", name);
+ return (NULL);
+ }
+ return (found);
+
+ambiguous:
+ *s = '\0';
+ for (loop = cmd_table; *loop != NULL; loop++) {
+ entry = *loop;
+ if (strncmp(entry->name, name, strlen(name)) != 0)
+ continue;
+ if (strlcat(s, entry->name, sizeof s) >= sizeof s)
+ break;
+ if (strlcat(s, ", ", sizeof s) >= sizeof s)
+ break;
+ }
+ s[strlen(s) - 2] = '\0';
+ xasprintf(cause, "ambiguous command: %s, could be: %s", name, s);
+ return (NULL);
+}
+
+/* Parse a single command from an argument vector. */
+struct cmd *
+cmd_parse(struct args_value *values, u_int count, const char *file, u_int line,
+ char **cause)
+{
+ const struct cmd_entry *entry;
+ struct cmd *cmd;
+ struct args *args;
+ char *error = NULL;
+
+ if (count == 0 || values[0].type != ARGS_STRING) {
+ xasprintf(cause, "no command");
+ return (NULL);
+ }
+ entry = cmd_find(values[0].string, cause);
+ if (entry == NULL)
+ return (NULL);
+
+ args = args_parse(&entry->args, values, count, &error);
+ if (args == NULL && error == NULL) {
+ xasprintf(cause, "usage: %s %s", entry->name, entry->usage);
+ return (NULL);
+ }
+ if (args == NULL) {
+ xasprintf(cause, "command %s: %s", entry->name, error);
+ free(error);
+ return (NULL);
+ }
+
+ cmd = xcalloc(1, sizeof *cmd);
+ cmd->entry = entry;
+ cmd->args = args;
+
+ if (file != NULL)
+ cmd->file = xstrdup(file);
+ cmd->line = line;
+
+ return (cmd);
+}
+
+/* Free a command. */
+void
+cmd_free(struct cmd *cmd)
+{
+ free(cmd->file);
+
+ args_free(cmd->args);
+ free(cmd);
+}
+
+/* Copy a command. */
+struct cmd *
+cmd_copy(struct cmd *cmd, int argc, char **argv)
+{
+ struct cmd *new_cmd;
+
+ new_cmd = xcalloc(1, sizeof *new_cmd);
+ new_cmd->entry = cmd->entry;
+ new_cmd->args = args_copy(cmd->args, argc, argv);
+
+ if (cmd->file != NULL)
+ new_cmd->file = xstrdup(cmd->file);
+ new_cmd->line = cmd->line;
+
+ return (new_cmd);
+}
+
+/* Get a command as a string. */
+char *
+cmd_print(struct cmd *cmd)
+{
+ char *out, *s;
+
+ s = args_print(cmd->args);
+ if (*s != '\0')
+ xasprintf(&out, "%s %s", cmd->entry->name, s);
+ else
+ out = xstrdup(cmd->entry->name);
+ free(s);
+
+ return (out);
+}
+
+/* Create a new command list. */
+struct cmd_list *
+cmd_list_new(void)
+{
+ struct cmd_list *cmdlist;
+
+ cmdlist = xcalloc(1, sizeof *cmdlist);
+ cmdlist->references = 1;
+ cmdlist->group = cmd_list_next_group++;
+ cmdlist->list = xcalloc(1, sizeof *cmdlist->list);
+ TAILQ_INIT(cmdlist->list);
+ return (cmdlist);
+}
+
+/* Append a command to a command list. */
+void
+cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
+{
+ cmd->group = cmdlist->group;
+ TAILQ_INSERT_TAIL(cmdlist->list, cmd, qentry);
+}
+
+/* Append all commands from one list to another. */
+void
+cmd_list_append_all(struct cmd_list *cmdlist, struct cmd_list *from)
+{
+ struct cmd *cmd;
+
+ TAILQ_FOREACH(cmd, from->list, qentry)
+ cmd->group = cmdlist->group;
+ TAILQ_CONCAT(cmdlist->list, from->list, qentry);
+}
+
+/* Move all commands from one command list to another. */
+void
+cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
+{
+ TAILQ_CONCAT(cmdlist->list, from->list, qentry);
+ cmdlist->group = cmd_list_next_group++;
+}
+
+/* Free a command list. */
+void
+cmd_list_free(struct cmd_list *cmdlist)
+{
+ struct cmd *cmd, *cmd1;
+
+ if (--cmdlist->references != 0)
+ return;
+
+ TAILQ_FOREACH_SAFE(cmd, cmdlist->list, qentry, cmd1) {
+ TAILQ_REMOVE(cmdlist->list, cmd, qentry);
+ cmd_free(cmd);
+ }
+ free(cmdlist->list);
+ free(cmdlist);
+}
+
+/* Copy a command list, expanding %s in arguments. */
+struct cmd_list *
+cmd_list_copy(struct cmd_list *cmdlist, int argc, char **argv)
+{
+ struct cmd *cmd;
+ struct cmd_list *new_cmdlist;
+ struct cmd *new_cmd;
+ u_int group = cmdlist->group;
+ char *s;
+
+ s = cmd_list_print(cmdlist, 0);
+ log_debug("%s: %s", __func__, s);
+ free(s);
+
+ new_cmdlist = cmd_list_new();
+ TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
+ if (cmd->group != group) {
+ new_cmdlist->group = cmd_list_next_group++;
+ group = cmd->group;
+ }
+ new_cmd = cmd_copy(cmd, argc, argv);
+ cmd_list_append(new_cmdlist, new_cmd);
+ }
+
+ s = cmd_list_print(new_cmdlist, 0);
+ log_debug("%s: %s", __func__, s);
+ free(s);
+
+ return (new_cmdlist);
+}
+
+/* Get a command list as a string. */
+char *
+cmd_list_print(struct cmd_list *cmdlist, int escaped)
+{
+ struct cmd *cmd, *next;
+ char *buf, *this;
+ size_t len;
+
+ len = 1;
+ buf = xcalloc(1, len);
+
+ TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
+ this = cmd_print(cmd);
+
+ len += strlen(this) + 6;
+ buf = xrealloc(buf, len);
+
+ strlcat(buf, this, len);
+
+ next = TAILQ_NEXT(cmd, qentry);
+ if (next != NULL) {
+ if (cmd->group != next->group) {
+ if (escaped)
+ strlcat(buf, " \\;\\; ", len);
+ else
+ strlcat(buf, " ;; ", len);
+ } else {
+ if (escaped)
+ strlcat(buf, " \\; ", len);
+ else
+ strlcat(buf, " ; ", len);
+ }
+ }
+
+ free(this);
+ }
+
+ return (buf);
+}
+
+/* Get first command in list. */
+struct cmd *
+cmd_list_first(struct cmd_list *cmdlist)
+{
+ return (TAILQ_FIRST(cmdlist->list));
+}
+
+/* Get next command in list. */
+struct cmd *
+cmd_list_next(struct cmd *cmd)
+{
+ return (TAILQ_NEXT(cmd, qentry));
+}
+
+/* Do all of the commands in this command list have this flag? */
+int
+cmd_list_all_have(struct cmd_list *cmdlist, int flag)
+{
+ struct cmd *cmd;
+
+ TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
+ if (~cmd->entry->flags & flag)
+ return (0);
+ }
+ return (1);
+}
+
+/* Do any of the commands in this command list have this flag? */
+int
+cmd_list_any_have(struct cmd_list *cmdlist, int flag)
+{
+ struct cmd *cmd;
+
+ TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
+ if (cmd->entry->flags & flag)
+ return (1);
+ }
+ return (0);
+}
+
+/* Adjust current mouse position for a pane. */
+int
+cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
+ u_int *yp, int last)
+{
+ u_int x, y;
+
+ if (last) {
+ x = m->lx + m->ox;
+ y = m->ly + m->oy;
+ } else {
+ x = m->x + m->ox;
+ y = m->y + m->oy;
+ }
+ log_debug("%s: x=%u, y=%u%s", __func__, x, y, last ? " (last)" : "");
+
+ if (m->statusat == 0 && y >= m->statuslines)
+ y -= m->statuslines;
+
+ if (x < wp->xoff || x >= wp->xoff + wp->sx)
+ return (-1);
+ if (y < wp->yoff || y >= wp->yoff + wp->sy)
+ return (-1);
+
+ if (xp != NULL)
+ *xp = x - wp->xoff;
+ if (yp != NULL)
+ *yp = y - wp->yoff;
+ return (0);
+}
+
+/* Get current mouse window if any. */
+struct winlink *
+cmd_mouse_window(struct mouse_event *m, struct session **sp)
+{
+ struct session *s;
+ struct window *w;
+ struct winlink *wl;
+
+ if (!m->valid)
+ return (NULL);
+ if (m->s == -1 || (s = session_find_by_id(m->s)) == NULL)
+ return (NULL);
+ if (m->w == -1)
+ wl = s->curw;
+ else {
+ if ((w = window_find_by_id(m->w)) == NULL)
+ return (NULL);
+ wl = winlink_find_by_window(&s->windows, w);
+ }
+ if (sp != NULL)
+ *sp = s;
+ return (wl);
+}
+
+/* Get current mouse pane if any. */
+struct window_pane *
+cmd_mouse_pane(struct mouse_event *m, struct session **sp,
+ struct winlink **wlp)
+{
+ struct winlink *wl;
+ struct window_pane *wp;
+
+ 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 (wlp != NULL)
+ *wlp = wl;
+ return (wp);
+}
+
+/* Replace the first %% or %idx in template by s. */
+char *
+cmd_template_replace(const char *template, const char *s, int idx)
+{
+ char ch, *buf;
+ const char *ptr, *cp, quote[] = "\"\\$;~";
+ int replaced, quoted;
+ size_t len;
+
+ if (strchr(template, '%') == NULL)
+ return (xstrdup(template));
+
+ buf = xmalloc(1);
+ *buf = '\0';
+ len = 0;
+ replaced = 0;
+
+ ptr = template;
+ while (*ptr != '\0') {
+ switch (ch = *ptr++) {
+ case '%':
+ if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) {
+ if (*ptr != '%' || replaced)
+ break;
+ replaced = 1;
+ }
+ ptr++;
+
+ quoted = (*ptr == '%');
+ if (quoted)
+ ptr++;
+
+ buf = xrealloc(buf, len + (strlen(s) * 3) + 1);
+ for (cp = s; *cp != '\0'; cp++) {
+ if (quoted && strchr(quote, *cp) != NULL)
+ buf[len++] = '\\';
+ buf[len++] = *cp;
+ }
+ buf[len] = '\0';
+ continue;
+ }
+ buf = xrealloc(buf, len + 2);
+ buf[len++] = ch;
+ buf[len] = '\0';
+ }
+
+ return (buf);
+}
diff --git a/colour.c b/colour.c
new file mode 100644
index 0000000..a282d18
--- /dev/null
+++ b/colour.c
@@ -0,0 +1,1073 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2016 Avi Halachmi <avihpit@yahoo.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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "tmux.h"
+
+static int
+colour_dist_sq(int R, int G, int B, int r, int g, int b)
+{
+ return ((R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b));
+}
+
+static int
+colour_to_6cube(int v)
+{
+ if (v < 48)
+ return (0);
+ if (v < 114)
+ return (1);
+ return ((v - 35) / 40);
+}
+
+/*
+ * Convert an RGB triplet to the xterm(1) 256 colour palette.
+ *
+ * xterm provides a 6x6x6 colour cube (16 - 231) and 24 greys (232 - 255). We
+ * map our RGB colour to the closest in the cube, also work out the closest
+ * grey, and use the nearest of the two.
+ *
+ * Note that the xterm has much lower resolution for darker colours (they are
+ * not evenly spread out), so our 6 levels are not evenly spread: 0x0, 0x5f
+ * (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are more
+ * evenly spread (8, 18, 28 ... 238).
+ */
+int
+colour_find_rgb(u_char r, u_char g, u_char b)
+{
+ static const int q2c[6] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff };
+ int qr, qg, qb, cr, cg, cb, d, idx;
+ int grey_avg, grey_idx, grey;
+
+ /* Map RGB to 6x6x6 cube. */
+ qr = colour_to_6cube(r); cr = q2c[qr];
+ qg = colour_to_6cube(g); cg = q2c[qg];
+ qb = colour_to_6cube(b); cb = q2c[qb];
+
+ /* If we have hit the colour exactly, return early. */
+ if (cr == r && cg == g && cb == b)
+ return ((16 + (36 * qr) + (6 * qg) + qb) | COLOUR_FLAG_256);
+
+ /* Work out the closest grey (average of RGB). */
+ grey_avg = (r + g + b) / 3;
+ if (grey_avg > 238)
+ grey_idx = 23;
+ else
+ grey_idx = (grey_avg - 3) / 10;
+ grey = 8 + (10 * grey_idx);
+
+ /* Is grey or 6x6x6 colour closest? */
+ d = colour_dist_sq(cr, cg, cb, r, g, b);
+ if (colour_dist_sq(grey, grey, grey, r, g, b) < d)
+ idx = 232 + grey_idx;
+ else
+ idx = 16 + (36 * qr) + (6 * qg) + qb;
+ return (idx | COLOUR_FLAG_256);
+}
+
+/* Join RGB into a colour. */
+int
+colour_join_rgb(u_char r, u_char g, u_char b)
+{
+ return ((((int)((r) & 0xff)) << 16) |
+ (((int)((g) & 0xff)) << 8) |
+ (((int)((b) & 0xff))) | COLOUR_FLAG_RGB);
+}
+
+/* Split colour into RGB. */
+void
+colour_split_rgb(int c, u_char *r, u_char *g, u_char *b)
+{
+ *r = (c >> 16) & 0xff;
+ *g = (c >> 8) & 0xff;
+ *b = c & 0xff;
+}
+
+/* Force colour to RGB if not already. */
+int
+colour_force_rgb(int c)
+{
+ if (c & COLOUR_FLAG_RGB)
+ return (c);
+ if (c & COLOUR_FLAG_256)
+ return (colour_256toRGB(c));
+ if (c >= 0 && c <= 7)
+ return (colour_256toRGB(c));
+ if (c >= 90 && c <= 97)
+ return (colour_256toRGB(8 + c - 90));
+ return (-1);
+}
+
+/* Convert colour to a string. */
+const char *
+colour_tostring(int c)
+{
+ static char s[32];
+ u_char r, g, b;
+
+ if (c == -1)
+ return ("none");
+
+ if (c & COLOUR_FLAG_RGB) {
+ colour_split_rgb(c, &r, &g, &b);
+ xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b);
+ return (s);
+ }
+
+ if (c & COLOUR_FLAG_256) {
+ xsnprintf(s, sizeof s, "colour%u", c & 0xff);
+ return (s);
+ }
+
+ switch (c) {
+ case 0:
+ return ("black");
+ case 1:
+ return ("red");
+ case 2:
+ return ("green");
+ case 3:
+ return ("yellow");
+ case 4:
+ return ("blue");
+ case 5:
+ return ("magenta");
+ case 6:
+ return ("cyan");
+ case 7:
+ return ("white");
+ case 8:
+ return ("default");
+ case 9:
+ return ("terminal");
+ case 90:
+ return ("brightblack");
+ case 91:
+ return ("brightred");
+ case 92:
+ return ("brightgreen");
+ case 93:
+ return ("brightyellow");
+ case 94:
+ return ("brightblue");
+ case 95:
+ return ("brightmagenta");
+ case 96:
+ return ("brightcyan");
+ case 97:
+ return ("brightwhite");
+ }
+ return ("invalid");
+}
+
+/* Convert colour from string. */
+int
+colour_fromstring(const char *s)
+{
+ const char *errstr;
+ const char *cp;
+ int n;
+ u_char r, g, b;
+
+ if (*s == '#' && strlen(s) == 7) {
+ for (cp = s + 1; isxdigit((u_char) *cp); cp++)
+ ;
+ if (*cp != '\0')
+ return (-1);
+ n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
+ if (n != 3)
+ return (-1);
+ return (colour_join_rgb(r, g, b));
+ }
+
+ if (strncasecmp(s, "colour", (sizeof "colour") - 1) == 0) {
+ n = strtonum(s + (sizeof "colour") - 1, 0, 255, &errstr);
+ if (errstr != NULL)
+ return (-1);
+ return (n | COLOUR_FLAG_256);
+ }
+ if (strncasecmp(s, "color", (sizeof "color") - 1) == 0) {
+ n = strtonum(s + (sizeof "color") - 1, 0, 255, &errstr);
+ if (errstr != NULL)
+ return (-1);
+ return (n | COLOUR_FLAG_256);
+ }
+
+ if (strcasecmp(s, "default") == 0)
+ return (8);
+ if (strcasecmp(s, "terminal") == 0)
+ return (9);
+
+ if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
+ return (0);
+ if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
+ return (1);
+ if (strcasecmp(s, "green") == 0 || strcmp(s, "2") == 0)
+ return (2);
+ if (strcasecmp(s, "yellow") == 0 || strcmp(s, "3") == 0)
+ return (3);
+ if (strcasecmp(s, "blue") == 0 || strcmp(s, "4") == 0)
+ return (4);
+ if (strcasecmp(s, "magenta") == 0 || strcmp(s, "5") == 0)
+ return (5);
+ if (strcasecmp(s, "cyan") == 0 || strcmp(s, "6") == 0)
+ return (6);
+ if (strcasecmp(s, "white") == 0 || strcmp(s, "7") == 0)
+ return (7);
+ if (strcasecmp(s, "brightblack") == 0 || strcmp(s, "90") == 0)
+ return (90);
+ if (strcasecmp(s, "brightred") == 0 || strcmp(s, "91") == 0)
+ return (91);
+ if (strcasecmp(s, "brightgreen") == 0 || strcmp(s, "92") == 0)
+ return (92);
+ if (strcasecmp(s, "brightyellow") == 0 || strcmp(s, "93") == 0)
+ return (93);
+ if (strcasecmp(s, "brightblue") == 0 || strcmp(s, "94") == 0)
+ return (94);
+ if (strcasecmp(s, "brightmagenta") == 0 || strcmp(s, "95") == 0)
+ return (95);
+ if (strcasecmp(s, "brightcyan") == 0 || strcmp(s, "96") == 0)
+ return (96);
+ if (strcasecmp(s, "brightwhite") == 0 || strcmp(s, "97") == 0)
+ return (97);
+ return (colour_byname(s));
+}
+
+/* Convert 256 colour to RGB colour. */
+int
+colour_256toRGB(int c)
+{
+ static const int table[256] = {
+ 0x000000, 0x800000, 0x008000, 0x808000,
+ 0x000080, 0x800080, 0x008080, 0xc0c0c0,
+ 0x808080, 0xff0000, 0x00ff00, 0xffff00,
+ 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
+ 0x000000, 0x00005f, 0x000087, 0x0000af,
+ 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
+ 0x005f87, 0x005faf, 0x005fd7, 0x005fff,
+ 0x008700, 0x00875f, 0x008787, 0x0087af,
+ 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f,
+ 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
+ 0x00d700, 0x00d75f, 0x00d787, 0x00d7af,
+ 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
+ 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff,
+ 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
+ 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f,
+ 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
+ 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af,
+ 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
+ 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff,
+ 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
+ 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f,
+ 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
+ 0x870000, 0x87005f, 0x870087, 0x8700af,
+ 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
+ 0x875f87, 0x875faf, 0x875fd7, 0x875fff,
+ 0x878700, 0x87875f, 0x878787, 0x8787af,
+ 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f,
+ 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
+ 0x87d700, 0x87d75f, 0x87d787, 0x87d7af,
+ 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
+ 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff,
+ 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
+ 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f,
+ 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
+ 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af,
+ 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
+ 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff,
+ 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
+ 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f,
+ 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
+ 0xd70000, 0xd7005f, 0xd70087, 0xd700af,
+ 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
+ 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff,
+ 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
+ 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f,
+ 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
+ 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af,
+ 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
+ 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff,
+ 0xff0000, 0xff005f, 0xff0087, 0xff00af,
+ 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f,
+ 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
+ 0xff8700, 0xff875f, 0xff8787, 0xff87af,
+ 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
+ 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff,
+ 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
+ 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f,
+ 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
+ 0x080808, 0x121212, 0x1c1c1c, 0x262626,
+ 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
+ 0x585858, 0x626262, 0x6c6c6c, 0x767676,
+ 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
+ 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6,
+ 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
+ };
+
+ return (table[c & 0xff] | COLOUR_FLAG_RGB);
+}
+
+/* Convert 256 colour to 16 colour. */
+int
+colour_256to16(int c)
+{
+ static const char table[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
+ 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
+ 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
+ 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
+ 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
+ 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
+ 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
+ 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
+ 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
+ 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
+ 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
+ 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
+ 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
+ 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
+ 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
+ };
+
+ return (table[c & 0xff]);
+}
+
+/* Get colour by X11 colour name. */
+int
+colour_byname(const char *name)
+{
+ static const struct {
+ const char *name;
+ int c;
+ } colours[] = {
+ { "AliceBlue", 0xf0f8ff },
+ { "AntiqueWhite", 0xfaebd7 },
+ { "AntiqueWhite1", 0xffefdb },
+ { "AntiqueWhite2", 0xeedfcc },
+ { "AntiqueWhite3", 0xcdc0b0 },
+ { "AntiqueWhite4", 0x8b8378 },
+ { "BlanchedAlmond", 0xffebcd },
+ { "BlueViolet", 0x8a2be2 },
+ { "CadetBlue", 0x5f9ea0 },
+ { "CadetBlue1", 0x98f5ff },
+ { "CadetBlue2", 0x8ee5ee },
+ { "CadetBlue3", 0x7ac5cd },
+ { "CadetBlue4", 0x53868b },
+ { "CornflowerBlue", 0x6495ed },
+ { "DarkBlue", 0x00008b },
+ { "DarkCyan", 0x008b8b },
+ { "DarkGoldenrod", 0xb8860b },
+ { "DarkGoldenrod1", 0xffb90f },
+ { "DarkGoldenrod2", 0xeead0e },
+ { "DarkGoldenrod3", 0xcd950c },
+ { "DarkGoldenrod4", 0x8b6508 },
+ { "DarkGray", 0xa9a9a9 },
+ { "DarkGreen", 0x006400 },
+ { "DarkGrey", 0xa9a9a9 },
+ { "DarkKhaki", 0xbdb76b },
+ { "DarkMagenta", 0x8b008b },
+ { "DarkOliveGreen", 0x556b2f },
+ { "DarkOliveGreen1", 0xcaff70 },
+ { "DarkOliveGreen2", 0xbcee68 },
+ { "DarkOliveGreen3", 0xa2cd5a },
+ { "DarkOliveGreen4", 0x6e8b3d },
+ { "DarkOrange", 0xff8c00 },
+ { "DarkOrange1", 0xff7f00 },
+ { "DarkOrange2", 0xee7600 },
+ { "DarkOrange3", 0xcd6600 },
+ { "DarkOrange4", 0x8b4500 },
+ { "DarkOrchid", 0x9932cc },
+ { "DarkOrchid1", 0xbf3eff },
+ { "DarkOrchid2", 0xb23aee },
+ { "DarkOrchid3", 0x9a32cd },
+ { "DarkOrchid4", 0x68228b },
+ { "DarkRed", 0x8b0000 },
+ { "DarkSalmon", 0xe9967a },
+ { "DarkSeaGreen", 0x8fbc8f },
+ { "DarkSeaGreen1", 0xc1ffc1 },
+ { "DarkSeaGreen2", 0xb4eeb4 },
+ { "DarkSeaGreen3", 0x9bcd9b },
+ { "DarkSeaGreen4", 0x698b69 },
+ { "DarkSlateBlue", 0x483d8b },
+ { "DarkSlateGray", 0x2f4f4f },
+ { "DarkSlateGray1", 0x97ffff },
+ { "DarkSlateGray2", 0x8deeee },
+ { "DarkSlateGray3", 0x79cdcd },
+ { "DarkSlateGray4", 0x528b8b },
+ { "DarkSlateGrey", 0x2f4f4f },
+ { "DarkTurquoise", 0x00ced1 },
+ { "DarkViolet", 0x9400d3 },
+ { "DeepPink", 0xff1493 },
+ { "DeepPink1", 0xff1493 },
+ { "DeepPink2", 0xee1289 },
+ { "DeepPink3", 0xcd1076 },
+ { "DeepPink4", 0x8b0a50 },
+ { "DeepSkyBlue", 0x00bfff },
+ { "DeepSkyBlue1", 0x00bfff },
+ { "DeepSkyBlue2", 0x00b2ee },
+ { "DeepSkyBlue3", 0x009acd },
+ { "DeepSkyBlue4", 0x00688b },
+ { "DimGray", 0x696969 },
+ { "DimGrey", 0x696969 },
+ { "DodgerBlue", 0x1e90ff },
+ { "DodgerBlue1", 0x1e90ff },
+ { "DodgerBlue2", 0x1c86ee },
+ { "DodgerBlue3", 0x1874cd },
+ { "DodgerBlue4", 0x104e8b },
+ { "FloralWhite", 0xfffaf0 },
+ { "ForestGreen", 0x228b22 },
+ { "GhostWhite", 0xf8f8ff },
+ { "GreenYellow", 0xadff2f },
+ { "HotPink", 0xff69b4 },
+ { "HotPink1", 0xff6eb4 },
+ { "HotPink2", 0xee6aa7 },
+ { "HotPink3", 0xcd6090 },
+ { "HotPink4", 0x8b3a62 },
+ { "IndianRed", 0xcd5c5c },
+ { "IndianRed1", 0xff6a6a },
+ { "IndianRed2", 0xee6363 },
+ { "IndianRed3", 0xcd5555 },
+ { "IndianRed4", 0x8b3a3a },
+ { "LavenderBlush", 0xfff0f5 },
+ { "LavenderBlush1", 0xfff0f5 },
+ { "LavenderBlush2", 0xeee0e5 },
+ { "LavenderBlush3", 0xcdc1c5 },
+ { "LavenderBlush4", 0x8b8386 },
+ { "LawnGreen", 0x7cfc00 },
+ { "LemonChiffon", 0xfffacd },
+ { "LemonChiffon1", 0xfffacd },
+ { "LemonChiffon2", 0xeee9bf },
+ { "LemonChiffon3", 0xcdc9a5 },
+ { "LemonChiffon4", 0x8b8970 },
+ { "LightBlue", 0xadd8e6 },
+ { "LightBlue1", 0xbfefff },
+ { "LightBlue2", 0xb2dfee },
+ { "LightBlue3", 0x9ac0cd },
+ { "LightBlue4", 0x68838b },
+ { "LightCoral", 0xf08080 },
+ { "LightCyan", 0xe0ffff },
+ { "LightCyan1", 0xe0ffff },
+ { "LightCyan2", 0xd1eeee },
+ { "LightCyan3", 0xb4cdcd },
+ { "LightCyan4", 0x7a8b8b },
+ { "LightGoldenrod", 0xeedd82 },
+ { "LightGoldenrod1", 0xffec8b },
+ { "LightGoldenrod2", 0xeedc82 },
+ { "LightGoldenrod3", 0xcdbe70 },
+ { "LightGoldenrod4", 0x8b814c },
+ { "LightGoldenrodYellow", 0xfafad2 },
+ { "LightGray", 0xd3d3d3 },
+ { "LightGreen", 0x90ee90 },
+ { "LightGrey", 0xd3d3d3 },
+ { "LightPink", 0xffb6c1 },
+ { "LightPink1", 0xffaeb9 },
+ { "LightPink2", 0xeea2ad },
+ { "LightPink3", 0xcd8c95 },
+ { "LightPink4", 0x8b5f65 },
+ { "LightSalmon", 0xffa07a },
+ { "LightSalmon1", 0xffa07a },
+ { "LightSalmon2", 0xee9572 },
+ { "LightSalmon3", 0xcd8162 },
+ { "LightSalmon4", 0x8b5742 },
+ { "LightSeaGreen", 0x20b2aa },
+ { "LightSkyBlue", 0x87cefa },
+ { "LightSkyBlue1", 0xb0e2ff },
+ { "LightSkyBlue2", 0xa4d3ee },
+ { "LightSkyBlue3", 0x8db6cd },
+ { "LightSkyBlue4", 0x607b8b },
+ { "LightSlateBlue", 0x8470ff },
+ { "LightSlateGray", 0x778899 },
+ { "LightSlateGrey", 0x778899 },
+ { "LightSteelBlue", 0xb0c4de },
+ { "LightSteelBlue1", 0xcae1ff },
+ { "LightSteelBlue2", 0xbcd2ee },
+ { "LightSteelBlue3", 0xa2b5cd },
+ { "LightSteelBlue4", 0x6e7b8b },
+ { "LightYellow", 0xffffe0 },
+ { "LightYellow1", 0xffffe0 },
+ { "LightYellow2", 0xeeeed1 },
+ { "LightYellow3", 0xcdcdb4 },
+ { "LightYellow4", 0x8b8b7a },
+ { "LimeGreen", 0x32cd32 },
+ { "MediumAquamarine", 0x66cdaa },
+ { "MediumBlue", 0x0000cd },
+ { "MediumOrchid", 0xba55d3 },
+ { "MediumOrchid1", 0xe066ff },
+ { "MediumOrchid2", 0xd15fee },
+ { "MediumOrchid3", 0xb452cd },
+ { "MediumOrchid4", 0x7a378b },
+ { "MediumPurple", 0x9370db },
+ { "MediumPurple1", 0xab82ff },
+ { "MediumPurple2", 0x9f79ee },
+ { "MediumPurple3", 0x8968cd },
+ { "MediumPurple4", 0x5d478b },
+ { "MediumSeaGreen", 0x3cb371 },
+ { "MediumSlateBlue", 0x7b68ee },
+ { "MediumSpringGreen", 0x00fa9a },
+ { "MediumTurquoise", 0x48d1cc },
+ { "MediumVioletRed", 0xc71585 },
+ { "MidnightBlue", 0x191970 },
+ { "MintCream", 0xf5fffa },
+ { "MistyRose", 0xffe4e1 },
+ { "MistyRose1", 0xffe4e1 },
+ { "MistyRose2", 0xeed5d2 },
+ { "MistyRose3", 0xcdb7b5 },
+ { "MistyRose4", 0x8b7d7b },
+ { "NavajoWhite", 0xffdead },
+ { "NavajoWhite1", 0xffdead },
+ { "NavajoWhite2", 0xeecfa1 },
+ { "NavajoWhite3", 0xcdb38b },
+ { "NavajoWhite4", 0x8b795e },
+ { "NavyBlue", 0x000080 },
+ { "OldLace", 0xfdf5e6 },
+ { "OliveDrab", 0x6b8e23 },
+ { "OliveDrab1", 0xc0ff3e },
+ { "OliveDrab2", 0xb3ee3a },
+ { "OliveDrab3", 0x9acd32 },
+ { "OliveDrab4", 0x698b22 },
+ { "OrangeRed", 0xff4500 },
+ { "OrangeRed1", 0xff4500 },
+ { "OrangeRed2", 0xee4000 },
+ { "OrangeRed3", 0xcd3700 },
+ { "OrangeRed4", 0x8b2500 },
+ { "PaleGoldenrod", 0xeee8aa },
+ { "PaleGreen", 0x98fb98 },
+ { "PaleGreen1", 0x9aff9a },
+ { "PaleGreen2", 0x90ee90 },
+ { "PaleGreen3", 0x7ccd7c },
+ { "PaleGreen4", 0x548b54 },
+ { "PaleTurquoise", 0xafeeee },
+ { "PaleTurquoise1", 0xbbffff },
+ { "PaleTurquoise2", 0xaeeeee },
+ { "PaleTurquoise3", 0x96cdcd },
+ { "PaleTurquoise4", 0x668b8b },
+ { "PaleVioletRed", 0xdb7093 },
+ { "PaleVioletRed1", 0xff82ab },
+ { "PaleVioletRed2", 0xee799f },
+ { "PaleVioletRed3", 0xcd6889 },
+ { "PaleVioletRed4", 0x8b475d },
+ { "PapayaWhip", 0xffefd5 },
+ { "PeachPuff", 0xffdab9 },
+ { "PeachPuff1", 0xffdab9 },
+ { "PeachPuff2", 0xeecbad },
+ { "PeachPuff3", 0xcdaf95 },
+ { "PeachPuff4", 0x8b7765 },
+ { "PowderBlue", 0xb0e0e6 },
+ { "RebeccaPurple", 0x663399 },
+ { "RosyBrown", 0xbc8f8f },
+ { "RosyBrown1", 0xffc1c1 },
+ { "RosyBrown2", 0xeeb4b4 },
+ { "RosyBrown3", 0xcd9b9b },
+ { "RosyBrown4", 0x8b6969 },
+ { "RoyalBlue", 0x4169e1 },
+ { "RoyalBlue1", 0x4876ff },
+ { "RoyalBlue2", 0x436eee },
+ { "RoyalBlue3", 0x3a5fcd },
+ { "RoyalBlue4", 0x27408b },
+ { "SaddleBrown", 0x8b4513 },
+ { "SandyBrown", 0xf4a460 },
+ { "SeaGreen", 0x2e8b57 },
+ { "SeaGreen1", 0x54ff9f },
+ { "SeaGreen2", 0x4eee94 },
+ { "SeaGreen3", 0x43cd80 },
+ { "SeaGreen4", 0x2e8b57 },
+ { "SkyBlue", 0x87ceeb },
+ { "SkyBlue1", 0x87ceff },
+ { "SkyBlue2", 0x7ec0ee },
+ { "SkyBlue3", 0x6ca6cd },
+ { "SkyBlue4", 0x4a708b },
+ { "SlateBlue", 0x6a5acd },
+ { "SlateBlue1", 0x836fff },
+ { "SlateBlue2", 0x7a67ee },
+ { "SlateBlue3", 0x6959cd },
+ { "SlateBlue4", 0x473c8b },
+ { "SlateGray", 0x708090 },
+ { "SlateGray1", 0xc6e2ff },
+ { "SlateGray2", 0xb9d3ee },
+ { "SlateGray3", 0x9fb6cd },
+ { "SlateGray4", 0x6c7b8b },
+ { "SlateGrey", 0x708090 },
+ { "SpringGreen", 0x00ff7f },
+ { "SpringGreen1", 0x00ff7f },
+ { "SpringGreen2", 0x00ee76 },
+ { "SpringGreen3", 0x00cd66 },
+ { "SpringGreen4", 0x008b45 },
+ { "SteelBlue", 0x4682b4 },
+ { "SteelBlue1", 0x63b8ff },
+ { "SteelBlue2", 0x5cacee },
+ { "SteelBlue3", 0x4f94cd },
+ { "SteelBlue4", 0x36648b },
+ { "VioletRed", 0xd02090 },
+ { "VioletRed1", 0xff3e96 },
+ { "VioletRed2", 0xee3a8c },
+ { "VioletRed3", 0xcd3278 },
+ { "VioletRed4", 0x8b2252 },
+ { "WebGray", 0x808080 },
+ { "WebGreen", 0x008000 },
+ { "WebGrey", 0x808080 },
+ { "WebMaroon", 0x800000 },
+ { "WebPurple", 0x800080 },
+ { "WhiteSmoke", 0xf5f5f5 },
+ { "X11Gray", 0xbebebe },
+ { "X11Green", 0x00ff00 },
+ { "X11Grey", 0xbebebe },
+ { "X11Maroon", 0xb03060 },
+ { "X11Purple", 0xa020f0 },
+ { "YellowGreen", 0x9acd32 },
+ { "alice blue", 0xf0f8ff },
+ { "antique white", 0xfaebd7 },
+ { "aqua", 0x00ffff },
+ { "aquamarine", 0x7fffd4 },
+ { "aquamarine1", 0x7fffd4 },
+ { "aquamarine2", 0x76eec6 },
+ { "aquamarine3", 0x66cdaa },
+ { "aquamarine4", 0x458b74 },
+ { "azure", 0xf0ffff },
+ { "azure1", 0xf0ffff },
+ { "azure2", 0xe0eeee },
+ { "azure3", 0xc1cdcd },
+ { "azure4", 0x838b8b },
+ { "beige", 0xf5f5dc },
+ { "bisque", 0xffe4c4 },
+ { "bisque1", 0xffe4c4 },
+ { "bisque2", 0xeed5b7 },
+ { "bisque3", 0xcdb79e },
+ { "bisque4", 0x8b7d6b },
+ { "black", 0x000000 },
+ { "blanched almond", 0xffebcd },
+ { "blue violet", 0x8a2be2 },
+ { "blue", 0x0000ff },
+ { "blue1", 0x0000ff },
+ { "blue2", 0x0000ee },
+ { "blue3", 0x0000cd },
+ { "blue4", 0x00008b },
+ { "brown", 0xa52a2a },
+ { "brown1", 0xff4040 },
+ { "brown2", 0xee3b3b },
+ { "brown3", 0xcd3333 },
+ { "brown4", 0x8b2323 },
+ { "burlywood", 0xdeb887 },
+ { "burlywood1", 0xffd39b },
+ { "burlywood2", 0xeec591 },
+ { "burlywood3", 0xcdaa7d },
+ { "burlywood4", 0x8b7355 },
+ { "cadet blue", 0x5f9ea0 },
+ { "chartreuse", 0x7fff00 },
+ { "chartreuse1", 0x7fff00 },
+ { "chartreuse2", 0x76ee00 },
+ { "chartreuse3", 0x66cd00 },
+ { "chartreuse4", 0x458b00 },
+ { "chocolate", 0xd2691e },
+ { "chocolate1", 0xff7f24 },
+ { "chocolate2", 0xee7621 },
+ { "chocolate3", 0xcd661d },
+ { "chocolate4", 0x8b4513 },
+ { "coral", 0xff7f50 },
+ { "coral1", 0xff7256 },
+ { "coral2", 0xee6a50 },
+ { "coral3", 0xcd5b45 },
+ { "coral4", 0x8b3e2f },
+ { "cornflower blue", 0x6495ed },
+ { "cornsilk", 0xfff8dc },
+ { "cornsilk1", 0xfff8dc },
+ { "cornsilk2", 0xeee8cd },
+ { "cornsilk3", 0xcdc8b1 },
+ { "cornsilk4", 0x8b8878 },
+ { "crimson", 0xdc143c },
+ { "cyan", 0x00ffff },
+ { "cyan1", 0x00ffff },
+ { "cyan2", 0x00eeee },
+ { "cyan3", 0x00cdcd },
+ { "cyan4", 0x008b8b },
+ { "dark blue", 0x00008b },
+ { "dark cyan", 0x008b8b },
+ { "dark goldenrod", 0xb8860b },
+ { "dark gray", 0xa9a9a9 },
+ { "dark green", 0x006400 },
+ { "dark grey", 0xa9a9a9 },
+ { "dark khaki", 0xbdb76b },
+ { "dark magenta", 0x8b008b },
+ { "dark olive green", 0x556b2f },
+ { "dark orange", 0xff8c00 },
+ { "dark orchid", 0x9932cc },
+ { "dark red", 0x8b0000 },
+ { "dark salmon", 0xe9967a },
+ { "dark sea green", 0x8fbc8f },
+ { "dark slate blue", 0x483d8b },
+ { "dark slate gray", 0x2f4f4f },
+ { "dark slate grey", 0x2f4f4f },
+ { "dark turquoise", 0x00ced1 },
+ { "dark violet", 0x9400d3 },
+ { "deep pink", 0xff1493 },
+ { "deep sky blue", 0x00bfff },
+ { "dim gray", 0x696969 },
+ { "dim grey", 0x696969 },
+ { "dodger blue", 0x1e90ff },
+ { "firebrick", 0xb22222 },
+ { "firebrick1", 0xff3030 },
+ { "firebrick2", 0xee2c2c },
+ { "firebrick3", 0xcd2626 },
+ { "firebrick4", 0x8b1a1a },
+ { "floral white", 0xfffaf0 },
+ { "forest green", 0x228b22 },
+ { "fuchsia", 0xff00ff },
+ { "gainsboro", 0xdcdcdc },
+ { "ghost white", 0xf8f8ff },
+ { "gold", 0xffd700 },
+ { "gold1", 0xffd700 },
+ { "gold2", 0xeec900 },
+ { "gold3", 0xcdad00 },
+ { "gold4", 0x8b7500 },
+ { "goldenrod", 0xdaa520 },
+ { "goldenrod1", 0xffc125 },
+ { "goldenrod2", 0xeeb422 },
+ { "goldenrod3", 0xcd9b1d },
+ { "goldenrod4", 0x8b6914 },
+ { "green yellow", 0xadff2f },
+ { "green", 0x00ff00 },
+ { "green1", 0x00ff00 },
+ { "green2", 0x00ee00 },
+ { "green3", 0x00cd00 },
+ { "green4", 0x008b00 },
+ { "honeydew", 0xf0fff0 },
+ { "honeydew1", 0xf0fff0 },
+ { "honeydew2", 0xe0eee0 },
+ { "honeydew3", 0xc1cdc1 },
+ { "honeydew4", 0x838b83 },
+ { "hot pink", 0xff69b4 },
+ { "indian red", 0xcd5c5c },
+ { "indigo", 0x4b0082 },
+ { "ivory", 0xfffff0 },
+ { "ivory1", 0xfffff0 },
+ { "ivory2", 0xeeeee0 },
+ { "ivory3", 0xcdcdc1 },
+ { "ivory4", 0x8b8b83 },
+ { "khaki", 0xf0e68c },
+ { "khaki1", 0xfff68f },
+ { "khaki2", 0xeee685 },
+ { "khaki3", 0xcdc673 },
+ { "khaki4", 0x8b864e },
+ { "lavender blush", 0xfff0f5 },
+ { "lavender", 0xe6e6fa },
+ { "lawn green", 0x7cfc00 },
+ { "lemon chiffon", 0xfffacd },
+ { "light blue", 0xadd8e6 },
+ { "light coral", 0xf08080 },
+ { "light cyan", 0xe0ffff },
+ { "light goldenrod yellow", 0xfafad2 },
+ { "light goldenrod", 0xeedd82 },
+ { "light gray", 0xd3d3d3 },
+ { "light green", 0x90ee90 },
+ { "light grey", 0xd3d3d3 },
+ { "light pink", 0xffb6c1 },
+ { "light salmon", 0xffa07a },
+ { "light sea green", 0x20b2aa },
+ { "light sky blue", 0x87cefa },
+ { "light slate blue", 0x8470ff },
+ { "light slate gray", 0x778899 },
+ { "light slate grey", 0x778899 },
+ { "light steel blue", 0xb0c4de },
+ { "light yellow", 0xffffe0 },
+ { "lime green", 0x32cd32 },
+ { "lime", 0x00ff00 },
+ { "linen", 0xfaf0e6 },
+ { "magenta", 0xff00ff },
+ { "magenta1", 0xff00ff },
+ { "magenta2", 0xee00ee },
+ { "magenta3", 0xcd00cd },
+ { "magenta4", 0x8b008b },
+ { "maroon", 0xb03060 },
+ { "maroon1", 0xff34b3 },
+ { "maroon2", 0xee30a7 },
+ { "maroon3", 0xcd2990 },
+ { "maroon4", 0x8b1c62 },
+ { "medium aquamarine", 0x66cdaa },
+ { "medium blue", 0x0000cd },
+ { "medium orchid", 0xba55d3 },
+ { "medium purple", 0x9370db },
+ { "medium sea green", 0x3cb371 },
+ { "medium slate blue", 0x7b68ee },
+ { "medium spring green", 0x00fa9a },
+ { "medium turquoise", 0x48d1cc },
+ { "medium violet red", 0xc71585 },
+ { "midnight blue", 0x191970 },
+ { "mint cream", 0xf5fffa },
+ { "misty rose", 0xffe4e1 },
+ { "moccasin", 0xffe4b5 },
+ { "navajo white", 0xffdead },
+ { "navy blue", 0x000080 },
+ { "navy", 0x000080 },
+ { "old lace", 0xfdf5e6 },
+ { "olive drab", 0x6b8e23 },
+ { "olive", 0x808000 },
+ { "orange red", 0xff4500 },
+ { "orange", 0xffa500 },
+ { "orange1", 0xffa500 },
+ { "orange2", 0xee9a00 },
+ { "orange3", 0xcd8500 },
+ { "orange4", 0x8b5a00 },
+ { "orchid", 0xda70d6 },
+ { "orchid1", 0xff83fa },
+ { "orchid2", 0xee7ae9 },
+ { "orchid3", 0xcd69c9 },
+ { "orchid4", 0x8b4789 },
+ { "pale goldenrod", 0xeee8aa },
+ { "pale green", 0x98fb98 },
+ { "pale turquoise", 0xafeeee },
+ { "pale violet red", 0xdb7093 },
+ { "papaya whip", 0xffefd5 },
+ { "peach puff", 0xffdab9 },
+ { "peru", 0xcd853f },
+ { "pink", 0xffc0cb },
+ { "pink1", 0xffb5c5 },
+ { "pink2", 0xeea9b8 },
+ { "pink3", 0xcd919e },
+ { "pink4", 0x8b636c },
+ { "plum", 0xdda0dd },
+ { "plum1", 0xffbbff },
+ { "plum2", 0xeeaeee },
+ { "plum3", 0xcd96cd },
+ { "plum4", 0x8b668b },
+ { "powder blue", 0xb0e0e6 },
+ { "purple", 0xa020f0 },
+ { "purple1", 0x9b30ff },
+ { "purple2", 0x912cee },
+ { "purple3", 0x7d26cd },
+ { "purple4", 0x551a8b },
+ { "rebecca purple", 0x663399 },
+ { "red", 0xff0000 },
+ { "red1", 0xff0000 },
+ { "red2", 0xee0000 },
+ { "red3", 0xcd0000 },
+ { "red4", 0x8b0000 },
+ { "rosy brown", 0xbc8f8f },
+ { "royal blue", 0x4169e1 },
+ { "saddle brown", 0x8b4513 },
+ { "salmon", 0xfa8072 },
+ { "salmon1", 0xff8c69 },
+ { "salmon2", 0xee8262 },
+ { "salmon3", 0xcd7054 },
+ { "salmon4", 0x8b4c39 },
+ { "sandy brown", 0xf4a460 },
+ { "sea green", 0x2e8b57 },
+ { "seashell", 0xfff5ee },
+ { "seashell1", 0xfff5ee },
+ { "seashell2", 0xeee5de },
+ { "seashell3", 0xcdc5bf },
+ { "seashell4", 0x8b8682 },
+ { "sienna", 0xa0522d },
+ { "sienna1", 0xff8247 },
+ { "sienna2", 0xee7942 },
+ { "sienna3", 0xcd6839 },
+ { "sienna4", 0x8b4726 },
+ { "silver", 0xc0c0c0 },
+ { "sky blue", 0x87ceeb },
+ { "slate blue", 0x6a5acd },
+ { "slate gray", 0x708090 },
+ { "slate grey", 0x708090 },
+ { "snow", 0xfffafa },
+ { "snow1", 0xfffafa },
+ { "snow2", 0xeee9e9 },
+ { "snow3", 0xcdc9c9 },
+ { "snow4", 0x8b8989 },
+ { "spring green", 0x00ff7f },
+ { "steel blue", 0x4682b4 },
+ { "tan", 0xd2b48c },
+ { "tan1", 0xffa54f },
+ { "tan2", 0xee9a49 },
+ { "tan3", 0xcd853f },
+ { "tan4", 0x8b5a2b },
+ { "teal", 0x008080 },
+ { "thistle", 0xd8bfd8 },
+ { "thistle1", 0xffe1ff },
+ { "thistle2", 0xeed2ee },
+ { "thistle3", 0xcdb5cd },
+ { "thistle4", 0x8b7b8b },
+ { "tomato", 0xff6347 },
+ { "tomato1", 0xff6347 },
+ { "tomato2", 0xee5c42 },
+ { "tomato3", 0xcd4f39 },
+ { "tomato4", 0x8b3626 },
+ { "turquoise", 0x40e0d0 },
+ { "turquoise1", 0x00f5ff },
+ { "turquoise2", 0x00e5ee },
+ { "turquoise3", 0x00c5cd },
+ { "turquoise4", 0x00868b },
+ { "violet red", 0xd02090 },
+ { "violet", 0xee82ee },
+ { "web gray", 0x808080 },
+ { "web green", 0x008000 },
+ { "web grey", 0x808080 },
+ { "web maroon", 0x800000 },
+ { "web purple", 0x800080 },
+ { "wheat", 0xf5deb3 },
+ { "wheat1", 0xffe7ba },
+ { "wheat2", 0xeed8ae },
+ { "wheat3", 0xcdba96 },
+ { "wheat4", 0x8b7e66 },
+ { "white smoke", 0xf5f5f5 },
+ { "white", 0xffffff },
+ { "x11 gray", 0xbebebe },
+ { "x11 green", 0x00ff00 },
+ { "x11 grey", 0xbebebe },
+ { "x11 maroon", 0xb03060 },
+ { "x11 purple", 0xa020f0 },
+ { "yellow green", 0x9acd32 },
+ { "yellow", 0xffff00 },
+ { "yellow1", 0xffff00 },
+ { "yellow2", 0xeeee00 },
+ { "yellow3", 0xcdcd00 },
+ { "yellow4", 0x8b8b00 }
+ };
+ u_int i;
+ int c;
+
+ if (strncmp(name, "grey", 4) == 0 || strncmp(name, "gray", 4) == 0) {
+ if (!isdigit((u_char)name[4]))
+ return (0xbebebe|COLOUR_FLAG_RGB);
+ c = round(2.55 * atoi(name + 4));
+ if (c < 0 || c > 255)
+ return (-1);
+ return (colour_join_rgb(c, c, c));
+ }
+ for (i = 0; i < nitems(colours); i++) {
+ if (strcasecmp(colours[i].name, name) == 0)
+ return (colours[i].c|COLOUR_FLAG_RGB);
+ }
+ return (-1);
+}
+
+/* Initialize palette. */
+void
+colour_palette_init(struct colour_palette *p)
+{
+ p->fg = 8;
+ p->bg = 8;
+ p->palette = NULL;
+ p->default_palette = NULL;
+}
+
+/* Clear palette. */
+void
+colour_palette_clear(struct colour_palette *p)
+{
+ if (p != NULL) {
+ p->fg = 8;
+ p->bg = 8;
+ free(p->palette);
+ p->palette = NULL;
+ }
+}
+
+/* Free a palette. */
+void
+colour_palette_free(struct colour_palette *p)
+{
+ if (p != NULL) {
+ free(p->palette);
+ p->palette = NULL;
+ free(p->default_palette);
+ p->default_palette = NULL;
+ }
+}
+
+/* Get a colour from a palette. */
+int
+colour_palette_get(struct colour_palette *p, int c)
+{
+ if (p == NULL)
+ return (-1);
+
+ if (c >= 90 && c <= 97)
+ c = 8 + c - 90;
+ else if (c & COLOUR_FLAG_256)
+ c &= ~COLOUR_FLAG_256;
+ else if (c >= 8)
+ return (-1);
+
+ if (p->palette != NULL && p->palette[c] != -1)
+ return (p->palette[c]);
+ if (p->default_palette != NULL && p->default_palette[c] != -1)
+ return (p->default_palette[c]);
+ return (-1);
+}
+
+/* Set a colour in a palette. */
+int
+colour_palette_set(struct colour_palette *p, int n, int c)
+{
+ u_int i;
+
+ if (p == NULL || n > 255)
+ return (0);
+
+ if (c == -1 && p->palette == NULL)
+ return (0);
+
+ if (c != -1 && p->palette == NULL) {
+ if (p->palette == NULL)
+ p->palette = xcalloc(256, sizeof *p->palette);
+ for (i = 0; i < 256; i++)
+ p->palette[i] = -1;
+ }
+ p->palette[n] = c;
+ return (1);
+}
+
+/* Build palette defaults from an option. */
+void
+colour_palette_from_option(struct colour_palette *p, struct options *oo)
+{
+ struct options_entry *o;
+ struct options_array_item *a;
+ u_int i, n;
+ int c;
+
+ if (p == NULL)
+ return;
+
+ o = options_get(oo, "pane-colours");
+ if ((a = options_array_first(o)) == NULL) {
+ if (p->default_palette != NULL) {
+ free(p->default_palette);
+ p->default_palette = NULL;
+ }
+ return;
+ }
+ if (p->default_palette == NULL)
+ p->default_palette = xcalloc(256, sizeof *p->default_palette);
+ for (i = 0; i < 256; i++)
+ p->default_palette[i] = -1;
+ while (a != NULL) {
+ n = options_array_item_index(a);
+ if (n < 256) {
+ c = options_array_item_value(a)->number;
+ p->default_palette[n] = c;
+ }
+ a = options_array_next(a);
+ }
+
+}
diff --git a/compat.h b/compat.h
new file mode 100644
index 0000000..6eb9761
--- /dev/null
+++ b/compat.h
@@ -0,0 +1,455 @@
+/*
+ * 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.
+ */
+
+#ifndef COMPAT_H
+#define COMPAT_H
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <fnmatch.h>
+#include <limits.h>
+#include <stdio.h>
+#include <termios.h>
+#include <wchar.h>
+
+#ifdef HAVE_EVENT2_EVENT_H
+#include <event2/event.h>
+#include <event2/event_compat.h>
+#include <event2/event_struct.h>
+#include <event2/buffer.h>
+#include <event2/buffer_compat.h>
+#include <event2/bufferevent.h>
+#include <event2/bufferevent_struct.h>
+#include <event2/bufferevent_compat.h>
+#else
+#include <event.h>
+#endif
+
+#ifdef HAVE_MALLOC_TRIM
+#include <malloc.h>
+#endif
+
+#ifdef HAVE_UTF8PROC
+#include <utf8proc.h>
+#endif
+
+#ifndef __GNUC__
+#define __attribute__(a)
+#endif
+
+#ifdef BROKEN___DEAD
+#undef __dead
+#endif
+
+#ifndef __unused
+#define __unused __attribute__ ((__unused__))
+#endif
+#ifndef __dead
+#define __dead __attribute__ ((__noreturn__))
+#endif
+#ifndef __packed
+#define __packed __attribute__ ((__packed__))
+#endif
+#ifndef __weak
+#define __weak __attribute__ ((__weak__))
+#endif
+
+#ifndef ECHOPRT
+#define ECHOPRT 0
+#endif
+
+#ifndef ACCESSPERMS
+#define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO)
+#endif
+
+#if !defined(FIONREAD) && defined(__sun)
+#include <sys/filio.h>
+#endif
+
+#ifdef HAVE_ERR_H
+#include <err.h>
+#else
+void err(int, const char *, ...);
+void errx(int, const char *, ...);
+void warn(const char *, ...);
+void warnx(const char *, ...);
+#endif
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+#ifndef _PATH_BSHELL
+#define _PATH_BSHELL "/bin/sh"
+#endif
+
+#ifndef _PATH_TMP
+#define _PATH_TMP "/tmp/"
+#endif
+
+#ifndef _PATH_DEVNULL
+#define _PATH_DEVNULL "/dev/null"
+#endif
+
+#ifndef _PATH_TTY
+#define _PATH_TTY "/dev/tty"
+#endif
+
+#ifndef _PATH_DEV
+#define _PATH_DEV "/dev/"
+#endif
+
+#ifndef _PATH_DEFPATH
+#define _PATH_DEFPATH "/usr/bin:/bin"
+#endif
+
+#ifndef _PATH_VI
+#define _PATH_VI "/usr/bin/vi"
+#endif
+
+#ifndef __OpenBSD__
+#define pledge(s, p) (0)
+#endif
+
+#ifndef IMAXBEL
+#define IMAXBEL 0
+#endif
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#else
+#include <inttypes.h>
+#endif
+
+#ifdef HAVE_QUEUE_H
+#include <sys/queue.h>
+#else
+#include "compat/queue.h"
+#endif
+
+#ifdef HAVE_TREE_H
+#include <sys/tree.h>
+#else
+#include "compat/tree.h"
+#endif
+
+#ifdef HAVE_BITSTRING_H
+#include <bitstring.h>
+#else
+#include "compat/bitstring.h"
+#endif
+
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+#ifdef HAVE_VIS
+#include <vis.h>
+#else
+#include "compat/vis.h"
+#endif
+
+#ifdef HAVE_IMSG
+#include <imsg.h>
+#else
+#include "compat/imsg.h"
+#endif
+
+#ifdef BROKEN_CMSG_FIRSTHDR
+#undef CMSG_FIRSTHDR
+#define CMSG_FIRSTHDR(mhdr) \
+ ((mhdr)->msg_controllen >= sizeof(struct cmsghdr) ? \
+ (struct cmsghdr *)(mhdr)->msg_control : \
+ (struct cmsghdr *)NULL)
+#endif
+
+#ifndef CMSG_ALIGN
+#ifdef _CMSG_DATA_ALIGN
+#define CMSG_ALIGN _CMSG_DATA_ALIGN
+#else
+#define CMSG_ALIGN(len) (((len) + sizeof(long) - 1) & ~(sizeof(long) - 1))
+#endif
+#endif
+
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(len))
+#endif
+
+#ifndef CMSG_LEN
+#define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
+#endif
+
+#ifndef O_DIRECTORY
+#define O_DIRECTORY 0
+#endif
+
+#ifndef FNM_CASEFOLD
+#ifdef FNM_IGNORECASE
+#define FNM_CASEFOLD FNM_IGNORECASE
+#else
+#define FNM_CASEFOLD 0
+#endif
+#endif
+
+#ifndef INFTIM
+#define INFTIM -1
+#endif
+
+#ifndef WAIT_ANY
+#define WAIT_ANY -1
+#endif
+
+#ifndef SUN_LEN
+#define SUN_LEN(sun) (sizeof (sun)->sun_path)
+#endif
+
+#ifndef timercmp
+#define timercmp(tvp, uvp, cmp) \
+ (((tvp)->tv_sec == (uvp)->tv_sec) ? \
+ ((tvp)->tv_usec cmp (uvp)->tv_usec) : \
+ ((tvp)->tv_sec cmp (uvp)->tv_sec))
+#endif
+
+#ifndef timeradd
+#define timeradd(tvp, uvp, vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
+ if ((vvp)->tv_usec >= 1000000) { \
+ (vvp)->tv_sec++; \
+ (vvp)->tv_usec -= 1000000; \
+ } \
+ } while (0)
+#endif
+
+#ifndef timersub
+#define timersub(tvp, uvp, vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
+ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
+ if ((vvp)->tv_usec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif
+
+#ifndef TTY_NAME_MAX
+#define TTY_NAME_MAX 32
+#endif
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 255
+#endif
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC CLOCK_REALTIME
+#endif
+
+#ifndef HAVE_FLOCK
+#define LOCK_SH 0
+#define LOCK_EX 0
+#define LOCK_NB 0
+#define flock(fd, op) (0)
+#endif
+
+#ifndef HAVE_EXPLICIT_BZERO
+/* explicit_bzero.c */
+void explicit_bzero(void *, size_t);
+#endif
+
+#ifndef HAVE_GETDTABLECOUNT
+/* getdtablecount.c */
+int getdtablecount(void);
+#endif
+
+#ifndef HAVE_CLOSEFROM
+/* closefrom.c */
+void closefrom(int);
+#endif
+
+#ifndef HAVE_STRCASESTR
+/* strcasestr.c */
+char *strcasestr(const char *, const char *);
+#endif
+
+#ifndef HAVE_STRSEP
+/* strsep.c */
+char *strsep(char **, const char *);
+#endif
+
+#ifndef HAVE_STRTONUM
+/* strtonum.c */
+long long strtonum(const char *, long long, long long, const char **);
+#endif
+
+#ifndef HAVE_STRLCPY
+/* strlcpy.c */
+size_t strlcpy(char *, const char *, size_t);
+#endif
+
+#ifndef HAVE_STRLCAT
+/* strlcat.c */
+size_t strlcat(char *, const char *, size_t);
+#endif
+
+#ifndef HAVE_STRNLEN
+/* strnlen.c */
+size_t strnlen(const char *, size_t);
+#endif
+
+#ifndef HAVE_STRNDUP
+/* strndup.c */
+char *strndup(const char *, size_t);
+#endif
+
+#ifndef HAVE_MEMMEM
+/* memmem.c */
+void *memmem(const void *, size_t, const void *, size_t);
+#endif
+
+#ifndef HAVE_GETPEEREID
+/* getpeereid.c */
+int getpeereid(int, uid_t *, gid_t *);
+#endif
+
+#ifndef HAVE_DAEMON
+/* daemon.c */
+int daemon(int, int);
+#endif
+
+#ifndef HAVE_GETPROGNAME
+/* getprogname.c */
+const char *getprogname(void);
+#endif
+
+#ifndef HAVE_SETPROCTITLE
+/* setproctitle.c */
+void setproctitle(const char *, ...);
+#endif
+
+#ifndef HAVE_CLOCK_GETTIME
+/* clock_gettime.c */
+int clock_gettime(int, struct timespec *);
+#endif
+
+#ifndef HAVE_B64_NTOP
+/* base64.c */
+#undef b64_ntop
+#undef b64_pton
+int b64_ntop(const char *, size_t, char *, size_t);
+int b64_pton(const char *, u_char *, size_t);
+#endif
+
+#ifndef HAVE_FDFORKPTY
+/* fdforkpty.c */
+int getptmfd(void);
+pid_t fdforkpty(int, int *, char *, struct termios *,
+ struct winsize *);
+#endif
+
+#ifndef HAVE_FORKPTY
+/* forkpty.c */
+pid_t forkpty(int *, char *, struct termios *, struct winsize *);
+#endif
+
+#ifndef HAVE_ASPRINTF
+/* asprintf.c */
+int asprintf(char **, const char *, ...);
+int vasprintf(char **, const char *, va_list);
+#endif
+
+#ifndef HAVE_FGETLN
+/* fgetln.c */
+char *fgetln(FILE *, size_t *);
+#endif
+
+#ifndef HAVE_GETLINE
+/* getline.c */
+ssize_t getline(char **, size_t *, FILE *);
+#endif
+
+#ifndef HAVE_SETENV
+/* setenv.c */
+int setenv(const char *, const char *, int);
+int unsetenv(const char *);
+#endif
+
+#ifndef HAVE_CFMAKERAW
+/* cfmakeraw.c */
+void cfmakeraw(struct termios *);
+#endif
+
+#ifndef HAVE_FREEZERO
+/* freezero.c */
+void freezero(void *, size_t);
+#endif
+
+#ifndef HAVE_REALLOCARRAY
+/* reallocarray.c */
+void *reallocarray(void *, size_t, size_t);
+#endif
+
+#ifndef HAVE_RECALLOCARRAY
+/* recallocarray.c */
+void *recallocarray(void *, size_t, size_t, size_t);
+#endif
+
+#ifdef HAVE_SYSTEMD
+/* systemd.c */
+int systemd_create_socket(int, char **);
+#endif
+
+#ifdef HAVE_UTF8PROC
+/* utf8proc.c */
+int utf8proc_wcwidth(wchar_t);
+int utf8proc_mbtowc(wchar_t *, const char *, size_t);
+int utf8proc_wctomb(char *, wchar_t);
+#endif
+
+#ifdef NEED_FUZZING
+/* tmux.c */
+#define main __weak main
+#endif
+
+/* getopt.c */
+extern int BSDopterr;
+extern int BSDoptind;
+extern int BSDoptopt;
+extern int BSDoptreset;
+extern char *BSDoptarg;
+int BSDgetopt(int, char *const *, const char *);
+#define getopt(ac, av, o) BSDgetopt(ac, av, o)
+#define opterr BSDopterr
+#define optind BSDoptind
+#define optopt BSDoptopt
+#define optreset BSDoptreset
+#define optarg BSDoptarg
+
+#endif /* COMPAT_H */
diff --git a/compat/asprintf.c b/compat/asprintf.c
new file mode 100644
index 0000000..187c19f
--- /dev/null
+++ b/compat/asprintf.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2006 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "compat.h"
+#include "xmalloc.h"
+
+int
+asprintf(char **ret, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = vasprintf(ret, fmt, ap);
+ va_end(ap);
+
+ return (n);
+}
+
+int
+vasprintf(char **ret, const char *fmt, va_list ap)
+{
+ int n;
+ va_list ap2;
+
+ va_copy(ap2, ap);
+
+ if ((n = vsnprintf(NULL, 0, fmt, ap)) < 0)
+ goto error;
+
+ *ret = xmalloc(n + 1);
+ if ((n = vsnprintf(*ret, n + 1, fmt, ap2)) < 0) {
+ free(*ret);
+ goto error;
+ }
+ va_end(ap2);
+
+ return (n);
+
+error:
+ va_end(ap2);
+ *ret = NULL;
+ return (-1);
+}
diff --git a/compat/base64.c b/compat/base64.c
new file mode 100644
index 0000000..e90696d
--- /dev/null
+++ b/compat/base64.c
@@ -0,0 +1,315 @@
+/* $OpenBSD: base64.c,v 1.8 2015/01/16 16:48:51 deraadt Exp $ */
+
+/*
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 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.
+ */
+
+/*
+ * Portions Copyright (c) 1995 by International Business Machines, Inc.
+ *
+ * International Business Machines, Inc. (hereinafter called IBM) grants
+ * permission under its copyrights to use, copy, modify, and distribute this
+ * Software with or without fee, provided that the above copyright notice and
+ * all paragraphs of this notice appear in all copies, and that the name of IBM
+ * not be used in connection with the marketing of any product incorporating
+ * the Software or modifications thereof, without specific, written prior
+ * permission.
+ *
+ * To the extent it has a right to do so, IBM grants an immunity from suit
+ * under its patents, if any, for the use, sale or manufacture of products to
+ * the extent that such products are used for performing Domain Name System
+ * dynamic updates in TCP/IP networks by means of the Software. No immunity is
+ * granted for any product per se or for any other function of any product.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
+ * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
+ * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ctype.h>
+#include <resolv.h>
+#include <stdio.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+static const char Base64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char Pad64 = '=';
+
+/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt)
+ The following encoding technique is taken from RFC 1521 by Borenstein
+ and Freed. It is reproduced here in a slightly edited form for
+ convenience.
+
+ A 65-character subset of US-ASCII is used, enabling 6 bits to be
+ represented per printable character. (The extra 65th character, "=",
+ is used to signify a special processing function.)
+
+ The encoding process represents 24-bit groups of input bits as output
+ strings of 4 encoded characters. Proceeding from left to right, a
+ 24-bit input group is formed by concatenating 3 8-bit input groups.
+ These 24 bits are then treated as 4 concatenated 6-bit groups, each
+ of which is translated into a single digit in the base64 alphabet.
+
+ Each 6-bit group is used as an index into an array of 64 printable
+ characters. The character referenced by the index is placed in the
+ output string.
+
+ Table 1: The Base64 Alphabet
+
+ Value Encoding Value Encoding Value Encoding Value Encoding
+ 0 A 17 R 34 i 51 z
+ 1 B 18 S 35 j 52 0
+ 2 C 19 T 36 k 53 1
+ 3 D 20 U 37 l 54 2
+ 4 E 21 V 38 m 55 3
+ 5 F 22 W 39 n 56 4
+ 6 G 23 X 40 o 57 5
+ 7 H 24 Y 41 p 58 6
+ 8 I 25 Z 42 q 59 7
+ 9 J 26 a 43 r 60 8
+ 10 K 27 b 44 s 61 9
+ 11 L 28 c 45 t 62 +
+ 12 M 29 d 46 u 63 /
+ 13 N 30 e 47 v
+ 14 O 31 f 48 w (pad) =
+ 15 P 32 g 49 x
+ 16 Q 33 h 50 y
+
+ Special processing is performed if fewer than 24 bits are available
+ at the end of the data being encoded. A full encoding quantum is
+ always completed at the end of a quantity. When fewer than 24 input
+ bits are available in an input group, zero bits are added (on the
+ right) to form an integral number of 6-bit groups. Padding at the
+ end of the data is performed using the '=' character.
+
+ Since all base64 input is an integral number of octets, only the
+ -------------------------------------------------
+ following cases can arise:
+
+ (1) the final quantum of encoding input is an integral
+ multiple of 24 bits; here, the final unit of encoded
+ output will be an integral multiple of 4 characters
+ with no "=" padding,
+ (2) the final quantum of encoding input is exactly 8 bits;
+ here, the final unit of encoded output will be two
+ characters followed by two "=" padding characters, or
+ (3) the final quantum of encoding input is exactly 16 bits;
+ here, the final unit of encoded output will be three
+ characters followed by one "=" padding character.
+ */
+
+int
+b64_ntop(src, srclength, target, targsize)
+ u_char const *src;
+ size_t srclength;
+ char *target;
+ size_t targsize;
+{
+ size_t datalength = 0;
+ u_char input[3];
+ u_char output[4];
+ int i;
+
+ while (2 < srclength) {
+ input[0] = *src++;
+ input[1] = *src++;
+ input[2] = *src++;
+ srclength -= 3;
+
+ output[0] = input[0] >> 2;
+ output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+ output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+ output[3] = input[2] & 0x3f;
+
+ if (datalength + 4 > targsize)
+ return (-1);
+ target[datalength++] = Base64[output[0]];
+ target[datalength++] = Base64[output[1]];
+ target[datalength++] = Base64[output[2]];
+ target[datalength++] = Base64[output[3]];
+ }
+
+ /* Now we worry about padding. */
+ if (0 != srclength) {
+ /* Get what's left. */
+ input[0] = input[1] = input[2] = '\0';
+ for (i = 0; i < srclength; i++)
+ input[i] = *src++;
+
+ output[0] = input[0] >> 2;
+ output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+ output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+
+ if (datalength + 4 > targsize)
+ return (-1);
+ target[datalength++] = Base64[output[0]];
+ target[datalength++] = Base64[output[1]];
+ if (srclength == 1)
+ target[datalength++] = Pad64;
+ else
+ target[datalength++] = Base64[output[2]];
+ target[datalength++] = Pad64;
+ }
+ if (datalength >= targsize)
+ return (-1);
+ target[datalength] = '\0'; /* Returned value doesn't count \0. */
+ return (datalength);
+}
+
+/* skips all whitespace anywhere.
+ converts characters, four at a time, starting at (or after)
+ src from base - 64 numbers into three 8 bit bytes in the target area.
+ it returns the number of data bytes stored at the target, or -1 on error.
+ */
+
+int
+b64_pton(src, target, targsize)
+ char const *src;
+ u_char *target;
+ size_t targsize;
+{
+ int tarindex, state, ch;
+ u_char nextbyte;
+ char *pos;
+
+ state = 0;
+ tarindex = 0;
+
+ while ((ch = (unsigned char)*src++) != '\0') {
+ if (isspace(ch)) /* Skip whitespace anywhere. */
+ continue;
+
+ if (ch == Pad64)
+ break;
+
+ pos = strchr(Base64, ch);
+ if (pos == 0) /* A non-base64 character. */
+ return (-1);
+
+ switch (state) {
+ case 0:
+ if (target) {
+ if (tarindex >= targsize)
+ return (-1);
+ target[tarindex] = (pos - Base64) << 2;
+ }
+ state = 1;
+ break;
+ case 1:
+ if (target) {
+ if (tarindex >= targsize)
+ return (-1);
+ target[tarindex] |= (pos - Base64) >> 4;
+ nextbyte = ((pos - Base64) & 0x0f) << 4;
+ if (tarindex + 1 < targsize)
+ target[tarindex+1] = nextbyte;
+ else if (nextbyte)
+ return (-1);
+ }
+ tarindex++;
+ state = 2;
+ break;
+ case 2:
+ if (target) {
+ if (tarindex >= targsize)
+ return (-1);
+ target[tarindex] |= (pos - Base64) >> 2;
+ nextbyte = ((pos - Base64) & 0x03) << 6;
+ if (tarindex + 1 < targsize)
+ target[tarindex+1] = nextbyte;
+ else if (nextbyte)
+ return (-1);
+ }
+ tarindex++;
+ state = 3;
+ break;
+ case 3:
+ if (target) {
+ if (tarindex >= targsize)
+ return (-1);
+ target[tarindex] |= (pos - Base64);
+ }
+ tarindex++;
+ state = 0;
+ break;
+ }
+ }
+
+ /*
+ * We are done decoding Base-64 chars. Let's see if we ended
+ * on a byte boundary, and/or with erroneous trailing characters.
+ */
+
+ if (ch == Pad64) { /* We got a pad char. */
+ ch = (unsigned char)*src++; /* Skip it, get next. */
+ switch (state) {
+ case 0: /* Invalid = in first position */
+ case 1: /* Invalid = in second position */
+ return (-1);
+
+ case 2: /* Valid, means one byte of info */
+ /* Skip any number of spaces. */
+ for (; ch != '\0'; ch = (unsigned char)*src++)
+ if (!isspace(ch))
+ break;
+ /* Make sure there is another trailing = sign. */
+ if (ch != Pad64)
+ return (-1);
+ ch = (unsigned char)*src++; /* Skip the = */
+ /* Fall through to "single trailing =" case. */
+ /* FALLTHROUGH */
+
+ case 3: /* Valid, means two bytes of info */
+ /*
+ * We know this char is an =. Is there anything but
+ * whitespace after it?
+ */
+ for (; ch != '\0'; ch = (unsigned char)*src++)
+ if (!isspace(ch))
+ return (-1);
+
+ /*
+ * Now make sure for cases 2 and 3 that the "extra"
+ * bits that slopped past the last full byte were
+ * zeros. If we don't check them, they become a
+ * subliminal channel.
+ */
+ if (target && tarindex < targsize &&
+ target[tarindex] != 0)
+ return (-1);
+ }
+ } else {
+ /*
+ * We ended by seeing the end of the string. Make sure we
+ * have no partial bytes lying around.
+ */
+ if (state != 0)
+ return (-1);
+ }
+
+ return (tarindex);
+}
diff --git a/compat/bitstring.h b/compat/bitstring.h
new file mode 100644
index 0000000..8fc3423
--- /dev/null
+++ b/compat/bitstring.h
@@ -0,0 +1,128 @@
+/* $OpenBSD: bitstring.h,v 1.5 2003/06/02 19:34:12 millert Exp $ */
+/* $NetBSD: bitstring.h,v 1.5 1997/05/14 15:49:55 pk Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Paul Vixie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)bitstring.h 8.1 (Berkeley) 7/19/93
+ */
+
+#ifndef _BITSTRING_H_
+#define _BITSTRING_H_
+
+/* modified for SV/AT and bitstring bugfix by M.R.Murphy, 11oct91
+ * bitstr_size changed gratuitously, but shorter
+ * bit_alloc spelling error fixed
+ * the following were efficient, but didn't work, they've been made to
+ * work, but are no longer as efficient :-)
+ * bit_nclear, bit_nset, bit_ffc, bit_ffs
+ */
+typedef unsigned char bitstr_t;
+
+/* internal macros */
+ /* byte of the bitstring bit is in */
+#define _bit_byte(bit) \
+ ((bit) >> 3)
+
+ /* mask for the bit within its byte */
+#define _bit_mask(bit) \
+ (1 << ((bit)&0x7))
+
+/* external macros */
+ /* bytes in a bitstring of nbits bits */
+#define bitstr_size(nbits) \
+ (((nbits) + 7) >> 3)
+
+ /* allocate a bitstring */
+#define bit_alloc(nbits) \
+ (bitstr_t *)calloc((size_t)bitstr_size(nbits), sizeof(bitstr_t))
+
+ /* allocate a bitstring on the stack */
+#define bit_decl(name, nbits) \
+ ((name)[bitstr_size(nbits)])
+
+ /* is bit N of bitstring name set? */
+#define bit_test(name, bit) \
+ ((name)[_bit_byte(bit)] & _bit_mask(bit))
+
+ /* set bit N of bitstring name */
+#define bit_set(name, bit) \
+ ((name)[_bit_byte(bit)] |= _bit_mask(bit))
+
+ /* clear bit N of bitstring name */
+#define bit_clear(name, bit) \
+ ((name)[_bit_byte(bit)] &= ~_bit_mask(bit))
+
+ /* clear bits start ... stop in bitstring */
+#define bit_nclear(name, start, stop) do { \
+ register bitstr_t *_name = name; \
+ register int _start = start, _stop = stop; \
+ while (_start <= _stop) { \
+ bit_clear(_name, _start); \
+ _start++; \
+ } \
+} while(0)
+
+ /* set bits start ... stop in bitstring */
+#define bit_nset(name, start, stop) do { \
+ register bitstr_t *_name = name; \
+ register int _start = start, _stop = stop; \
+ while (_start <= _stop) { \
+ bit_set(_name, _start); \
+ _start++; \
+ } \
+} while(0)
+
+ /* find first bit clear in name */
+#define bit_ffc(name, nbits, value) do { \
+ register bitstr_t *_name = name; \
+ register int _bit, _nbits = nbits, _value = -1; \
+ for (_bit = 0; _bit < _nbits; ++_bit) \
+ if (!bit_test(_name, _bit)) { \
+ _value = _bit; \
+ break; \
+ } \
+ *(value) = _value; \
+} while(0)
+
+ /* find first bit set in name */
+#define bit_ffs(name, nbits, value) do { \
+ register bitstr_t *_name = name; \
+ register int _bit, _nbits = nbits, _value = -1; \
+ for (_bit = 0; _bit < _nbits; ++_bit) \
+ if (bit_test(_name, _bit)) { \
+ _value = _bit; \
+ break; \
+ } \
+ *(value) = _value; \
+} while(0)
+
+#endif /* !_BITSTRING_H_ */
diff --git a/compat/cfmakeraw.c b/compat/cfmakeraw.c
new file mode 100644
index 0000000..b481a90
--- /dev/null
+++ b/compat/cfmakeraw.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013 Dagobert Michelsen
+ * Copyright (c) 2013 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 <string.h>
+#include <termios.h>
+
+#include "compat.h"
+
+void
+cfmakeraw(struct termios *tio)
+{
+ tio->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+ tio->c_oflag &= ~OPOST;
+ tio->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ tio->c_cflag &= ~(CSIZE|PARENB);
+ tio->c_cflag |= CS8;
+}
diff --git a/compat/clock_gettime.c b/compat/clock_gettime.c
new file mode 100644
index 0000000..8290e75
--- /dev/null
+++ b/compat/clock_gettime.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "compat.h"
+
+#ifndef TIMEVAL_TO_TIMESPEC
+#define TIMEVAL_TO_TIMESPEC(tv, ts) do { \
+ (ts)->tv_sec = (tv)->tv_sec; \
+ (ts)->tv_nsec = (tv)->tv_usec * 1000; \
+} while (0)
+#endif
+
+int
+clock_gettime(int clock, struct timespec *ts)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ TIMEVAL_TO_TIMESPEC(&tv, ts);
+ return 0;
+}
diff --git a/compat/closefrom.c b/compat/closefrom.c
new file mode 100644
index 0000000..be00823
--- /dev/null
+++ b/compat/closefrom.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2004-2005 Todd C. Miller <Todd.Miller@courtesan.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 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.
+ */
+
+#ifndef HAVE_CLOSEFROM
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <stdio.h>
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#include <limits.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# ifdef HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+# include <ndir.h>
+# endif
+#endif
+#if defined(HAVE_LIBPROC_H)
+# include <libproc.h>
+#endif
+
+#include "compat.h"
+
+#ifndef OPEN_MAX
+# define OPEN_MAX 256
+#endif
+
+#if 0
+__unused static const char rcsid[] = "$Sudo: closefrom.c,v 1.11 2006/08/17 15:26:54 millert Exp $";
+#endif /* lint */
+
+#ifndef HAVE_FCNTL_CLOSEM
+/*
+ * Close all file descriptors greater than or equal to lowfd.
+ */
+static void
+closefrom_fallback(int lowfd)
+{
+ long fd, maxfd;
+
+ /*
+ * Fall back on sysconf() or getdtablesize(). We avoid checking
+ * resource limits since it is possible to open a file descriptor
+ * and then drop the rlimit such that it is below the open fd.
+ */
+#ifdef HAVE_SYSCONF
+ maxfd = sysconf(_SC_OPEN_MAX);
+#else
+ maxfd = getdtablesize();
+#endif /* HAVE_SYSCONF */
+ if (maxfd < 0)
+ maxfd = OPEN_MAX;
+
+ for (fd = lowfd; fd < maxfd; fd++)
+ (void) close((int) fd);
+}
+#endif /* HAVE_FCNTL_CLOSEM */
+
+#ifdef HAVE_FCNTL_CLOSEM
+void
+closefrom(int lowfd)
+{
+ (void) fcntl(lowfd, F_CLOSEM, 0);
+}
+#elif defined(HAVE_LIBPROC_H) && defined(HAVE_PROC_PIDINFO)
+void
+closefrom(int lowfd)
+{
+ int i, r, sz;
+ pid_t pid = getpid();
+ struct proc_fdinfo *fdinfo_buf = NULL;
+
+ sz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
+ if (sz == 0)
+ return; /* no fds, really? */
+ else if (sz == -1)
+ goto fallback;
+ if ((fdinfo_buf = malloc(sz)) == NULL)
+ goto fallback;
+ r = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdinfo_buf, sz);
+ if (r < 0 || r > sz)
+ goto fallback;
+ for (i = 0; i < r / (int)PROC_PIDLISTFD_SIZE; i++) {
+ if (fdinfo_buf[i].proc_fd >= lowfd)
+ close(fdinfo_buf[i].proc_fd);
+ }
+ free(fdinfo_buf);
+ return;
+ fallback:
+ free(fdinfo_buf);
+ closefrom_fallback(lowfd);
+ return;
+}
+#elif defined(HAVE_DIRFD) && defined(HAVE_PROC_PID)
+void
+closefrom(int lowfd)
+{
+ long fd;
+ char fdpath[PATH_MAX], *endp;
+ struct dirent *dent;
+ DIR *dirp;
+ int len;
+
+ /* Check for a /proc/$$/fd directory. */
+ len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid());
+ if (len > 0 && (size_t)len < sizeof(fdpath) && (dirp = opendir(fdpath))) {
+ while ((dent = readdir(dirp)) != NULL) {
+ fd = strtol(dent->d_name, &endp, 10);
+ if (dent->d_name != endp && *endp == '\0' &&
+ fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp))
+ (void) close((int) fd);
+ }
+ (void) closedir(dirp);
+ return;
+ }
+ /* /proc/$$/fd strategy failed, fall back to brute force closure */
+ closefrom_fallback(lowfd);
+}
+#else
+void
+closefrom(int lowfd)
+{
+ closefrom_fallback(lowfd);
+}
+#endif /* !HAVE_FCNTL_CLOSEM */
+#endif /* HAVE_CLOSEFROM */
diff --git a/compat/daemon-darwin.c b/compat/daemon-darwin.c
new file mode 100644
index 0000000..6440020
--- /dev/null
+++ b/compat/daemon-darwin.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+/*
+ * Copyright (c) 2011-2013, Chris Johnsen <chris_johnsen@pobox.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <sys/types.h>
+
+#include <mach/mach.h>
+
+#include <Availability.h>
+#include <unistd.h>
+
+void daemon_darwin(void);
+
+#ifdef __MAC_10_10
+
+extern kern_return_t bootstrap_look_up_per_user(mach_port_t, const char *,
+ uid_t, mach_port_t *);
+extern kern_return_t bootstrap_get_root(mach_port_t, mach_port_t *);
+
+void
+daemon_darwin(void)
+{
+ mach_port_t root = MACH_PORT_NULL;
+ mach_port_t s = MACH_PORT_NULL;
+ uid_t uid;
+
+ uid = getuid();
+ if (bootstrap_get_root(bootstrap_port, &root) == KERN_SUCCESS &&
+ bootstrap_look_up_per_user(root, NULL, uid, &s) == KERN_SUCCESS &&
+ task_set_bootstrap_port(mach_task_self(), s) == KERN_SUCCESS &&
+ mach_port_deallocate(mach_task_self(), bootstrap_port) == KERN_SUCCESS)
+ bootstrap_port = s;
+}
+
+#else
+
+void
+daemon_darwin(void)
+{
+}
+
+#endif
diff --git a/compat/daemon.c b/compat/daemon.c
new file mode 100644
index 0000000..5d0c9d8
--- /dev/null
+++ b/compat/daemon.c
@@ -0,0 +1,75 @@
+/* $OpenBSD: daemon.c,v 1.6 2005/08/08 08:05:33 espie Exp $ */
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+#ifdef __APPLE__
+extern void daemon_darwin(void);
+#endif
+
+int
+daemon(int nochdir, int noclose)
+{
+ int fd;
+
+ switch (fork()) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ _exit(0);
+ }
+
+ if (setsid() == -1)
+ return (-1);
+
+ if (!nochdir)
+ (void)chdir("/");
+
+ if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
+ (void)dup2(fd, STDIN_FILENO);
+ (void)dup2(fd, STDOUT_FILENO);
+ (void)dup2(fd, STDERR_FILENO);
+ if (fd > 2)
+ (void)close (fd);
+ }
+
+#ifdef __APPLE__
+ daemon_darwin();
+#endif
+ return (0);
+}
diff --git a/compat/err.c b/compat/err.c
new file mode 100644
index 0000000..f470692
--- /dev/null
+++ b/compat/err.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2017 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 <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "compat.h"
+
+void
+err(int eval, const char *fmt, ...)
+{
+ va_list ap;
+ int saved_errno = errno;
+
+ fprintf(stderr, "%s: ", getprogname());
+
+ va_start(ap, fmt);
+ if (fmt != NULL) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, ": ");
+ }
+ va_end(ap);
+
+ fprintf(stderr, "%s\n", strerror(saved_errno));
+ exit(eval);
+}
+
+void
+errx(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", getprogname());
+
+ va_start(ap, fmt);
+ if (fmt != NULL)
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ putc('\n', stderr);
+ exit(eval);
+}
+
+void
+warn(const char *fmt, ...)
+{
+ va_list ap;
+ int saved_errno = errno;
+
+ fprintf(stderr, "%s: ", getprogname());
+
+ va_start(ap, fmt);
+ if (fmt != NULL) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, ": ");
+ }
+ va_end(ap);
+
+ fprintf(stderr, "%s\n", strerror(saved_errno));
+}
+
+void
+warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", getprogname());
+
+ va_start(ap, fmt);
+ if (fmt != NULL)
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ putc('\n', stderr);
+}
diff --git a/compat/explicit_bzero.c b/compat/explicit_bzero.c
new file mode 100644
index 0000000..e8bc5b3
--- /dev/null
+++ b/compat/explicit_bzero.c
@@ -0,0 +1,15 @@
+/* $OpenBSD: explicit_bzero.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */
+/*
+ * Public domain.
+ * Written by Matthew Dempsky.
+ */
+
+#include <string.h>
+
+#include "compat.h"
+
+void
+explicit_bzero(void *buf, size_t len)
+{
+ memset(buf, 0, len);
+}
diff --git a/compat/fdforkpty.c b/compat/fdforkpty.c
new file mode 100644
index 0000000..c05f0db
--- /dev/null
+++ b/compat/fdforkpty.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017 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 <limits.h>
+
+#include "compat.h"
+
+int
+getptmfd(void)
+{
+ return (INT_MAX);
+}
+
+pid_t
+fdforkpty(__unused int ptmfd, int *master, char *name, struct termios *tio,
+ struct winsize *ws)
+{
+ return (forkpty(master, name, tio, ws));
+}
diff --git a/compat/fgetln.c b/compat/fgetln.c
new file mode 100644
index 0000000..c71918c
--- /dev/null
+++ b/compat/fgetln.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015 Joerg Jung <jung@openbsd.org>
+ *
+ * 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 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.
+ */
+
+/*
+ * portable fgetln() version, NOT reentrant
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "compat.h"
+
+char *
+fgetln(FILE *fp, size_t *len)
+{
+ static char *buf = NULL;
+ static size_t bufsz = 0;
+ size_t r = 0;
+ char *p;
+ int c, e;
+
+ if (!fp || !len) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (!buf) {
+ if (!(buf = calloc(1, BUFSIZ)))
+ return NULL;
+ bufsz = BUFSIZ;
+ }
+ while ((c = getc(fp)) != EOF) {
+ buf[r++] = c;
+ if (r == bufsz) {
+ if (!(p = reallocarray(buf, 2, bufsz))) {
+ e = errno;
+ free(buf);
+ errno = e;
+ buf = NULL, bufsz = 0;
+ return NULL;
+ }
+ buf = p, bufsz = 2 * bufsz;
+ }
+ if (c == '\n')
+ break;
+ }
+ return (*len = r) ? buf : NULL;
+}
diff --git a/compat/forkpty-aix.c b/compat/forkpty-aix.c
new file mode 100644
index 0000000..5eba9fd
--- /dev/null
+++ b/compat/forkpty-aix.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stropts.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "compat.h"
+
+void fatal(const char *, ...);
+void fatalx(const char *, ...);
+
+pid_t
+forkpty(int *master, __unused char *name, struct termios *tio,
+ struct winsize *ws)
+{
+ int slave = -1, fd, pipe_fd[2];
+ char *path, dummy;
+ pid_t pid;
+
+ if (pipe(pipe_fd) == -1)
+ return (-1);
+
+ if ((*master = open("/dev/ptc", O_RDWR|O_NOCTTY)) == -1)
+ goto out;
+
+ if ((path = ttyname(*master)) == NULL)
+ goto out;
+
+ if (name != NULL)
+ strlcpy(name, path, TTY_NAME_MAX);
+
+ if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1)
+ goto out;
+
+ switch (pid = fork()) {
+ case -1:
+ goto out;
+ case 0:
+ close(*master);
+
+ close(pipe_fd[1]);
+ while (read(pipe_fd[0], &dummy, 1) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ close(pipe_fd[0]);
+
+ fd = open(_PATH_TTY, O_RDWR|O_NOCTTY);
+ if (fd >= 0) {
+ ioctl(fd, TIOCNOTTY, NULL);
+ close(fd);
+ }
+
+ if (setsid() < 0)
+ fatal("setsid");
+
+ fd = open(_PATH_TTY, O_RDWR|O_NOCTTY);
+ if (fd >= 0)
+ fatalx("open succeeded (failed to disconnect)");
+
+ fd = open(path, O_RDWR);
+ if (fd < 0)
+ fatal("open failed");
+ close(fd);
+
+ fd = open("/dev/tty", O_WRONLY);
+ if (fd < 0)
+ fatal("open failed");
+ close(fd);
+
+ if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1)
+ fatal("tcsetattr failed");
+ if (ioctl(slave, TIOCSWINSZ, ws) == -1)
+ fatal("ioctl failed");
+
+ dup2(slave, 0);
+ dup2(slave, 1);
+ dup2(slave, 2);
+ if (slave > 2)
+ close(slave);
+
+ return (0);
+ }
+
+ close(slave);
+
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ return (pid);
+
+out:
+ if (*master != -1)
+ close(*master);
+ if (slave != -1)
+ close(slave);
+
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ return (-1);
+}
diff --git a/compat/forkpty-haiku.c b/compat/forkpty-haiku.c
new file mode 100644
index 0000000..6112164
--- /dev/null
+++ b/compat/forkpty-haiku.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+void fatal(const char *, ...);
+void fatalx(const char *, ...);
+
+pid_t
+forkpty(int *master, char *name, struct termios *tio, struct winsize *ws)
+{
+ int slave = -1;
+ char *path;
+ pid_t pid;
+
+ if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1)
+ return (-1);
+ if (grantpt(*master) != 0)
+ goto out;
+ if (unlockpt(*master) != 0)
+ goto out;
+
+ if ((path = ptsname(*master)) == NULL)
+ goto out;
+ if (name != NULL)
+ strlcpy(name, path, TTY_NAME_MAX);
+ if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1)
+ goto out;
+
+ switch (pid = fork()) {
+ case -1:
+ goto out;
+ case 0:
+ close(*master);
+
+ setsid();
+ if (ioctl(slave, TIOCSCTTY, NULL) == -1)
+ fatal("ioctl failed");
+
+ if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1)
+ fatal("tcsetattr failed");
+ if (ioctl(slave, TIOCSWINSZ, ws) == -1)
+ fatal("ioctl failed");
+
+ dup2(slave, 0);
+ dup2(slave, 1);
+ dup2(slave, 2);
+ if (slave > 2)
+ close(slave);
+ return (0);
+ }
+
+ close(slave);
+ return (pid);
+
+out:
+ if (*master != -1)
+ close(*master);
+ if (slave != -1)
+ close(slave);
+ return (-1);
+}
diff --git a/compat/forkpty-hpux.c b/compat/forkpty-hpux.c
new file mode 100644
index 0000000..64494f6
--- /dev/null
+++ b/compat/forkpty-hpux.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stropts.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+void fatal(const char *, ...);
+void fatalx(const char *, ...);
+
+pid_t
+forkpty(int *master, char *name, struct termios *tio, struct winsize *ws)
+{
+ int slave = -1;
+ char *path;
+ pid_t pid;
+
+ if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1)
+ return (-1);
+ if (grantpt(*master) != 0)
+ goto out;
+ if (unlockpt(*master) != 0)
+ goto out;
+
+ if ((path = ptsname(*master)) == NULL)
+ goto out;
+ if (name != NULL)
+ strlcpy(name, path, TTY_NAME_MAX);
+ if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1)
+ goto out;
+
+ switch (pid = fork()) {
+ case -1:
+ goto out;
+ case 0:
+ close(*master);
+
+ setsid();
+#ifdef TIOCSCTTY
+ if (ioctl(slave, TIOCSCTTY, NULL) == -1)
+ fatal("ioctl failed");
+#endif
+
+ if (ioctl(slave, I_PUSH, "ptem") == -1)
+ fatal("ioctl failed");
+ if (ioctl(slave, I_PUSH, "ldterm") == -1)
+ fatal("ioctl failed");
+
+ if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1)
+ fatal("tcsetattr failed");
+ if (ioctl(slave, TIOCSWINSZ, ws) == -1)
+ fatal("ioctl failed");
+
+ dup2(slave, 0);
+ dup2(slave, 1);
+ dup2(slave, 2);
+ if (slave > 2)
+ close(slave);
+ return (0);
+ }
+
+ close(slave);
+ return (pid);
+
+out:
+ if (*master != -1)
+ close(*master);
+ if (slave != -1)
+ close(slave);
+ return (-1);
+}
diff --git a/compat/forkpty-sunos.c b/compat/forkpty-sunos.c
new file mode 100644
index 0000000..18bba3b
--- /dev/null
+++ b/compat/forkpty-sunos.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <stropts.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+void fatal(const char *, ...);
+void fatalx(const char *, ...);
+
+pid_t
+forkpty(int *master, char *name, struct termios *tio, struct winsize *ws)
+{
+ int slave = -1;
+ char *path;
+ pid_t pid;
+
+ if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1)
+ return (-1);
+ if (grantpt(*master) != 0)
+ goto out;
+ if (unlockpt(*master) != 0)
+ goto out;
+
+ if ((path = ptsname(*master)) == NULL)
+ goto out;
+ if (name != NULL)
+ strlcpy(name, path, TTY_NAME_MAX);
+ if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1)
+ goto out;
+
+ switch (pid = fork()) {
+ case -1:
+ goto out;
+ case 0:
+ close(*master);
+
+ setsid();
+#ifdef TIOCSCTTY
+ if (ioctl(slave, TIOCSCTTY, NULL) == -1)
+ fatal("ioctl failed");
+#endif
+
+ if (ioctl(slave, I_PUSH, "ptem") == -1)
+ fatal("ioctl failed");
+ if (ioctl(slave, I_PUSH, "ldterm") == -1)
+ fatal("ioctl failed");
+
+ if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1)
+ fatal("tcsetattr failed");
+ if (ioctl(slave, TIOCSWINSZ, ws) == -1)
+ fatal("ioctl failed");
+
+ dup2(slave, 0);
+ dup2(slave, 1);
+ dup2(slave, 2);
+ if (slave > 2)
+ close(slave);
+ return (0);
+ }
+
+ close(slave);
+ return (pid);
+
+out:
+ if (*master != -1)
+ close(*master);
+ if (slave != -1)
+ close(slave);
+ return (-1);
+}
diff --git a/compat/freezero.c b/compat/freezero.c
new file mode 100644
index 0000000..711a10c
--- /dev/null
+++ b/compat/freezero.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017 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 "compat.h"
+
+void
+freezero(void *ptr, size_t size)
+{
+ if (ptr != NULL) {
+ memset(ptr, 0, size);
+ free(ptr);
+ }
+}
diff --git a/compat/getdtablecount.c b/compat/getdtablecount.c
new file mode 100644
index 0000000..1f9a0aa
--- /dev/null
+++ b/compat/getdtablecount.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2017 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 <glob.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+void fatal(const char *, ...);
+void fatalx(const char *, ...);
+
+#ifdef HAVE_PROC_PID
+int
+getdtablecount(void)
+{
+ char path[PATH_MAX];
+ glob_t g;
+ int n = 0;
+
+ if (snprintf(path, sizeof path, "/proc/%ld/fd/*", (long)getpid()) < 0)
+ fatal("snprintf overflow");
+ if (glob(path, 0, NULL, &g) == 0)
+ n = g.gl_pathc;
+ globfree(&g);
+ return (n);
+}
+#else
+int
+getdtablecount(void)
+{
+ return (0);
+}
+#endif
diff --git a/compat/getdtablesize.c b/compat/getdtablesize.c
new file mode 100644
index 0000000..fa82577
--- /dev/null
+++ b/compat/getdtablesize.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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 <unistd.h>
+
+#include "compat.h"
+
+#ifdef HAVE_SYSCONF
+int
+getdtablesize(void)
+{
+ return (sysconf(_SC_OPEN_MAX));
+}
+#endif
diff --git a/compat/getline.c b/compat/getline.c
new file mode 100644
index 0000000..90437cc
--- /dev/null
+++ b/compat/getline.c
@@ -0,0 +1,93 @@
+/* $NetBSD: getline.c,v 1.1.1.6 2015/01/02 20:34:27 christos Exp $ */
+
+/* NetBSD: getline.c,v 1.2 2014/09/16 17:23:50 christos Exp */
+
+/*-
+ * Copyright (c) 2011 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* NETBSD ORIGINAL: external/bsd/file/dist/src/getline.c */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static ssize_t
+getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
+{
+ char *ptr, *eptr;
+
+
+ if (*buf == NULL || *bufsiz == 0) {
+ if ((*buf = malloc(BUFSIZ)) == NULL)
+ return -1;
+ *bufsiz = BUFSIZ;
+ }
+
+ for (ptr = *buf, eptr = *buf + *bufsiz;;) {
+ int c = fgetc(fp);
+ if (c == -1) {
+ if (feof(fp)) {
+ ssize_t diff = (ssize_t)(ptr - *buf);
+ if (diff != 0) {
+ *ptr = '\0';
+ return diff;
+ }
+ }
+ return -1;
+ }
+ *ptr++ = c;
+ if (c == delimiter) {
+ *ptr = '\0';
+ return ptr - *buf;
+ }
+ if (ptr + 2 >= eptr) {
+ char *nbuf;
+ size_t nbufsiz = *bufsiz * 2;
+ ssize_t d = ptr - *buf;
+ if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
+ return -1;
+ *buf = nbuf;
+ *bufsiz = nbufsiz;
+ eptr = nbuf + nbufsiz;
+ ptr = nbuf + d;
+ }
+ }
+}
+
+ssize_t
+getline(char **buf, size_t *bufsiz, FILE *fp)
+{
+ return getdelim(buf, bufsiz, '\n', fp);
+}
diff --git a/compat/getopt.c b/compat/getopt.c
new file mode 100644
index 0000000..26e0e6d
--- /dev/null
+++ b/compat/getopt.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/getopt.c */
+
+#include "compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int BSDopterr = 1, /* if error message should be printed */
+ BSDoptind = 1, /* index into parent argv vector */
+ BSDoptopt, /* character checked for validity */
+ BSDoptreset; /* reset getopt */
+char *BSDoptarg; /* argument associated with option */
+
+#define BADCH (int)'?'
+#define BADARG (int)':'
+#define EMSG ""
+
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+BSDgetopt(int nargc, char *const *nargv, const char *ostr)
+{
+ static const char *place = EMSG; /* option letter processing */
+ char *oli; /* option letter list index */
+
+ if (ostr == NULL)
+ return (-1);
+
+ if (BSDoptreset || !*place) { /* update scanning pointer */
+ BSDoptreset = 0;
+ if (BSDoptind >= nargc || *(place = nargv[BSDoptind]) != '-') {
+ place = EMSG;
+ return (-1);
+ }
+ if (place[1] && *++place == '-') { /* found "--" */
+ if (place[1])
+ return (BADCH);
+ ++BSDoptind;
+ place = EMSG;
+ return (-1);
+ }
+ } /* option letter okay? */
+ if ((BSDoptopt = (int)*place++) == (int)':' ||
+ !(oli = strchr(ostr, BSDoptopt))) {
+ /*
+ * if the user didn't specify '-' as an option,
+ * assume it means -1.
+ */
+ if (BSDoptopt == (int)'-')
+ return (-1);
+ if (!*place)
+ ++BSDoptind;
+ if (BSDopterr && *ostr != ':')
+ (void)fprintf(stderr,
+ "%s: unknown option -- %c\n", getprogname(),
+ BSDoptopt);
+ return (BADCH);
+ }
+ if (*++oli != ':') { /* don't need argument */
+ BSDoptarg = NULL;
+ if (!*place)
+ ++BSDoptind;
+ }
+ else { /* need an argument */
+ if (*place) /* no white space */
+ BSDoptarg = (char *)place;
+ else if (nargc <= ++BSDoptind) { /* no arg */
+ place = EMSG;
+ if (*ostr == ':')
+ return (BADARG);
+ if (BSDopterr)
+ (void)fprintf(stderr,
+ "%s: option requires an argument -- %c\n",
+ getprogname(), BSDoptopt);
+ return (BADCH);
+ }
+ else /* white space */
+ BSDoptarg = nargv[BSDoptind];
+ place = EMSG;
+ ++BSDoptind;
+ }
+ return (BSDoptopt); /* dump back option letter */
+}
diff --git a/compat/getpeereid.c b/compat/getpeereid.c
new file mode 100644
index 0000000..c194e88
--- /dev/null
+++ b/compat/getpeereid.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <stdio.h>
+
+#ifdef HAVE_UCRED_H
+#include <ucred.h>
+#endif
+
+#include "compat.h"
+
+int
+getpeereid(int s, uid_t *uid, gid_t *gid)
+{
+#ifdef HAVE_SO_PEERCRED
+ struct ucred uc;
+ int len = sizeof uc;
+
+ if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &uc, &len) == -1)
+ return (-1);
+ *uid = uc.uid;
+ *gid = uc.gid;
+ return (0);
+#elif defined(HAVE_GETPEERUCRED)
+ ucred_t *ucred = NULL;
+
+ if (getpeerucred(s, &ucred) == -1)
+ return (-1);
+ if ((*uid = ucred_geteuid(ucred)) == -1)
+ return (-1);
+ if ((*gid = ucred_getrgid(ucred)) == -1)
+ return (-1);
+ ucred_free(ucred);
+ return (0);
+#else
+ return (getuid());
+#endif
+}
diff --git a/compat/getprogname.c b/compat/getprogname.c
new file mode 100644
index 0000000..50afbac
--- /dev/null
+++ b/compat/getprogname.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2016 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 <errno.h>
+
+#include "compat.h"
+
+#if defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME)
+const char *
+getprogname(void)
+{
+ return (program_invocation_short_name);
+}
+#elif defined(HAVE___PROGNAME)
+const char *
+getprogname(void)
+{
+ extern char *__progname;
+
+ return (__progname);
+}
+#else
+const char *
+getprogname(void)
+{
+ return ("tmux");
+}
+#endif
diff --git a/compat/imsg-buffer.c b/compat/imsg-buffer.c
new file mode 100644
index 0000000..67d4c70
--- /dev/null
+++ b/compat/imsg-buffer.c
@@ -0,0 +1,309 @@
+/* $OpenBSD: imsg-buffer.c,v 1.12 2019/01/20 02:50:03 bcook Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "compat.h"
+#include "imsg.h"
+
+static int ibuf_realloc(struct ibuf *, size_t);
+static void ibuf_enqueue(struct msgbuf *, struct ibuf *);
+static void ibuf_dequeue(struct msgbuf *, struct ibuf *);
+
+struct ibuf *
+ibuf_open(size_t len)
+{
+ struct ibuf *buf;
+
+ if ((buf = calloc(1, sizeof(struct ibuf))) == NULL)
+ return (NULL);
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf->size = buf->max = len;
+ buf->fd = -1;
+
+ return (buf);
+}
+
+struct ibuf *
+ibuf_dynamic(size_t len, size_t max)
+{
+ struct ibuf *buf;
+
+ if (max < len)
+ return (NULL);
+
+ if ((buf = ibuf_open(len)) == NULL)
+ return (NULL);
+
+ if (max > 0)
+ buf->max = max;
+
+ return (buf);
+}
+
+static int
+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) {
+ errno = ERANGE;
+ return (-1);
+ }
+
+ b = recallocarray(buf->buf, buf->size, buf->wpos + len, 1);
+ if (b == NULL)
+ return (-1);
+ buf->buf = b;
+ buf->size = buf->wpos + 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 (buf->wpos + len > buf->size)
+ if (ibuf_realloc(buf, len) == -1)
+ return (NULL);
+
+ b = buf->buf + buf->wpos;
+ buf->wpos += len;
+ return (b);
+}
+
+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)
+ return (NULL);
+
+ return (buf->buf + pos);
+}
+
+size_t
+ibuf_size(struct ibuf *buf)
+{
+ return (buf->wpos);
+}
+
+size_t
+ibuf_left(struct ibuf *buf)
+{
+ return (buf->max - buf->wpos);
+}
+
+void
+ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+ ibuf_enqueue(msgbuf, buf);
+}
+
+int
+ibuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct ibuf *buf;
+ unsigned int i = 0;
+ ssize_t n;
+
+ memset(&iov, 0, sizeof(iov));
+ 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;
+ i++;
+ }
+
+again:
+ if ((n = writev(msgbuf->fd, iov, i)) == -1) {
+ if (errno == EINTR)
+ goto again;
+ if (errno == ENOBUFS)
+ errno = EAGAIN;
+ return (-1);
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (0);
+ }
+
+ msgbuf_drain(msgbuf, n);
+
+ return (1);
+}
+
+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;
+ msgbuf->fd = -1;
+ TAILQ_INIT(&msgbuf->bufs);
+}
+
+void
+msgbuf_drain(struct msgbuf *msgbuf, size_t n)
+{
+ struct ibuf *buf, *next;
+
+ 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;
+ ibuf_dequeue(msgbuf, buf);
+ } else {
+ buf->rpos += n;
+ n = 0;
+ }
+ }
+}
+
+void
+msgbuf_clear(struct msgbuf *msgbuf)
+{
+ struct ibuf *buf;
+
+ while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
+ ibuf_dequeue(msgbuf, buf);
+}
+
+int
+msgbuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct ibuf *buf;
+ unsigned int i = 0;
+ ssize_t n;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+ } cmsgbuf;
+
+ memset(&iov, 0, sizeof(iov));
+ memset(&msg, 0, sizeof(msg));
+ memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+ 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;
+ i++;
+ if (buf->fd != -1)
+ break;
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ if (buf != NULL && buf->fd != -1) {
+ 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;
+ }
+
+again:
+ if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+ if (errno == EINTR)
+ goto again;
+ if (errno == ENOBUFS)
+ errno = EAGAIN;
+ return (-1);
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (0);
+ }
+
+ /*
+ * 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;
+ }
+
+ msgbuf_drain(msgbuf, n);
+
+ return (1);
+}
+
+static void
+ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+ msgbuf->queued++;
+}
+
+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
new file mode 100644
index 0000000..54ac7e5
--- /dev/null
+++ b/compat/imsg.c
@@ -0,0 +1,302 @@
+/* $OpenBSD: imsg.c,v 1.16 2017/12/14 09:27:44 kettenis Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "compat.h"
+#include "imsg.h"
+
+int imsg_fd_overhead = 0;
+
+static int imsg_get_fd(struct imsgbuf *);
+
+void
+imsg_init(struct imsgbuf *ibuf, 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);
+}
+
+ssize_t
+imsg_read(struct imsgbuf *ibuf)
+{
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int) * 1)];
+ } cmsgbuf;
+ struct iovec iov;
+ ssize_t n = -1;
+ int fd;
+ struct imsg_fd *ifd;
+
+ 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;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+ if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL)
+ return (-1);
+
+again:
+ if (getdtablecount() + imsg_fd_overhead +
+ (int)((CMSG_SPACE(sizeof(int))-CMSG_SPACE(0))/sizeof(int))
+ >= getdtablesize()) {
+ errno = EAGAIN;
+ free(ifd);
+ return (-1);
+ }
+
+ if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) {
+ if (errno == EINTR)
+ goto again;
+ goto fail;
+ }
+
+ ibuf->r.wpos += n;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ int i;
+ int j;
+
+ /*
+ * We only accept one file descriptor. Due to C
+ * padding rules, our control buffer might contain
+ * more than one fd, and we must close them.
+ */
+ j = ((char *)cmsg + cmsg->cmsg_len -
+ (char *)CMSG_DATA(cmsg)) / sizeof(int);
+ for (i = 0; i < j; i++) {
+ fd = ((int *)CMSG_DATA(cmsg))[i];
+ if (ifd != NULL) {
+ ifd->fd = fd;
+ TAILQ_INSERT_TAIL(&ibuf->fds, ifd,
+ entry);
+ ifd = NULL;
+ } else
+ close(fd);
+ }
+ }
+ /* we do not handle other ctl data level */
+ }
+
+fail:
+ free(ifd);
+ return (n);
+}
+
+ssize_t
+imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ size_t av, left, datalen;
+
+ av = ibuf->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) {
+ errno = ERANGE;
+ return (-1);
+ }
+ if (imsg->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;
+
+ memcpy(imsg->data, ibuf->r.rptr, datalen);
+
+ 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;
+ } else
+ ibuf->r.wpos = 0;
+
+ 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)
+{
+ struct ibuf *wbuf;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return (-1);
+
+ if (imsg_add(wbuf, data, datalen) == -1)
+ return (-1);
+
+ wbuf->fd = fd;
+
+ imsg_close(ibuf, wbuf);
+
+ return (1);
+}
+
+int
+imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid,
+ int fd, const struct iovec *iov, int iovcnt)
+{
+ struct ibuf *wbuf;
+ int i, datalen = 0;
+
+ for (i = 0; i < iovcnt; i++)
+ datalen += iov[i].iov_len;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, 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;
+
+ imsg_close(ibuf, wbuf);
+
+ return (1);
+}
+
+/* ARGSUSED */
+struct ibuf *
+imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid,
+ uint16_t datalen)
+{
+ struct ibuf *wbuf;
+ struct imsg_hdr hdr;
+
+ datalen += IMSG_HEADER_SIZE;
+ if (datalen > MAX_IMSGSIZE) {
+ errno = ERANGE;
+ return (NULL);
+ }
+
+ hdr.type = type;
+ hdr.flags = 0;
+ hdr.peerid = peerid;
+ if ((hdr.pid = pid) == 0)
+ hdr.pid = ibuf->pid;
+ if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) {
+ return (NULL);
+ }
+ if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+ return (NULL);
+
+ return (wbuf);
+}
+
+int
+imsg_add(struct ibuf *msg, const void *data, uint16_t datalen)
+{
+ if (datalen)
+ if (ibuf_add(msg, data, datalen) == -1) {
+ ibuf_free(msg);
+ return (-1);
+ }
+ return (datalen);
+}
+
+void
+imsg_close(struct imsgbuf *ibuf, struct ibuf *msg)
+{
+ struct imsg_hdr *hdr;
+
+ hdr = (struct imsg_hdr *)msg->buf;
+
+ hdr->flags &= ~IMSGF_HASFD;
+ if (msg->fd != -1)
+ hdr->flags |= IMSGF_HASFD;
+
+ hdr->len = (uint16_t)msg->wpos;
+
+ ibuf_close(&ibuf->w, msg);
+}
+
+void
+imsg_free(struct imsg *imsg)
+{
+ freezero(imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE);
+}
+
+static int
+imsg_get_fd(struct imsgbuf *ibuf)
+{
+ int fd;
+ struct imsg_fd *ifd;
+
+ if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL)
+ return (-1);
+
+ fd = ifd->fd;
+ TAILQ_REMOVE(&ibuf->fds, ifd, entry);
+ free(ifd);
+
+ return (fd);
+}
+
+int
+imsg_flush(struct imsgbuf *ibuf)
+{
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) <= 0)
+ return (-1);
+ return (0);
+}
+
+void
+imsg_clear(struct imsgbuf *ibuf)
+{
+ int fd;
+
+ msgbuf_clear(&ibuf->w);
+ while ((fd = imsg_get_fd(ibuf)) != -1)
+ close(fd);
+}
diff --git a/compat/imsg.h b/compat/imsg.h
new file mode 100644
index 0000000..5b092cf
--- /dev/null
+++ b/compat/imsg.h
@@ -0,0 +1,113 @@
+/* $OpenBSD: imsg.h,v 1.5 2019/01/20 02:50:03 bcook Exp $ */
+
+/*
+ * 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>
+ *
+ * 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 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.
+ */
+
+#ifndef _IMSG_H_
+#define _IMSG_H_
+
+#include <stdint.h>
+
+#define IBUF_READ_SIZE 65535
+#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE 16384
+
+struct ibuf {
+ TAILQ_ENTRY(ibuf) entry;
+ unsigned char *buf;
+ size_t size;
+ size_t max;
+ size_t wpos;
+ size_t rpos;
+ int fd;
+};
+
+struct msgbuf {
+ TAILQ_HEAD(, ibuf) bufs;
+ uint32_t queued;
+ int fd;
+};
+
+struct ibuf_read {
+ unsigned char buf[IBUF_READ_SIZE];
+ unsigned char *rptr;
+ size_t wpos;
+};
+
+struct imsg_fd {
+ TAILQ_ENTRY(imsg_fd) entry;
+ int fd;
+};
+
+struct imsgbuf {
+ TAILQ_HEAD(, imsg_fd) fds;
+ struct ibuf_read r;
+ struct msgbuf w;
+ int fd;
+ pid_t pid;
+};
+
+#define IMSGF_HASFD 1
+
+struct imsg_hdr {
+ uint32_t type;
+ uint16_t len;
+ uint16_t flags;
+ uint32_t peerid;
+ uint32_t pid;
+};
+
+struct imsg {
+ struct imsg_hdr hdr;
+ int fd;
+ void *data;
+};
+
+
+/* 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);
+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 *);
+void ibuf_close(struct msgbuf *, struct ibuf *);
+int ibuf_write(struct msgbuf *);
+void ibuf_free(struct ibuf *);
+void msgbuf_init(struct msgbuf *);
+void msgbuf_clear(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_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int,
+ const void *, uint16_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);
+void imsg_close(struct imsgbuf *, struct ibuf *);
+void imsg_free(struct imsg *);
+int imsg_flush(struct imsgbuf *);
+void imsg_clear(struct imsgbuf *);
+
+#endif
diff --git a/compat/memmem.c b/compat/memmem.c
new file mode 100644
index 0000000..99e3f55
--- /dev/null
+++ b/compat/memmem.c
@@ -0,0 +1,65 @@
+/* $OpenBSD: memmem.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */
+/*-
+ * Copyright (c) 2005 Pascal Gloor <pascal.gloor@spale.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "compat.h"
+
+/*
+ * Find the first occurrence of the byte string s in byte string l.
+ */
+
+void *
+memmem(const void *l, size_t l_len, const void *s, size_t s_len)
+{
+ const char *cur, *last;
+ const char *cl = l;
+ const char *cs = s;
+
+ /* a zero length needle should just return the haystack */
+ if (s_len == 0)
+ return (void *)cl;
+
+ /* "s" must be smaller or equal to "l" */
+ if (l_len < s_len)
+ return NULL;
+
+ /* special case where s_len == 1 */
+ if (s_len == 1)
+ return memchr(l, *cs, l_len);
+
+ /* the last position where its possible to find "s" in "l" */
+ last = cl + l_len - s_len;
+
+ for (cur = cl; cur <= last; cur++)
+ if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
+ return (void *)cur;
+
+ return NULL;
+}
diff --git a/compat/queue.h b/compat/queue.h
new file mode 100644
index 0000000..a20532c
--- /dev/null
+++ b/compat/queue.h
@@ -0,0 +1,533 @@
+/* $OpenBSD: queue.h,v 1.44 2016/09/09 20:31:46 millert Exp $ */
+/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues and XOR simple queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * An XOR simple queue is used in the same way as a regular simple queue.
+ * The difference is that the head structure also includes a "cookie" that
+ * is XOR'd with the queue pointer (first, last or next) to generate the
+ * real pointer value.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define SLIST_FIRST(head) ((head)->slh_first)
+#define SLIST_END(head) NULL
+#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_FOREACH(var, head, field) \
+ for((var) = SLIST_FIRST(head); \
+ (var) != SLIST_END(head); \
+ (var) = SLIST_NEXT(var, field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST(head); \
+ (var) && ((tvar) = SLIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) { \
+ SLIST_FIRST(head) = SLIST_END(head); \
+}
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ (elm)->field.sle_next = (slistelm)->field.sle_next; \
+ (slistelm)->field.sle_next = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.sle_next = (head)->slh_first; \
+ (head)->slh_first = (elm); \
+} while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do { \
+ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ (head)->slh_first = (head)->slh_first->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if ((head)->slh_first == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->slh_first; \
+ \
+ while (curelm->field.sle_next != (elm)) \
+ curelm = curelm->field.sle_next; \
+ curelm->field.sle_next = \
+ curelm->field.sle_next->field.sle_next; \
+ } \
+ _Q_INVALIDATE((elm)->field.sle_next); \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List access methods.
+ */
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_END(head) NULL
+#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field) \
+ for((var) = LIST_FIRST(head); \
+ (var)!= LIST_END(head); \
+ (var) = LIST_NEXT(var, field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST(head); \
+ (var) && ((tvar) = LIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) do { \
+ LIST_FIRST(head) = LIST_END(head); \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do { \
+ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
+ (elm2)->field.le_next->field.le_prev = \
+ &(elm2)->field.le_next; \
+ (elm2)->field.le_prev = (elm)->field.le_prev; \
+ *(elm2)->field.le_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqh_first; /* first element */ \
+ struct type **sqh_last; /* addr of last next element */ \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqe_next; /* next element */ \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for((var) = SIMPLEQ_FIRST(head); \
+ (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+
+#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define SIMPLEQ_INIT(head) do { \
+ (head)->sqh_first = NULL; \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (head)->sqh_first = (elm); \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqe_next = NULL; \
+ *(head)->sqh_last = (elm); \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (listelm)->field.sqe_next = (elm); \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+#define SIMPLEQ_CONCAT(head1, head2) do { \
+ if (!SIMPLEQ_EMPTY((head2))) { \
+ *(head1)->sqh_last = (head2)->sqh_first; \
+ (head1)->sqh_last = (head2)->sqh_last; \
+ SIMPLEQ_INIT((head2)); \
+ } \
+} while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqx_first; /* first element */ \
+ struct type **sqx_last; /* addr of last next element */ \
+ unsigned long sqx_cookie; \
+}
+
+#define XSIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqx_next; /* next element */ \
+}
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \
+ (unsigned long)(ptr)))
+#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define XSIMPLEQ_END(head) NULL
+#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+
+#define XSIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) != XSIMPLEQ_END(head); \
+ (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define XSIMPLEQ_INIT(head) do { \
+ arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqx_next = (head)->sqx_first) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \
+ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqx_first = XSIMPLEQ_XOR(head, \
+ (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \
+ (elm)->field.sqx_next)->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+
+/*
+ * Tail queue access methods.
+ */
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_END(head) NULL
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define TAILQ_EMPTY(head) \
+ (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field) \
+ for((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_NEXT(var, field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_PREV(var, headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
+ (elm2)->field.tqe_next->field.tqe_prev = \
+ &(elm2)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm2)->field.tqe_next; \
+ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
+ *(elm2)->field.tqe_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ } \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/compat/reallocarray.c b/compat/reallocarray.c
new file mode 100644
index 0000000..ba17e3f
--- /dev/null
+++ b/compat/reallocarray.c
@@ -0,0 +1,40 @@
+/* $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * 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 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 <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(optr, size * nmemb);
+}
diff --git a/compat/recallocarray.c b/compat/recallocarray.c
new file mode 100644
index 0000000..74a919a
--- /dev/null
+++ b/compat/recallocarray.c
@@ -0,0 +1,82 @@
+/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */
+/*
+ * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net>
+ *
+ * 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 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 <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
+{
+ size_t oldsize, newsize;
+ void *newptr;
+
+ if (ptr == NULL)
+ return calloc(newnmemb, size);
+
+ if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ newnmemb > 0 && SIZE_MAX / newnmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ newsize = newnmemb * size;
+
+ if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
+ errno = EINVAL;
+ return NULL;
+ }
+ oldsize = oldnmemb * size;
+
+ /*
+ * Don't bother too much if we're shrinking just a bit,
+ * we do not shrink for series of small steps, oh well.
+ */
+ if (newsize <= oldsize) {
+ size_t d = oldsize - newsize;
+
+ if (d < oldsize / 2 && d < (size_t)getpagesize()) {
+ memset((char *)ptr + newsize, 0, d);
+ return ptr;
+ }
+ }
+
+ newptr = malloc(newsize);
+ if (newptr == NULL)
+ return NULL;
+
+ if (newsize > oldsize) {
+ memcpy(newptr, ptr, oldsize);
+ memset((char *)newptr + oldsize, 0, newsize - oldsize);
+ } else
+ memcpy(newptr, ptr, newsize);
+
+ explicit_bzero(ptr, oldsize);
+ free(ptr);
+
+ return newptr;
+}
diff --git a/compat/setenv.c b/compat/setenv.c
new file mode 100644
index 0000000..d9235bc
--- /dev/null
+++ b/compat/setenv.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010 Dagobert Michelsen
+ * Copyright (c) 2010 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 "compat.h"
+
+int
+setenv(const char *name, const char *value, __unused int overwrite)
+{
+ char *newval;
+
+ xasprintf(&newval, "%s=%s", name, value);
+ return (putenv(newval));
+}
+
+int
+unsetenv(const char *name)
+{
+ char **envptr;
+ int namelen;
+
+ namelen = strlen(name);
+ for (envptr = environ; *envptr != NULL; envptr++) {
+ if (strncmp(name, *envptr, namelen) == 0 &&
+ ((*envptr)[namelen] == '=' || (*envptr)[namelen] == '\0'))
+ break;
+ }
+ for (; *envptr != NULL; envptr++)
+ *envptr = *(envptr + 1);
+ return (0);
+}
diff --git a/compat/setproctitle.c b/compat/setproctitle.c
new file mode 100644
index 0000000..e72ae27
--- /dev/null
+++ b/compat/setproctitle.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016 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 <stdarg.h>
+#include <string.h>
+
+#include "compat.h"
+
+#if defined(HAVE_PRCTL) && defined(HAVE_PR_SET_NAME)
+
+#include <sys/prctl.h>
+
+void
+setproctitle(const char *fmt, ...)
+{
+ char title[16], name[16], *cp;
+ va_list ap;
+ int used;
+
+ va_start(ap, fmt);
+ vsnprintf(title, sizeof title, fmt, ap);
+ va_end(ap);
+
+ used = snprintf(name, sizeof name, "%s: %s", getprogname(), title);
+ if (used >= (int)sizeof name) {
+ cp = strrchr(name, ' ');
+ if (cp != NULL)
+ *cp = '\0';
+ }
+ prctl(PR_SET_NAME, name);
+}
+#else
+void
+setproctitle(__unused const char *fmt, ...)
+{
+}
+#endif
diff --git a/compat/strcasestr.c b/compat/strcasestr.c
new file mode 100644
index 0000000..8679cf8
--- /dev/null
+++ b/compat/strcasestr.c
@@ -0,0 +1,62 @@
+/* $OpenBSD: strcasestr.c,v 1.3 2006/03/31 05:34:55 deraadt Exp $ */
+/* $NetBSD: strcasestr.c,v 1.2 2005/02/09 21:35:47 kleink Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include "compat.h"
+
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
+char *
+strcasestr(const char *s, const char *find)
+{
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != 0) {
+ c = (char)tolower((unsigned char)c);
+ len = strlen(find);
+ do {
+ do {
+ if ((sc = *s++) == 0)
+ return (NULL);
+ } while ((char)tolower((unsigned char)sc) != c);
+ } while (strncasecmp(s, find, len) != 0);
+ s--;
+ }
+ return ((char *)s);
+}
diff --git a/compat/strlcat.c b/compat/strlcat.c
new file mode 100644
index 0000000..836e392
--- /dev/null
+++ b/compat/strlcat.c
@@ -0,0 +1,57 @@
+/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.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 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 <string.h>
+
+#include "compat.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/compat/strlcpy.c b/compat/strlcpy.c
new file mode 100644
index 0000000..46a2224
--- /dev/null
+++ b/compat/strlcpy.c
@@ -0,0 +1,53 @@
+/* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.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 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 <string.h>
+
+#include "compat.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/compat/strndup.c b/compat/strndup.c
new file mode 100644
index 0000000..32bec6a
--- /dev/null
+++ b/compat/strndup.c
@@ -0,0 +1,41 @@
+/* $OpenBSD: strndup.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */
+
+/*
+ * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.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 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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "compat.h"
+
+char *
+strndup(const char *str, size_t maxlen)
+{
+ char *copy;
+ size_t len;
+
+ len = strnlen(str, maxlen);
+ copy = malloc(len + 1);
+ if (copy != NULL) {
+ (void)memcpy(copy, str, len);
+ copy[len] = '\0';
+ }
+
+ return copy;
+}
diff --git a/compat/strnlen.c b/compat/strnlen.c
new file mode 100644
index 0000000..eeeb833
--- /dev/null
+++ b/compat/strnlen.c
@@ -0,0 +1,34 @@
+/* $OpenBSD: strnlen.c,v 1.8 2016/10/16 17:37:39 dtucker Exp $ */
+
+/*
+ * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.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 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 <string.h>
+
+#include "compat.h"
+
+size_t
+strnlen(const char *str, size_t maxlen)
+{
+ const char *cp;
+
+ for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--)
+ ;
+
+ return (size_t)(cp - str);
+}
diff --git a/compat/strsep.c b/compat/strsep.c
new file mode 100644
index 0000000..5858458
--- /dev/null
+++ b/compat/strsep.c
@@ -0,0 +1,73 @@
+/* $OpenBSD: strsep.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "compat.h"
+
+/*
+ * Get next token from string *stringp, where tokens are possibly-empty
+ * strings separated by characters from delim.
+ *
+ * Writes NULs into the string at *stringp to end tokens.
+ * delim need not remain constant from call to call.
+ * On return, *stringp points past the last NUL written (if there might
+ * be further tokens), or is NULL (if there are definitely no more tokens).
+ *
+ * If *stringp is NULL, strsep returns NULL.
+ */
+char *
+strsep(char **stringp, const char *delim)
+{
+ char *s;
+ const char *spanp;
+ int c, sc;
+ char *tok;
+
+ if ((s = *stringp) == NULL)
+ return (NULL);
+ for (tok = s;;) {
+ c = *s++;
+ spanp = delim;
+ do {
+ if ((sc = *spanp++) == c) {
+ if (c == 0)
+ s = NULL;
+ else
+ s[-1] = 0;
+ *stringp = s;
+ return (tok);
+ }
+ } while (sc != 0);
+ }
+ /* NOTREACHED */
+}
diff --git a/compat/strtonum.c b/compat/strtonum.c
new file mode 100644
index 0000000..25a5861
--- /dev/null
+++ b/compat/strtonum.c
@@ -0,0 +1,67 @@
+/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * 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 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 <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+#define INVALID 1
+#define TOOSMALL 2
+#define TOOLARGE 3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+ const char **errstrp)
+{
+ long long ll = 0;
+ char *ep;
+ int error = 0;
+ struct errval {
+ const char *errstr;
+ int err;
+ } ev[4] = {
+ { NULL, 0 },
+ { "invalid", EINVAL },
+ { "too small", ERANGE },
+ { "too large", ERANGE },
+ };
+
+ ev[0].err = errno;
+ errno = 0;
+ if (minval > maxval)
+ error = INVALID;
+ else {
+ ll = strtoll(numstr, &ep, 10);
+ if (numstr == ep || *ep != '\0')
+ error = INVALID;
+ else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+ error = TOOSMALL;
+ else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+ error = TOOLARGE;
+ }
+ if (errstrp != NULL)
+ *errstrp = ev[error].errstr;
+ errno = ev[error].err;
+ if (error)
+ ll = 0;
+
+ return (ll);
+}
diff --git a/compat/systemd.c b/compat/systemd.c
new file mode 100644
index 0000000..7317e43
--- /dev/null
+++ b/compat/systemd.c
@@ -0,0 +1,58 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "tmux.h"
+
+int
+systemd_create_socket(int flags, char **cause)
+{
+ int fds;
+ int fd;
+ struct sockaddr_un sa;
+ int addrlen = sizeof sa;
+
+ fds = sd_listen_fds(0);
+ if (fds > 1) { /* too many file descriptors */
+ errno = E2BIG;
+ goto fail;
+ }
+
+ if (fds == 1) { /* socket-activated */
+ fd = SD_LISTEN_FDS_START;
+ if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) {
+ errno = EPFNOSUPPORT;
+ goto fail;
+ }
+ if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1)
+ goto fail;
+ socket_path = xstrdup(sa.sun_path);
+ return (fd);
+ }
+
+ return (server_create_socket(flags, cause));
+
+fail:
+ if (cause != NULL)
+ xasprintf(cause, "systemd socket error (%s)", strerror(errno));
+ return (-1);
+}
diff --git a/compat/tree.h b/compat/tree.h
new file mode 100644
index 0000000..80d0f53
--- /dev/null
+++ b/compat/tree.h
@@ -0,0 +1,748 @@
+/* $OpenBSD: tree.h,v 1.13 2011/07/09 00:19:45 pirofti Exp $ */
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYS_TREE_H_
+#define _SYS_TREE_H_
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure. Every operation
+ * on the tree causes a splay to happen. The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree. On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n). The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute. It fulfills a set of conditions:
+ * - every search path from the root to a leaf consists of the
+ * same number of black nodes,
+ * - each red node (except for the root) has a black parent,
+ * - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type) \
+struct name { \
+ struct type *sph_root; /* root of the tree */ \
+}
+
+#define SPLAY_INITIALIZER(root) \
+ { NULL }
+
+#define SPLAY_INIT(root) do { \
+ (root)->sph_root = NULL; \
+} while (0)
+
+#define SPLAY_ENTRY(type) \
+struct { \
+ struct type *spe_left; /* left element */ \
+ struct type *spe_right; /* right element */ \
+}
+
+#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
+#define SPLAY_ROOT(head) (head)->sph_root
+#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \
+ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
+ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
+ (head)->sph_root = tmp; \
+} while (0)
+
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \
+ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
+ SPLAY_LEFT(tmp, field) = (head)->sph_root; \
+ (head)->sph_root = tmp; \
+} while (0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do { \
+ SPLAY_LEFT(tmp, field) = (head)->sph_root; \
+ tmp = (head)->sph_root; \
+ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
+} while (0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do { \
+ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
+ tmp = (head)->sph_root; \
+ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
+} while (0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \
+ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
+ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
+ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
+} while (0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp) \
+void name##_SPLAY(struct name *, struct type *); \
+void name##_SPLAY_MINMAX(struct name *, int); \
+struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
+ \
+/* Finds the node with the same key as elm */ \
+static __inline struct type * \
+name##_SPLAY_FIND(struct name *head, struct type *elm) \
+{ \
+ if (SPLAY_EMPTY(head)) \
+ return(NULL); \
+ name##_SPLAY(head, elm); \
+ if ((cmp)(elm, (head)->sph_root) == 0) \
+ return (head->sph_root); \
+ return (NULL); \
+} \
+ \
+static __inline struct type * \
+name##_SPLAY_NEXT(struct name *head, struct type *elm) \
+{ \
+ name##_SPLAY(head, elm); \
+ if (SPLAY_RIGHT(elm, field) != NULL) { \
+ elm = SPLAY_RIGHT(elm, field); \
+ while (SPLAY_LEFT(elm, field) != NULL) { \
+ elm = SPLAY_LEFT(elm, field); \
+ } \
+ } else \
+ elm = NULL; \
+ return (elm); \
+} \
+ \
+static __inline struct type * \
+name##_SPLAY_MIN_MAX(struct name *head, int val) \
+{ \
+ name##_SPLAY_MINMAX(head, val); \
+ return (SPLAY_ROOT(head)); \
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp) \
+struct type * \
+name##_SPLAY_INSERT(struct name *head, struct type *elm) \
+{ \
+ if (SPLAY_EMPTY(head)) { \
+ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \
+ } else { \
+ int __comp; \
+ name##_SPLAY(head, elm); \
+ __comp = (cmp)(elm, (head)->sph_root); \
+ if(__comp < 0) { \
+ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+ SPLAY_RIGHT(elm, field) = (head)->sph_root; \
+ SPLAY_LEFT((head)->sph_root, field) = NULL; \
+ } else if (__comp > 0) { \
+ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+ SPLAY_LEFT(elm, field) = (head)->sph_root; \
+ SPLAY_RIGHT((head)->sph_root, field) = NULL; \
+ } else \
+ return ((head)->sph_root); \
+ } \
+ (head)->sph_root = (elm); \
+ return (NULL); \
+} \
+ \
+struct type * \
+name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
+{ \
+ struct type *__tmp; \
+ if (SPLAY_EMPTY(head)) \
+ return (NULL); \
+ name##_SPLAY(head, elm); \
+ if ((cmp)(elm, (head)->sph_root) == 0) { \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
+ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+ } else { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+ name##_SPLAY(head, elm); \
+ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
+ } \
+ return (elm); \
+ } \
+ return (NULL); \
+} \
+ \
+void \
+name##_SPLAY(struct name *head, struct type *elm) \
+{ \
+ struct type __node, *__left, *__right, *__tmp; \
+ int __comp; \
+\
+ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+ __left = __right = &__node; \
+\
+ while ((__comp = (cmp)(elm, (head)->sph_root))) { \
+ if (__comp < 0) { \
+ __tmp = SPLAY_LEFT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if ((cmp)(elm, __tmp) < 0){ \
+ SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKLEFT(head, __right, field); \
+ } else if (__comp > 0) { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if ((cmp)(elm, __tmp) > 0){ \
+ SPLAY_ROTATE_LEFT(head, __tmp, field); \
+ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKRIGHT(head, __left, field); \
+ } \
+ } \
+ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
+} \
+ \
+/* Splay with either the minimum or the maximum element \
+ * Used to find minimum or maximum element in tree. \
+ */ \
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{ \
+ struct type __node, *__left, *__right, *__tmp; \
+\
+ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+ __left = __right = &__node; \
+\
+ while (1) { \
+ if (__comp < 0) { \
+ __tmp = SPLAY_LEFT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if (__comp < 0){ \
+ SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKLEFT(head, __right, field); \
+ } else if (__comp > 0) { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if (__comp > 0) { \
+ SPLAY_ROTATE_LEFT(head, __tmp, field); \
+ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKRIGHT(head, __left, field); \
+ } \
+ } \
+ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
+}
+
+#define SPLAY_NEGINF -1
+#define SPLAY_INF 1
+
+#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \
+ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \
+ : name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head) \
+ for ((x) = SPLAY_MIN(name, head); \
+ (x) != NULL; \
+ (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type) \
+struct name { \
+ struct type *rbh_root; /* root of the tree */ \
+}
+
+#define RB_INITIALIZER(root) \
+ { NULL }
+
+#define RB_INIT(root) do { \
+ (root)->rbh_root = NULL; \
+} while (0)
+
+#define RB_BLACK 0
+#define RB_RED 1
+#define RB_ENTRY(type) \
+struct { \
+ struct type *rbe_left; /* left element */ \
+ struct type *rbe_right; /* right element */ \
+ struct type *rbe_parent; /* parent element */ \
+ int rbe_color; /* node color */ \
+}
+
+#define RB_LEFT(elm, field) (elm)->field.rbe_left
+#define RB_RIGHT(elm, field) (elm)->field.rbe_right
+#define RB_PARENT(elm, field) (elm)->field.rbe_parent
+#define RB_COLOR(elm, field) (elm)->field.rbe_color
+#define RB_ROOT(head) (head)->rbh_root
+#define RB_EMPTY(head) (RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do { \
+ RB_PARENT(elm, field) = parent; \
+ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \
+ RB_COLOR(elm, field) = RB_RED; \
+} while (0)
+
+#define RB_SET_BLACKRED(black, red, field) do { \
+ RB_COLOR(black, field) = RB_BLACK; \
+ RB_COLOR(red, field) = RB_RED; \
+} while (0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x) do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \
+ (tmp) = RB_RIGHT(elm, field); \
+ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \
+ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \
+ } \
+ RB_AUGMENT(elm); \
+ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \
+ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
+ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
+ else \
+ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+ } else \
+ (head)->rbh_root = (tmp); \
+ RB_LEFT(tmp, field) = (elm); \
+ RB_PARENT(elm, field) = (tmp); \
+ RB_AUGMENT(tmp); \
+ if ((RB_PARENT(tmp, field))) \
+ RB_AUGMENT(RB_PARENT(tmp, field)); \
+} while (0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \
+ (tmp) = RB_LEFT(elm, field); \
+ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \
+ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \
+ } \
+ RB_AUGMENT(elm); \
+ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \
+ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
+ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
+ else \
+ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+ } else \
+ (head)->rbh_root = (tmp); \
+ RB_RIGHT(tmp, field) = (elm); \
+ RB_PARENT(elm, field) = (tmp); \
+ RB_AUGMENT(tmp); \
+ if ((RB_PARENT(tmp, field))) \
+ RB_AUGMENT(RB_PARENT(tmp, field)); \
+} while (0)
+
+/* Generates prototypes and inline functions */
+#define RB_PROTOTYPE(name, type, field, cmp) \
+ RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \
+ RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *); \
+attr struct type *name##_RB_INSERT(struct name *, struct type *); \
+attr struct type *name##_RB_FIND(struct name *, struct type *); \
+attr struct type *name##_RB_NFIND(struct name *, struct type *); \
+attr struct type *name##_RB_NEXT(struct type *); \
+attr struct type *name##_RB_PREV(struct type *); \
+attr struct type *name##_RB_MINMAX(struct name *, int); \
+ \
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define RB_GENERATE(name, type, field, cmp) \
+ RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define RB_GENERATE_STATIC(name, type, field, cmp) \
+ RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
+attr void \
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \
+{ \
+ struct type *parent, *gparent, *tmp; \
+ while ((parent = RB_PARENT(elm, field)) && \
+ RB_COLOR(parent, field) == RB_RED) { \
+ gparent = RB_PARENT(parent, field); \
+ if (parent == RB_LEFT(gparent, field)) { \
+ tmp = RB_RIGHT(gparent, field); \
+ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
+ RB_COLOR(tmp, field) = RB_BLACK; \
+ RB_SET_BLACKRED(parent, gparent, field);\
+ elm = gparent; \
+ continue; \
+ } \
+ if (RB_RIGHT(parent, field) == elm) { \
+ RB_ROTATE_LEFT(head, parent, tmp, field);\
+ tmp = parent; \
+ parent = elm; \
+ elm = tmp; \
+ } \
+ RB_SET_BLACKRED(parent, gparent, field); \
+ RB_ROTATE_RIGHT(head, gparent, tmp, field); \
+ } else { \
+ tmp = RB_LEFT(gparent, field); \
+ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
+ RB_COLOR(tmp, field) = RB_BLACK; \
+ RB_SET_BLACKRED(parent, gparent, field);\
+ elm = gparent; \
+ continue; \
+ } \
+ if (RB_LEFT(parent, field) == elm) { \
+ RB_ROTATE_RIGHT(head, parent, tmp, field);\
+ tmp = parent; \
+ parent = elm; \
+ elm = tmp; \
+ } \
+ RB_SET_BLACKRED(parent, gparent, field); \
+ RB_ROTATE_LEFT(head, gparent, tmp, field); \
+ } \
+ } \
+ RB_COLOR(head->rbh_root, field) = RB_BLACK; \
+} \
+ \
+attr void \
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{ \
+ struct type *tmp; \
+ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \
+ elm != RB_ROOT(head)) { \
+ if (RB_LEFT(parent, field) == elm) { \
+ tmp = RB_RIGHT(parent, field); \
+ if (RB_COLOR(tmp, field) == RB_RED) { \
+ RB_SET_BLACKRED(tmp, parent, field); \
+ RB_ROTATE_LEFT(head, parent, tmp, field);\
+ tmp = RB_RIGHT(parent, field); \
+ } \
+ if ((RB_LEFT(tmp, field) == NULL || \
+ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+ (RB_RIGHT(tmp, field) == NULL || \
+ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+ RB_COLOR(tmp, field) = RB_RED; \
+ elm = parent; \
+ parent = RB_PARENT(elm, field); \
+ } else { \
+ if (RB_RIGHT(tmp, field) == NULL || \
+ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+ struct type *oleft; \
+ if ((oleft = RB_LEFT(tmp, field)))\
+ RB_COLOR(oleft, field) = RB_BLACK;\
+ RB_COLOR(tmp, field) = RB_RED; \
+ RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+ tmp = RB_RIGHT(parent, field); \
+ } \
+ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+ RB_COLOR(parent, field) = RB_BLACK; \
+ if (RB_RIGHT(tmp, field)) \
+ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+ RB_ROTATE_LEFT(head, parent, tmp, field);\
+ elm = RB_ROOT(head); \
+ break; \
+ } \
+ } else { \
+ tmp = RB_LEFT(parent, field); \
+ if (RB_COLOR(tmp, field) == RB_RED) { \
+ RB_SET_BLACKRED(tmp, parent, field); \
+ RB_ROTATE_RIGHT(head, parent, tmp, field);\
+ tmp = RB_LEFT(parent, field); \
+ } \
+ if ((RB_LEFT(tmp, field) == NULL || \
+ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+ (RB_RIGHT(tmp, field) == NULL || \
+ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+ RB_COLOR(tmp, field) = RB_RED; \
+ elm = parent; \
+ parent = RB_PARENT(elm, field); \
+ } else { \
+ if (RB_LEFT(tmp, field) == NULL || \
+ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+ struct type *oright; \
+ if ((oright = RB_RIGHT(tmp, field)))\
+ RB_COLOR(oright, field) = RB_BLACK;\
+ RB_COLOR(tmp, field) = RB_RED; \
+ RB_ROTATE_LEFT(head, tmp, oright, field);\
+ tmp = RB_LEFT(parent, field); \
+ } \
+ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+ RB_COLOR(parent, field) = RB_BLACK; \
+ if (RB_LEFT(tmp, field)) \
+ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+ RB_ROTATE_RIGHT(head, parent, tmp, field);\
+ elm = RB_ROOT(head); \
+ break; \
+ } \
+ } \
+ } \
+ if (elm) \
+ RB_COLOR(elm, field) = RB_BLACK; \
+} \
+ \
+attr struct type * \
+name##_RB_REMOVE(struct name *head, struct type *elm) \
+{ \
+ struct type *child, *parent, *old = elm; \
+ int color; \
+ if (RB_LEFT(elm, field) == NULL) \
+ child = RB_RIGHT(elm, field); \
+ else if (RB_RIGHT(elm, field) == NULL) \
+ child = RB_LEFT(elm, field); \
+ else { \
+ struct type *left; \
+ elm = RB_RIGHT(elm, field); \
+ while ((left = RB_LEFT(elm, field))) \
+ elm = left; \
+ child = RB_RIGHT(elm, field); \
+ parent = RB_PARENT(elm, field); \
+ color = RB_COLOR(elm, field); \
+ if (child) \
+ RB_PARENT(child, field) = parent; \
+ if (parent) { \
+ if (RB_LEFT(parent, field) == elm) \
+ RB_LEFT(parent, field) = child; \
+ else \
+ RB_RIGHT(parent, field) = child; \
+ RB_AUGMENT(parent); \
+ } else \
+ RB_ROOT(head) = child; \
+ if (RB_PARENT(elm, field) == old) \
+ parent = elm; \
+ (elm)->field = (old)->field; \
+ if (RB_PARENT(old, field)) { \
+ if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+ RB_LEFT(RB_PARENT(old, field), field) = elm;\
+ else \
+ RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+ RB_AUGMENT(RB_PARENT(old, field)); \
+ } else \
+ RB_ROOT(head) = elm; \
+ RB_PARENT(RB_LEFT(old, field), field) = elm; \
+ if (RB_RIGHT(old, field)) \
+ RB_PARENT(RB_RIGHT(old, field), field) = elm; \
+ if (parent) { \
+ left = parent; \
+ do { \
+ RB_AUGMENT(left); \
+ } while ((left = RB_PARENT(left, field))); \
+ } \
+ goto color; \
+ } \
+ parent = RB_PARENT(elm, field); \
+ color = RB_COLOR(elm, field); \
+ if (child) \
+ RB_PARENT(child, field) = parent; \
+ if (parent) { \
+ if (RB_LEFT(parent, field) == elm) \
+ RB_LEFT(parent, field) = child; \
+ else \
+ RB_RIGHT(parent, field) = child; \
+ RB_AUGMENT(parent); \
+ } else \
+ RB_ROOT(head) = child; \
+color: \
+ if (color == RB_BLACK) \
+ name##_RB_REMOVE_COLOR(head, parent, child); \
+ return (old); \
+} \
+ \
+/* Inserts a node into the RB tree */ \
+attr struct type * \
+name##_RB_INSERT(struct name *head, struct type *elm) \
+{ \
+ struct type *tmp; \
+ struct type *parent = NULL; \
+ int comp = 0; \
+ tmp = RB_ROOT(head); \
+ while (tmp) { \
+ parent = tmp; \
+ comp = (cmp)(elm, parent); \
+ if (comp < 0) \
+ tmp = RB_LEFT(tmp, field); \
+ else if (comp > 0) \
+ tmp = RB_RIGHT(tmp, field); \
+ else \
+ return (tmp); \
+ } \
+ RB_SET(elm, parent, field); \
+ if (parent != NULL) { \
+ if (comp < 0) \
+ RB_LEFT(parent, field) = elm; \
+ else \
+ RB_RIGHT(parent, field) = elm; \
+ RB_AUGMENT(parent); \
+ } else \
+ RB_ROOT(head) = elm; \
+ name##_RB_INSERT_COLOR(head, elm); \
+ return (NULL); \
+} \
+ \
+/* Finds the node with the same key as elm */ \
+attr struct type * \
+name##_RB_FIND(struct name *head, struct type *elm) \
+{ \
+ struct type *tmp = RB_ROOT(head); \
+ int comp; \
+ while (tmp) { \
+ comp = cmp(elm, tmp); \
+ if (comp < 0) \
+ tmp = RB_LEFT(tmp, field); \
+ else if (comp > 0) \
+ tmp = RB_RIGHT(tmp, field); \
+ else \
+ return (tmp); \
+ } \
+ return (NULL); \
+} \
+ \
+/* Finds the first node greater than or equal to the search key */ \
+attr struct type * \
+name##_RB_NFIND(struct name *head, struct type *elm) \
+{ \
+ struct type *tmp = RB_ROOT(head); \
+ struct type *res = NULL; \
+ int comp; \
+ while (tmp) { \
+ comp = cmp(elm, tmp); \
+ if (comp < 0) { \
+ res = tmp; \
+ tmp = RB_LEFT(tmp, field); \
+ } \
+ else if (comp > 0) \
+ tmp = RB_RIGHT(tmp, field); \
+ else \
+ return (tmp); \
+ } \
+ return (res); \
+} \
+ \
+/* ARGSUSED */ \
+attr struct type * \
+name##_RB_NEXT(struct type *elm) \
+{ \
+ if (RB_RIGHT(elm, field)) { \
+ elm = RB_RIGHT(elm, field); \
+ while (RB_LEFT(elm, field)) \
+ elm = RB_LEFT(elm, field); \
+ } else { \
+ if (RB_PARENT(elm, field) && \
+ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \
+ elm = RB_PARENT(elm, field); \
+ else { \
+ while (RB_PARENT(elm, field) && \
+ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+ elm = RB_PARENT(elm, field); \
+ elm = RB_PARENT(elm, field); \
+ } \
+ } \
+ return (elm); \
+} \
+ \
+/* ARGSUSED */ \
+attr struct type * \
+name##_RB_PREV(struct type *elm) \
+{ \
+ if (RB_LEFT(elm, field)) { \
+ elm = RB_LEFT(elm, field); \
+ while (RB_RIGHT(elm, field)) \
+ elm = RB_RIGHT(elm, field); \
+ } else { \
+ if (RB_PARENT(elm, field) && \
+ (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \
+ elm = RB_PARENT(elm, field); \
+ else { \
+ while (RB_PARENT(elm, field) && \
+ (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+ elm = RB_PARENT(elm, field); \
+ elm = RB_PARENT(elm, field); \
+ } \
+ } \
+ return (elm); \
+} \
+ \
+attr struct type * \
+name##_RB_MINMAX(struct name *head, int val) \
+{ \
+ struct type *tmp = RB_ROOT(head); \
+ struct type *parent = NULL; \
+ while (tmp) { \
+ parent = tmp; \
+ if (val < 0) \
+ tmp = RB_LEFT(tmp, field); \
+ else \
+ tmp = RB_RIGHT(tmp, field); \
+ } \
+ return (parent); \
+}
+
+#define RB_NEGINF -1
+#define RB_INF 1
+
+#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y) name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y) name##_RB_NEXT(y)
+#define RB_PREV(name, x, y) name##_RB_PREV(y)
+#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head) \
+ for ((x) = RB_MIN(name, head); \
+ (x) != NULL; \
+ (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_SAFE(x, name, head, y) \
+ for ((x) = RB_MIN(name, head); \
+ ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1); \
+ (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head) \
+ for ((x) = RB_MAX(name, head); \
+ (x) != NULL; \
+ (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \
+ for ((x) = RB_MAX(name, head); \
+ ((x) != NULL) && ((y) = name##_RB_PREV(x), 1); \
+ (x) = (y))
+
+#endif /* _SYS_TREE_H_ */
diff --git a/compat/unvis.c b/compat/unvis.c
new file mode 100644
index 0000000..6108e77
--- /dev/null
+++ b/compat/unvis.c
@@ -0,0 +1,281 @@
+/* $OpenBSD: unvis.c,v 1.12 2005/08/08 08:05:34 espie Exp $ */
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "compat.h"
+
+/*
+ * decode driven by state machine
+ */
+#define S_GROUND 0 /* haven't seen escape char */
+#define S_START 1 /* start decoding special sequence */
+#define S_META 2 /* metachar started (M) */
+#define S_META1 3 /* metachar more, regular char (-) */
+#define S_CTRL 4 /* control char started (^) */
+#define S_OCTAL2 5 /* octal digit 2 */
+#define S_OCTAL3 6 /* octal digit 3 */
+
+#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
+
+/*
+ * unvis - decode characters previously encoded by vis
+ */
+int
+unvis(char *cp, char c, int *astate, int flag)
+{
+
+ if (flag & UNVIS_END) {
+ if (*astate == S_OCTAL2 || *astate == S_OCTAL3) {
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ }
+ return (*astate == S_GROUND ? UNVIS_NOCHAR : UNVIS_SYNBAD);
+ }
+
+ switch (*astate) {
+
+ case S_GROUND:
+ *cp = 0;
+ if (c == '\\') {
+ *astate = S_START;
+ return (0);
+ }
+ *cp = c;
+ return (UNVIS_VALID);
+
+ case S_START:
+ switch(c) {
+ case '\\':
+ *cp = c;
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ *cp = (c - '0');
+ *astate = S_OCTAL2;
+ return (0);
+ case 'M':
+ *cp = (char) 0200;
+ *astate = S_META;
+ return (0);
+ case '^':
+ *astate = S_CTRL;
+ return (0);
+ case 'n':
+ *cp = '\n';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 'r':
+ *cp = '\r';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 'b':
+ *cp = '\b';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 'a':
+ *cp = '\007';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 'v':
+ *cp = '\v';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 't':
+ *cp = '\t';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 'f':
+ *cp = '\f';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 's':
+ *cp = ' ';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case 'E':
+ *cp = '\033';
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+ case '\n':
+ /*
+ * hidden newline
+ */
+ *astate = S_GROUND;
+ return (UNVIS_NOCHAR);
+ case '$':
+ /*
+ * hidden marker
+ */
+ *astate = S_GROUND;
+ return (UNVIS_NOCHAR);
+ }
+ *astate = S_GROUND;
+ return (UNVIS_SYNBAD);
+
+ case S_META:
+ if (c == '-')
+ *astate = S_META1;
+ else if (c == '^')
+ *astate = S_CTRL;
+ else {
+ *astate = S_GROUND;
+ return (UNVIS_SYNBAD);
+ }
+ return (0);
+
+ case S_META1:
+ *astate = S_GROUND;
+ *cp |= c;
+ return (UNVIS_VALID);
+
+ case S_CTRL:
+ if (c == '?')
+ *cp |= 0177;
+ else
+ *cp |= c & 037;
+ *astate = S_GROUND;
+ return (UNVIS_VALID);
+
+ case S_OCTAL2: /* second possible octal digit */
+ if (isoctal(c)) {
+ /*
+ * yes - and maybe a third
+ */
+ *cp = (*cp << 3) + (c - '0');
+ *astate = S_OCTAL3;
+ return (0);
+ }
+ /*
+ * no - done with current sequence, push back passed char
+ */
+ *astate = S_GROUND;
+ return (UNVIS_VALIDPUSH);
+
+ case S_OCTAL3: /* third possible octal digit */
+ *astate = S_GROUND;
+ if (isoctal(c)) {
+ *cp = (*cp << 3) + (c - '0');
+ return (UNVIS_VALID);
+ }
+ /*
+ * we were done, push back passed char
+ */
+ return (UNVIS_VALIDPUSH);
+
+ default:
+ /*
+ * decoder in unknown state - (probably uninitialized)
+ */
+ *astate = S_GROUND;
+ return (UNVIS_SYNBAD);
+ }
+}
+
+/*
+ * strunvis - decode src into dst
+ *
+ * Number of chars decoded into dst is returned, -1 on error.
+ * Dst is null terminated.
+ */
+
+int
+strunvis(char *dst, const char *src)
+{
+ char c;
+ char *start = dst;
+ int state = 0;
+
+ while ((c = *src++)) {
+ again:
+ switch (unvis(dst, c, &state, 0)) {
+ case UNVIS_VALID:
+ dst++;
+ break;
+ case UNVIS_VALIDPUSH:
+ dst++;
+ goto again;
+ case 0:
+ case UNVIS_NOCHAR:
+ break;
+ default:
+ *dst = '\0';
+ return (-1);
+ }
+ }
+ if (unvis(dst, c, &state, UNVIS_END) == UNVIS_VALID)
+ dst++;
+ *dst = '\0';
+ return (dst - start);
+}
+
+ssize_t
+strnunvis(char *dst, const char *src, size_t sz)
+{
+ char c, p;
+ char *start = dst, *end = dst + sz - 1;
+ int state = 0;
+
+ if (sz > 0)
+ *end = '\0';
+ while ((c = *src++)) {
+ again:
+ switch (unvis(&p, c, &state, 0)) {
+ case UNVIS_VALID:
+ if (dst < end)
+ *dst = p;
+ dst++;
+ break;
+ case UNVIS_VALIDPUSH:
+ if (dst < end)
+ *dst = p;
+ dst++;
+ goto again;
+ case 0:
+ case UNVIS_NOCHAR:
+ break;
+ default:
+ if (dst <= end)
+ *dst = '\0';
+ return (-1);
+ }
+ }
+ if (unvis(&p, c, &state, UNVIS_END) == UNVIS_VALID) {
+ if (dst < end)
+ *dst = p;
+ dst++;
+ }
+ if (dst <= end)
+ *dst = '\0';
+ return (dst - start);
+}
+
diff --git a/compat/utf8proc.c b/compat/utf8proc.c
new file mode 100644
index 0000000..dd4ab27
--- /dev/null
+++ b/compat/utf8proc.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2016 Joshua Rubin <joshua@rubixconsulting.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 <utf8proc.h>
+
+#include "compat.h"
+
+int
+utf8proc_wcwidth(wchar_t wc)
+{
+ int cat;
+
+ cat = utf8proc_category(wc);
+ if (cat == UTF8PROC_CATEGORY_CO) {
+ /*
+ * The private use category is where powerline and similar
+ * codepoints are stored, they have "ambiguous" width - use 1.
+ */
+ return (1);
+ }
+ return (utf8proc_charwidth(wc));
+}
+
+int
+utf8proc_mbtowc(wchar_t *pwc, const char *s, size_t n)
+{
+ utf8proc_ssize_t slen;
+
+ if (s == NULL)
+ return (0);
+
+ /*
+ * *pwc == -1 indicates invalid codepoint
+ * slen < 0 indicates an error
+ */
+ slen = utf8proc_iterate(s, n, pwc);
+ if (*pwc == (wchar_t)-1 || slen < 0)
+ return (-1);
+ return (slen);
+}
+
+int
+utf8proc_wctomb(char *s, wchar_t wc)
+{
+ if (s == NULL)
+ return (0);
+
+ if (!utf8proc_codepoint_valid(wc))
+ return (-1);
+ return (utf8proc_encode_char(wc, s));
+}
diff --git a/compat/vis.c b/compat/vis.c
new file mode 100644
index 0000000..3d37805
--- /dev/null
+++ b/compat/vis.c
@@ -0,0 +1,242 @@
+/* $OpenBSD: vis.c,v 1.24 2015/07/20 01:52:28 millert Exp $ */
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
+#define isvisible(c,flag) \
+ (((c) == '\\' || (flag & VIS_ALL) == 0) && \
+ (((u_int)(c) <= UCHAR_MAX && isascii((u_char)(c)) && \
+ (((c) != '*' && (c) != '?' && (c) != '[' && (c) != '#') || \
+ (flag & VIS_GLOB) == 0) && isgraph((u_char)(c))) || \
+ ((flag & VIS_SP) == 0 && (c) == ' ') || \
+ ((flag & VIS_TAB) == 0 && (c) == '\t') || \
+ ((flag & VIS_NL) == 0 && (c) == '\n') || \
+ ((flag & VIS_SAFE) && ((c) == '\b' || \
+ (c) == '\007' || (c) == '\r' || \
+ isgraph((u_char)(c))))))
+
+/*
+ * vis - visually encode characters
+ */
+char *
+vis(char *dst, int c, int flag, int nextc)
+{
+ if (isvisible(c, flag)) {
+ if ((c == '"' && (flag & VIS_DQ) != 0) ||
+ (c == '\\' && (flag & VIS_NOSLASH) == 0))
+ *dst++ = '\\';
+ *dst++ = c;
+ *dst = '\0';
+ return (dst);
+ }
+
+ if (flag & VIS_CSTYLE) {
+ switch(c) {
+ case '\n':
+ *dst++ = '\\';
+ *dst++ = 'n';
+ goto done;
+ case '\r':
+ *dst++ = '\\';
+ *dst++ = 'r';
+ goto done;
+ case '\b':
+ *dst++ = '\\';
+ *dst++ = 'b';
+ goto done;
+ case '\a':
+ *dst++ = '\\';
+ *dst++ = 'a';
+ goto done;
+ case '\v':
+ *dst++ = '\\';
+ *dst++ = 'v';
+ goto done;
+ case '\t':
+ *dst++ = '\\';
+ *dst++ = 't';
+ goto done;
+ case '\f':
+ *dst++ = '\\';
+ *dst++ = 'f';
+ goto done;
+ case ' ':
+ *dst++ = '\\';
+ *dst++ = 's';
+ goto done;
+ case '\0':
+ *dst++ = '\\';
+ *dst++ = '0';
+ if (isoctal(nextc)) {
+ *dst++ = '0';
+ *dst++ = '0';
+ }
+ goto done;
+ }
+ }
+ if (((c & 0177) == ' ') || (flag & VIS_OCTAL) ||
+ ((flag & VIS_GLOB) && (c == '*' || c == '?' || c == '[' || c == '#'))) {
+ *dst++ = '\\';
+ *dst++ = ((u_char)c >> 6 & 07) + '0';
+ *dst++ = ((u_char)c >> 3 & 07) + '0';
+ *dst++ = ((u_char)c & 07) + '0';
+ goto done;
+ }
+ if ((flag & VIS_NOSLASH) == 0)
+ *dst++ = '\\';
+ if (c & 0200) {
+ c &= 0177;
+ *dst++ = 'M';
+ }
+ if (iscntrl((u_char)c)) {
+ *dst++ = '^';
+ if (c == 0177)
+ *dst++ = '?';
+ else
+ *dst++ = c + '@';
+ } else {
+ *dst++ = '-';
+ *dst++ = c;
+ }
+done:
+ *dst = '\0';
+ return (dst);
+}
+
+/*
+ * strvis, strnvis, strvisx - visually encode characters from src into dst
+ *
+ * Dst must be 4 times the size of src to account for possible
+ * expansion. The length of dst, not including the trailing NULL,
+ * is returned.
+ *
+ * Strnvis will write no more than siz-1 bytes (and will NULL terminate).
+ * The number of bytes needed to fully encode the string is returned.
+ *
+ * Strvisx encodes exactly len bytes from src into dst.
+ * This is useful for encoding a block of data.
+ */
+int
+strvis(char *dst, const char *src, int flag)
+{
+ char c;
+ char *start;
+
+ for (start = dst; (c = *src);)
+ dst = vis(dst, c, flag, *++src);
+ *dst = '\0';
+ return (dst - start);
+}
+
+int
+strnvis(char *dst, const char *src, size_t siz, int flag)
+{
+ char *start, *end;
+ char tbuf[5];
+ int c, i;
+
+ i = 0;
+ for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) {
+ if (isvisible(c, flag)) {
+ if ((c == '"' && (flag & VIS_DQ) != 0) ||
+ (c == '\\' && (flag & VIS_NOSLASH) == 0)) {
+ /* need space for the extra '\\' */
+ if (dst + 1 >= end) {
+ i = 2;
+ break;
+ }
+ *dst++ = '\\';
+ }
+ i = 1;
+ *dst++ = c;
+ src++;
+ } else {
+ i = vis(tbuf, c, flag, *++src) - tbuf;
+ if (dst + i <= end) {
+ memcpy(dst, tbuf, i);
+ dst += i;
+ } else {
+ src--;
+ break;
+ }
+ }
+ }
+ if (siz > 0)
+ *dst = '\0';
+ if (dst + i > end) {
+ /* adjust return value for truncation */
+ while ((c = *src))
+ dst += vis(tbuf, c, flag, *++src) - tbuf;
+ }
+ return (dst - start);
+}
+
+int
+stravis(char **outp, const char *src, int flag)
+{
+ char *buf;
+ int len, serrno;
+
+ buf = calloc(4, strlen(src) + 1);
+ if (buf == NULL)
+ return -1;
+ len = strvis(buf, src, flag);
+ serrno = errno;
+ *outp = realloc(buf, len + 1);
+ if (*outp == NULL) {
+ *outp = buf;
+ errno = serrno;
+ }
+ return (len);
+}
+
+int
+strvisx(char *dst, const char *src, size_t len, int flag)
+{
+ char c;
+ char *start;
+
+ for (start = dst; len > 1; len--) {
+ c = *src;
+ dst = vis(dst, c, flag, *++src);
+ }
+ if (len)
+ dst = vis(dst, *src, flag, '\0');
+ *dst = '\0';
+ return (dst - start);
+}
diff --git a/compat/vis.h b/compat/vis.h
new file mode 100644
index 0000000..9f12d23
--- /dev/null
+++ b/compat/vis.h
@@ -0,0 +1,85 @@
+/* $OpenBSD: vis.h,v 1.15 2015/07/20 01:52:27 millert Exp $ */
+/* $NetBSD: vis.h,v 1.4 1994/10/26 00:56:41 cgd Exp $ */
+
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)vis.h 5.9 (Berkeley) 4/3/91
+ */
+
+#ifndef _VIS_H_
+#define _VIS_H_
+
+/*
+ * to select alternate encoding format
+ */
+#define VIS_OCTAL 0x01 /* use octal \ddd format */
+#define VIS_CSTYLE 0x02 /* use \[nrft0..] where appropriate */
+
+/*
+ * to alter set of characters encoded (default is to encode all
+ * non-graphic except space, tab, and newline).
+ */
+#define VIS_SP 0x04 /* also encode space */
+#define VIS_TAB 0x08 /* also encode tab */
+#define VIS_NL 0x10 /* also encode newline */
+#define VIS_WHITE (VIS_SP | VIS_TAB | VIS_NL)
+#define VIS_SAFE 0x20 /* only encode "unsafe" characters */
+#define VIS_DQ 0x200 /* backslash-escape double quotes */
+#define VIS_ALL 0x400 /* encode all characters */
+
+/*
+ * other
+ */
+#define VIS_NOSLASH 0x40 /* inhibit printing '\' */
+#define VIS_GLOB 0x100 /* encode glob(3) magics and '#' */
+
+/*
+ * unvis return codes
+ */
+#define UNVIS_VALID 1 /* character valid */
+#define UNVIS_VALIDPUSH 2 /* character valid, push back passed char */
+#define UNVIS_NOCHAR 3 /* valid sequence, no character produced */
+#define UNVIS_SYNBAD -1 /* unrecognized escape sequence */
+#define UNVIS_ERROR -2 /* decoder in unknown state (unrecoverable) */
+
+/*
+ * unvis flags
+ */
+#define UNVIS_END 1 /* no more characters */
+
+char *vis(char *, int, int, int);
+int strvis(char *, const char *, int);
+int stravis(char **, const char *, int);
+int strnvis(char *, const char *, size_t, int);
+int strvisx(char *, const char *, size_t, int);
+int strunvis(char *, const char *);
+int unvis(char *, char, int *, int);
+ssize_t strnunvis(char *, const char *, size_t);
+
+#endif /* !_VIS_H_ */
diff --git a/configure b/configure
new file mode 100755
index 0000000..5e32bf3
--- /dev/null
+++ b/configure
@@ -0,0 +1,9280 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.69 for tmux 3.3a.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='print -r --'
+ as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='printf %s\n'
+ as_echo_n='printf %s'
+else
+ if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+ as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+ as_echo_n='/usr/ucb/echo -n'
+ else
+ as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+ as_echo_n_body='eval
+ arg=$1;
+ case $arg in #(
+ *"$as_nl"*)
+ expr "X$arg" : "X\\(.*\\)$as_nl";
+ arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+ esac;
+ expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+ '
+ export as_echo_n_body
+ as_echo_n='sh -c $as_echo_n_body as_echo'
+ fi
+ export as_echo_body
+ as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" "" $as_nl"
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there. '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+ # into an infinite loop, continuously re-executing ourselves.
+ if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+ _as_can_reexec=no; export _as_can_reexec;
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+ fi
+ # We don't want this to propagate to other subprocesses.
+ { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+ as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '\${1+\"\$@\"}'='\"\$@\"'
+ setopt NO_GLOB_SUBST
+else
+ case \`(set -o) 2>/dev/null\` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+"
+ as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+ exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+ as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+ as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+ eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+ test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+ if (eval "$as_required") 2>/dev/null; then :
+ as_have_required=yes
+else
+ as_have_required=no
+fi
+ if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ as_found=:
+ case $as_dir in #(
+ /*)
+ for as_base in sh bash ksh sh5; do
+ # Try only shells that exist, to save several forks.
+ as_shell=$as_dir/$as_base
+ if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+ { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+ CONFIG_SHELL=$as_shell as_have_required=yes
+ if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+ break 2
+fi
+fi
+ done;;
+ esac
+ as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+ { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+ CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+ if test "x$CONFIG_SHELL" != x; then :
+ export CONFIG_SHELL
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+ if test x$as_have_required = xno; then :
+ $as_echo "$0: This script requires a shell more modern than all"
+ $as_echo "$0: the shells that I found on your system."
+ if test x${ZSH_VERSION+set} = xset ; then
+ $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+ $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+ else
+ $as_echo "$0: Please tell bug-autoconf@gnu.org about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+ fi
+ exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ $as_echo "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+ as_lineno_1=$LINENO as_lineno_1a=$LINENO
+ as_lineno_2=$LINENO as_lineno_2a=$LINENO
+ eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+ test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+ # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-)
+ sed -n '
+ p
+ /[$]LINENO/=
+ ' <$as_myself |
+ sed '
+ s/[$]LINENO.*/&-/
+ t lineno
+ b
+ :lineno
+ N
+ :loop
+ s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+ t loop
+ s/-\n.*//
+ ' >$as_me.lineno &&
+ chmod +x "$as_me.lineno" ||
+ { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+ # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+ # already done that, so ensure we don't try to do so again and fall
+ # in an infinite loop. This has already happened in practice.
+ _as_can_reexec=no; export _as_can_reexec
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensitive to this).
+ . "./$as_me.lineno"
+ # Exit status is that of the last command.
+ exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='tmux'
+PACKAGE_TARNAME='tmux'
+PACKAGE_VERSION='3.3a'
+PACKAGE_STRING='tmux 3.3a'
+PACKAGE_BUGREPORT=''
+PACKAGE_URL=''
+
+ac_config_libobj_dir=compat
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='am__EXEEXT_FALSE
+am__EXEEXT_TRUE
+LTLIBOBJS
+AM_LDFLAGS
+AM_CFLAGS
+AM_CPPFLAGS
+IS_UNKNOWN_FALSE
+IS_UNKNOWN_TRUE
+IS_HAIKU_FALSE
+IS_HAIKU_TRUE
+IS_HPUX_FALSE
+IS_HPUX_TRUE
+IS_SUNOS_FALSE
+IS_SUNOS_TRUE
+IS_OPENBSD_FALSE
+IS_OPENBSD_TRUE
+IS_NETBSD_FALSE
+IS_NETBSD_TRUE
+IS_FREEBSD_FALSE
+IS_FREEBSD_TRUE
+IS_LINUX_FALSE
+IS_LINUX_TRUE
+IS_DRAGONFLY_FALSE
+IS_DRAGONFLY_TRUE
+IS_DARWIN_FALSE
+IS_DARWIN_TRUE
+IS_AIX_FALSE
+IS_AIX_TRUE
+PLATFORM
+MANFORMAT
+DEFAULT_TERM
+NEED_FORKPTY_FALSE
+NEED_FORKPTY_TRUE
+XOPEN_DEFINES
+HAVE_SYSTEMD_FALSE
+HAVE_SYSTEMD_TRUE
+SYSTEMD_LIBS
+SYSTEMD_CFLAGS
+HAVE_UTF8PROC_FALSE
+HAVE_UTF8PROC_TRUE
+LIBNCURSESW_LIBS
+LIBNCURSESW_CFLAGS
+LIBNCURSES_LIBS
+LIBNCURSES_CFLAGS
+LIBTINFO_LIBS
+LIBTINFO_CFLAGS
+LIBEVENT_LIBS
+LIBEVENT_CFLAGS
+LIBEVENT_CORE_LIBS
+LIBEVENT_CORE_CFLAGS
+LIBOBJS
+IS_SUNCC_FALSE
+IS_SUNCC_TRUE
+IS_GCC_FALSE
+IS_GCC_TRUE
+NEED_FUZZING_FALSE
+NEED_FUZZING_TRUE
+IS_DEBUG_FALSE
+IS_DEBUG_TRUE
+PKG_CONFIG_LIBDIR
+PKG_CONFIG_PATH
+PKG_CONFIG
+YFLAGS
+YACC
+EGREP
+GREP
+CPP
+am__fastdepCC_FALSE
+am__fastdepCC_TRUE
+CCDEPMODE
+am__nodep
+AMDEPBACKSLASH
+AMDEP_FALSE
+AMDEP_TRUE
+am__quote
+am__include
+DEPDIR
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+FUZZING_LIBS
+host_os
+host_vendor
+host_cpu
+host
+build_os
+build_vendor
+build_cpu
+build
+AM_BACKSLASH
+AM_DEFAULT_VERBOSITY
+AM_DEFAULT_V
+AM_V
+am__untar
+am__tar
+AMTAR
+am__leading_dot
+SET_MAKE
+AWK
+mkdir_p
+MKDIR_P
+INSTALL_STRIP_PROGRAM
+STRIP
+install_sh
+MAKEINFO
+AUTOHEADER
+AUTOMAKE
+AUTOCONF
+ACLOCAL
+VERSION
+PACKAGE
+CYGPATH_W
+am__isrc
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+runstatedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_silent_rules
+enable_fuzzing
+enable_dependency_tracking
+enable_debug
+enable_static
+with_TERM
+enable_utempter
+enable_utf8proc
+enable_systemd
+'
+ ac_precious_vars='build_alias
+host_alias
+target_alias
+FUZZING_LIBS
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP
+YACC
+YFLAGS
+PKG_CONFIG
+PKG_CONFIG_PATH
+PKG_CONFIG_LIBDIR
+LIBEVENT_CORE_CFLAGS
+LIBEVENT_CORE_LIBS
+LIBEVENT_CFLAGS
+LIBEVENT_LIBS
+LIBTINFO_CFLAGS
+LIBTINFO_LIBS
+LIBNCURSES_CFLAGS
+LIBNCURSES_LIBS
+LIBNCURSESW_CFLAGS
+LIBNCURSESW_LIBS
+SYSTEMD_CFLAGS
+SYSTEMD_LIBS'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval $ac_prev=\$ac_option
+ ac_prev=
+ continue
+ fi
+
+ case $ac_option in
+ *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+ *=) ac_optarg= ;;
+ *) ac_optarg=yes ;;
+ esac
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_dashdash$ac_option in
+ --)
+ ac_dashdash=yes ;;
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=*)
+ datadir=$ac_optarg ;;
+
+ -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+ | --dataroo | --dataro | --datar)
+ ac_prev=datarootdir ;;
+ -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+ datarootdir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=no ;;
+
+ -docdir | --docdir | --docdi | --doc | --do)
+ ac_prev=docdir ;;
+ -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+ docdir=$ac_optarg ;;
+
+ -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+ ac_prev=dvidir ;;
+ -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+ dvidir=$ac_optarg ;;
+
+ -enable-* | --enable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=\$ac_optarg ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+ ac_prev=htmldir ;;
+ -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+ | --ht=*)
+ htmldir=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localedir | --localedir | --localedi | --localed | --locale)
+ ac_prev=localedir ;;
+ -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+ localedir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst | --locals)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+ ac_prev=pdfdir ;;
+ -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+ pdfdir=$ac_optarg ;;
+
+ -psdir | --psdir | --psdi | --psd | --ps)
+ ac_prev=psdir ;;
+ -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+ psdir=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=\$ac_optarg ;;
+
+ -without-* | --without-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=no ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ case $ac_envvar in #(
+ '' | [0-9]* | *[!_$as_cr_alnum]* )
+ as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+ esac
+ eval $ac_envvar=\$ac_optarg
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+ case $enable_option_checking in
+ no) ;;
+ fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+ *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+ esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
+ datadir sysconfdir sharedstatedir localstatedir includedir \
+ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+ libdir localedir mandir runstatedir
+do
+ eval ac_val=\$$ac_var
+ # Remove trailing slashes.
+ case $ac_val in
+ */ )
+ ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+ eval $ac_var=\$ac_val;;
+ esac
+ # Be sure to have absolute directory names.
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) continue;;
+ NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+ esac
+ as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+ as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+ as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then the parent directory.
+ ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_myself" : 'X\(//\)[^/]' \| \
+ X"$as_myself" : 'X\(//\)$' \| \
+ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r "$srcdir/$ac_unique_file"; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+ test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+ as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+ cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+ pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+ srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+ eval ac_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_env_${ac_var}_value=\$${ac_var}
+ eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+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.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking ...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --docdir=DIR documentation root [DATAROOTDIR/doc/tmux]
+ --htmldir=DIR html documentation [DOCDIR]
+ --dvidir=DIR dvi documentation [DOCDIR]
+ --pdfdir=DIR pdf documentation [DOCDIR]
+ --psdir=DIR ps documentation [DOCDIR]
+_ACEOF
+
+ cat <<\_ACEOF
+
+Program names:
+ --program-prefix=PREFIX prepend PREFIX to installed program names
+ --program-suffix=SUFFIX append SUFFIX to installed program names
+ --program-transform-name=PROGRAM run sed PROGRAM on installed program names
+
+System types:
+ --build=BUILD configure for building on BUILD [guessed]
+ --host=HOST cross-compile to build programs to run on HOST [BUILD]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of tmux 3.3a:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-option-checking ignore unrecognized --enable/--with options
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --enable-silent-rules less verbose build output (undo: "make V=1")
+ --disable-silent-rules verbose build output (undo: "make V=0")
+ --enable-fuzzing build fuzzers
+
+ --enable-dependency-tracking
+ do not reject slow dependency extractors
+ --disable-dependency-tracking
+ speeds up one-time build
+ --enable-debug enable debug build flags
+ --enable-static create a static build
+
+ --enable-utempter use utempter if it is installed
+
+ --enable-utf8proc use utf8proc if it is installed
+
+ --enable-systemd enable systemd integration
+
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-TERM set default TERM
+
+Some influential environment variables:
+ FUZZING_LIBS
+ libraries to link fuzzing targets with
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ LIBS libraries to pass to the linker, e.g. -l<library>
+ CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+ you have headers in a nonstandard directory <include dir>
+ CPP C preprocessor
+ YACC The `Yet Another Compiler Compiler' implementation to use.
+ Defaults to the first program found out of: `bison -y', `byacc',
+ `yacc'.
+ YFLAGS The list of arguments that will be passed by default to $YACC.
+ This script will default YFLAGS to the empty string to avoid a
+ default value of `-d' given by some make applications.
+ PKG_CONFIG path to pkg-config utility
+ PKG_CONFIG_PATH
+ directories to add to pkg-config's search path
+ PKG_CONFIG_LIBDIR
+ path overriding pkg-config's built-in search path
+ LIBEVENT_CORE_CFLAGS
+ C compiler flags for LIBEVENT_CORE, overriding pkg-config
+ LIBEVENT_CORE_LIBS
+ linker flags for LIBEVENT_CORE, overriding pkg-config
+ LIBEVENT_CFLAGS
+ C compiler flags for LIBEVENT, overriding pkg-config
+ LIBEVENT_LIBS
+ linker flags for LIBEVENT, overriding pkg-config
+ LIBTINFO_CFLAGS
+ C compiler flags for LIBTINFO, overriding pkg-config
+ LIBTINFO_LIBS
+ linker flags for LIBTINFO, overriding pkg-config
+ LIBNCURSES_CFLAGS
+ C compiler flags for LIBNCURSES, overriding pkg-config
+ LIBNCURSES_LIBS
+ linker flags for LIBNCURSES, overriding pkg-config
+ LIBNCURSESW_CFLAGS
+ C compiler flags for LIBNCURSESW, overriding pkg-config
+ LIBNCURSESW_LIBS
+ linker flags for LIBNCURSESW, overriding pkg-config
+ SYSTEMD_CFLAGS
+ C compiler flags for SYSTEMD, overriding pkg-config
+ SYSTEMD_LIBS
+ linker flags for SYSTEMD, overriding pkg-config
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to the package provider.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d "$ac_dir" ||
+ { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+ continue
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+ cd "$ac_dir" || { ac_status=$?; continue; }
+ # Check for guested configure.
+ if test -f "$ac_srcdir/configure.gnu"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+ elif test -f "$ac_srcdir/configure"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure" --help=recursive
+ else
+ $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi || ac_status=$?
+ cd "$ac_pwd" || { ac_status=$?; break; }
+ done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+ cat <<\_ACEOF
+tmux configure 3.3a
+generated by GNU Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext
+ if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } > conftest.i && {
+ test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if eval \${$3+:} false; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+ # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_header_compiler=yes
+else
+ ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ ac_header_preproc=yes
+else
+ ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So? What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+ yes:no: )
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+ ;;
+ no:yes:* )
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_mongrel
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: program exited with status $ac_status" >&5
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=$ac_status
+fi
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest$ac_exeext
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext && {
+ test "$cross_compiling" = yes ||
+ test -x conftest$ac_exeext
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+ # interfere with the next link command; also delete a directory that is
+ # left behind by Apple's compiler. We do this before executing the actions.
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+ For example, HP-UX 11i <limits.h> declares gettimeofday. */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $2 (); below.
+ Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ <limits.h> exists even on freestanding compilers. */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES
+# ---------------------------------------------
+# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
+# accordingly.
+ac_fn_c_check_decl ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ as_decl_name=`echo $2|sed 's/ *(.*//'`
+ as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
+$as_echo_n "checking whether $as_decl_name is declared... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+#ifndef $as_decl_name
+#ifdef __cplusplus
+ (void) $as_decl_use;
+#else
+ (void) $as_decl_name;
+#endif
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_decl
+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
+generated by GNU Autoconf 2.69. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ $as_echo "PATH: $as_dir"
+ done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+ for ac_arg
+ do
+ case $ac_arg in
+ -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ continue ;;
+ *\'*)
+ ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case $ac_pass in
+ 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+ 2)
+ as_fn_append ac_configure_args1 " '$ac_arg'"
+ if test $ac_must_keep_next = true; then
+ ac_must_keep_next=false # Got value, back to normal.
+ else
+ case $ac_arg in
+ *=* | --config-cache | -C | -disable-* | --disable-* \
+ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+ | -with-* | --with-* | -without-* | --without-* | --x)
+ case "$ac_configure_args0 " in
+ "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+ esac
+ ;;
+ -* ) ac_must_keep_next=true ;;
+ esac
+ fi
+ as_fn_append ac_configure_args " '$ac_arg'"
+ ;;
+ esac
+ done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+
+ $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+(
+ for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+ (set) 2>&1 |
+ case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ sed -n \
+ "s/'\''/'\''\\\\'\'''\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+ ;; #(
+ *)
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+)
+ echo
+
+ $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+ echo
+ for ac_var in $ac_subst_vars
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ $as_echo "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+
+ if test -n "$ac_subst_files"; then
+ $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+ echo
+ for ac_var in $ac_subst_files
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ $as_echo "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+ fi
+
+ if test -s confdefs.h; then
+ $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+ echo
+ cat confdefs.h
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ $as_echo "$as_me: caught signal $ac_signal"
+ $as_echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core *.core core.conftest.* &&
+ rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+ # We do not want a PATH search for config.site.
+ case $CONFIG_SITE in #((
+ -*) ac_site_file1=./$CONFIG_SITE;;
+ */*) ac_site_file1=$CONFIG_SITE;;
+ *) ac_site_file1=./$CONFIG_SITE;;
+ esac
+elif test "x$prefix" != xNONE; then
+ ac_site_file1=$prefix/share/config.site
+ ac_site_file2=$prefix/etc/config.site
+else
+ ac_site_file1=$ac_default_prefix/share/config.site
+ ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+ test "x$ac_site_file" = xNONE && continue
+ if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file" \
+ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special files
+ # actually), so we avoid doing that. DJGPP emulates it as a regular file.
+ if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . "$cache_file";;
+ *) . "./$cache_file";;
+ esac
+ fi
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val=\$ac_cv_env_${ac_var}_value
+ eval ac_new_val=\$ac_env_${ac_var}_value
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ # differences in whitespace do not lead to failure.
+ ac_old_val_w=`echo x $ac_old_val`
+ ac_new_val_w=`echo x $ac_new_val`
+ if test "$ac_old_val_w" != "$ac_new_val_w"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ ac_cache_corrupted=:
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+ eval $ac_var=\$ac_old_val
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5
+$as_echo "$as_me: former value: \`$ac_old_val'" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5
+$as_echo "$as_me: current value: \`$ac_new_val'" >&2;}
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+ac_aux_dir=
+for ac_dir in etc "$srcdir"/etc; do
+ if test -f "$ac_dir/install-sh"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f "$ac_dir/install.sh"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ elif test -f "$ac_dir/shtool"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/shtool install -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ as_fn_error $? "cannot find install-sh, install.sh, or shtool in etc \"$srcdir\"/etc" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
+
+
+
+am__api_version='1.15'
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+ ./ | .// | /[cC]/* | \
+ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+ /usr/ucb/* ) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+ if test $ac_prog = install &&
+ grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ elif test $ac_prog = install &&
+ grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # program-specific install script used by HP pwplus--don't use.
+ :
+ else
+ rm -rf conftest.one conftest.two conftest.dir
+ echo one > conftest.one
+ echo two > conftest.two
+ mkdir conftest.dir
+ if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+ test -s conftest.one && test -s conftest.two &&
+ test -s conftest.dir/conftest.one &&
+ test -s conftest.dir/conftest.two
+ then
+ ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+ break 3
+ fi
+ fi
+ fi
+ done
+ done
+ ;;
+esac
+
+ done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL=$ac_cv_path_install
+ else
+ # As a last resort, use the slow shell script. Don't cache a
+ # value for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the value is a relative name.
+ INSTALL=$ac_install_sh
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5
+$as_echo_n "checking whether build environment is sane... " >&6; }
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name. Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+ *[\\\"\#\$\&\'\`$am_lf]*)
+ as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;;
+esac
+case $srcdir in
+ *[\\\"\#\$\&\'\`$am_lf\ \ ]*)
+ as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments. Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+ am_has_slept=no
+ for am_try in 1 2; do
+ echo "timestamp, slept: $am_has_slept" > conftest.file
+ set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+ if test "$*" = "X"; then
+ # -L didn't work.
+ set X `ls -t "$srcdir/configure" conftest.file`
+ fi
+ if test "$*" != "X $srcdir/configure conftest.file" \
+ && test "$*" != "X conftest.file $srcdir/configure"; then
+
+ # If neither matched, then we have a broken ls. This can happen
+ # if, for instance, CONFIG_SHELL is bash and it inherits a
+ # broken ls alias from the environment. This has actually
+ # happened. Such a system could not be considered "sane".
+ as_fn_error $? "ls -t appears to fail. Make sure there is not a broken
+ alias in your environment" "$LINENO" 5
+ fi
+ if test "$2" = conftest.file || test $am_try -eq 2; then
+ break
+ fi
+ # Just in case.
+ sleep 1
+ am_has_slept=yes
+ done
+ test "$2" = conftest.file
+ )
+then
+ # Ok.
+ :
+else
+ as_fn_error $? "newly created file is older than distributed files!
+Check your system clock" "$LINENO" 5
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+ ( sleep 1 ) &
+ am_sleep_pid=$!
+fi
+
+rm -f conftest.file
+
+test "$program_prefix" != NONE &&
+ program_transform_name="s&^&$program_prefix&;$program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+ program_transform_name="s&\$&$program_suffix&;$program_transform_name"
+# Double any \ or $.
+# By default was `s,x,x', remove it if useless.
+ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
+program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
+
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+
+if test x"${MISSING+set}" != xset; then
+ case $am_aux_dir in
+ *\ * | *\ *)
+ MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+ *)
+ MISSING="\${SHELL} $am_aux_dir/missing" ;;
+ esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+ am_missing_run="$MISSING "
+else
+ am_missing_run=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5
+$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;}
+fi
+
+if test x"${install_sh+set}" != xset; then
+ case $am_aux_dir in
+ *\ * | *\ *)
+ install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+ *)
+ install_sh="\${SHELL} $am_aux_dir/install-sh"
+ esac
+fi
+
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip". However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+if test "$cross_compiling" != no; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; 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_STRIP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$STRIP"; then
+ ac_cv_prog_STRIP="$STRIP" # 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_STRIP="${ac_tool_prefix}strip"
+ $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
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+ ac_ct_STRIP=$STRIP
+ # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; 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_ac_ct_STRIP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_STRIP"; then
+ ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # 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_ac_ct_STRIP="strip"
+ $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
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_STRIP" = x; then
+ STRIP=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ STRIP=$ac_ct_STRIP
+ fi
+else
+ STRIP="$ac_cv_prog_STRIP"
+fi
+
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5
+$as_echo_n "checking for a thread-safe mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+ if ${ac_cv_path_mkdir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in mkdir gmkdir; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue
+ case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
+ 'mkdir (GNU coreutils) '* | \
+ 'mkdir (coreutils) '* | \
+ 'mkdir (fileutils) '4.1*)
+ ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
+ break 3;;
+ esac
+ done
+ done
+ done
+IFS=$as_save_IFS
+
+fi
+
+ test -d ./--version && rmdir ./--version
+ if test "${ac_cv_path_mkdir+set}" = set; then
+ MKDIR_P="$ac_cv_path_mkdir -p"
+ else
+ # As a last resort, use the slow shell script. Don't cache a
+ # value for MKDIR_P within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the value is a relative name.
+ MKDIR_P="$ac_install_sh -d"
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+$as_echo "$MKDIR_P" >&6; }
+
+for ac_prog in gawk mawk nawk awk
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; 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_AWK+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$AWK"; then
+ ac_cv_prog_AWK="$AWK" # 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_AWK="$ac_prog"
+ $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
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$AWK" && break
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+ @echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+ *@@@%%%=?*=@@@%%%*)
+ eval ac_cv_prog_make_${ac_make}_set=yes;;
+ *)
+ eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ SET_MAKE=
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+ am__leading_dot=.
+else
+ am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+
+# Check whether --enable-silent-rules was given.
+if test "${enable_silent_rules+set}" = set; then :
+ enableval=$enable_silent_rules;
+fi
+
+case $enable_silent_rules in # (((
+ yes) AM_DEFAULT_VERBOSITY=0;;
+ no) AM_DEFAULT_VERBOSITY=1;;
+ *) AM_DEFAULT_VERBOSITY=1;;
+esac
+am_make=${MAKE-make}
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5
+$as_echo_n "checking whether $am_make supports nested variables... " >&6; }
+if ${am_cv_make_support_nested_variables+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if $as_echo 'TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+ @$(TRUE)
+.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then
+ am_cv_make_support_nested_variables=yes
+else
+ am_cv_make_support_nested_variables=no
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5
+$as_echo "$am_cv_make_support_nested_variables" >&6; }
+if test $am_cv_make_support_nested_variables = yes; then
+ AM_V='$(V)'
+ AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+ AM_V=$AM_DEFAULT_VERBOSITY
+ AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AM_BACKSLASH='\'
+
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+ # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+ # is not polluted with repeated "-I."
+ am__isrc=' -I$(srcdir)'
+ # test to see if srcdir already configured
+ if test -f $srcdir/config.status; then
+ as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5
+ fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+ if (cygpath --version) >/dev/null 2>/dev/null; then
+ CYGPATH_W='cygpath -w'
+ else
+ CYGPATH_W=echo
+ fi
+fi
+
+
+# Define the identity of the package.
+ PACKAGE='tmux'
+ VERSION='3.3a'
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE "$PACKAGE"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define VERSION "$VERSION"
+_ACEOF
+
+# Some tools Automake needs.
+
+ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"}
+
+
+AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"}
+
+
+AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"}
+
+
+AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
+
+
+MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
+
+# For better backward compatibility. To be removed once Automake 1.9.x
+# dies out for good. For more background, see:
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+mkdir_p='$(MKDIR_P)'
+
+# We need awk for the "check" target (and possibly the TAP driver). The
+# system "awk" is bad on some platforms.
+# Always define AMTAR for backward compatibility. Yes, it's still used
+# in the wild :-( We should find a proper way to deprecate it ...
+AMTAR='$${TAR-tar}'
+
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar pax cpio none'
+
+am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'
+
+
+
+
+
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes. So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+ cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present. This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message. This
+can help us improve future automake versions.
+
+END
+ if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+ echo 'Configuration will proceed anyway, since you have set the' >&2
+ echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+ echo >&2
+ else
+ cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <http://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+ as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5
+ fi
+fi
+
+
+# Make sure we can run config.sub.
+$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
+ as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
+$as_echo_n "checking build system type... " >&6; }
+if ${ac_cv_build+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_build_alias=$build_alias
+test "x$ac_build_alias" = x &&
+ ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
+test "x$ac_build_alias" = x &&
+ as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
+ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
+ as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
+$as_echo "$ac_cv_build" >&6; }
+case $ac_cv_build in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
+esac
+build=$ac_cv_build
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_build
+shift
+build_cpu=$1
+build_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+build_os=$*
+IFS=$ac_save_IFS
+case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
+$as_echo_n "checking host system type... " >&6; }
+if ${ac_cv_host+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "x$host_alias" = x; then
+ ac_cv_host=$ac_cv_build
+else
+ ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
+ as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
+$as_echo "$ac_cv_host" >&6; }
+case $ac_cv_host in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
+esac
+host=$ac_cv_host
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_host
+shift
+host_cpu=$1
+host_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+host_os=$*
+IFS=$ac_save_IFS
+case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
+
+
+
+# When CFLAGS isn't set at this stage and gcc is detected by the macro below,
+# autoconf will automatically use CFLAGS="-O2 -g". Prevent that by using an
+# empty default.
+: ${CFLAGS=""}
+
+# Save user CPPFLAGS, CFLAGS and LDFLAGS. We need to change them because
+# AC_CHECK_HEADER doesn't give us any other way to update the include
+# paths. But for Makefile.am we want to use AM_CPPFLAGS and friends.
+SAVED_CFLAGS="$CFLAGS"
+SAVED_CPPFLAGS="$CPPFLAGS"
+SAVED_LDFLAGS="$LDFLAGS"
+
+# Is this oss-fuzz build?
+# Check whether --enable-fuzzing was given.
+if test "${enable_fuzzing+set}" = set; then :
+ enableval=$enable_fuzzing;
+fi
+
+
+
+# Set up convenient fuzzing defaults before initializing compiler.
+if test "x$enable_fuzzing" = xyes; then
+ $as_echo "#define NEED_FUZZING 1" >>confdefs.h
+
+ test "x$CC" = x && CC=clang
+ test "x$FUZZING_LIBS" = x && \
+ FUZZING_LIBS="-fsanitize=fuzzer"
+ test "x$SAVED_CFLAGS" = x && \
+ AM_CFLAGS="-g -fsanitize=fuzzer-no-link,address"
+fi
+
+# Set up the compiler in two different ways and say yes we may want to install.
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; 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_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # 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_CC="${ac_tool_prefix}gcc"
+ $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
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; 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_ac_ct_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # 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_ac_ct_CC="gcc"
+ $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
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; 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_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # 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_CC="${ac_tool_prefix}cc"
+ $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
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ fi
+fi
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; 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_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ ac_prog_rejected=no
+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
+ if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ $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
+
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# != 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+ fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in cl.exe
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; 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_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # 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_CC="$ac_tool_prefix$ac_prog"
+ $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
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$CC" && break
+ done
+fi
+if test -z "$CC"; then
+ ac_ct_CC=$CC
+ for ac_prog in cl.exe
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; 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_ac_ct_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # 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_ac_ct_CC="$ac_prog"
+ $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
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_CC" && break
+done
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+ { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ sed '10a\
+... rest of stderr output deleted ...
+ 10q' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ fi
+ rm -f conftest.er1 conftest.err
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+ esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link_default") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile. We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+ ;;
+ [ab].out )
+ # We found the default executable, but exeext='' is most
+ # certainly right.
+ break;;
+ *.* )
+ if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+ then :; else
+ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ fi
+ # We set ac_cv_exeext here because the later test for it is not
+ # safe: cross compilers may not add the suffix if given an `-o'
+ # argument, so we may need to know it at that point already.
+ # Even if this section looks crufty: it has the advantage of
+ # actually working.
+ break;;
+ * )
+ break;;
+ esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+ ac_file=''
+fi
+if test -z "$ac_file"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ break;;
+ * ) break;;
+ esac
+done
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+ { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if { ac_try='./conftest$ac_cv_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then
+ cross_compiling=no
+ else
+ if test "$cross_compiling" = maybe; then
+ cross_compiling=yes
+ else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ for ac_file in conftest.o conftest.obj conftest.*; do
+ test -f "$ac_file" || continue;
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+ *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+ break;;
+ esac
+done
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_compiler_gnu=yes
+else
+ ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+ GCC=yes
+else
+ GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_save_c_werror_flag=$ac_c_werror_flag
+ ac_c_werror_flag=yes
+ ac_cv_prog_cc_g=no
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_g=yes
+else
+ CFLAGS=""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ ac_c_werror_flag=$ac_save_c_werror_flag
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+ char **p;
+ int i;
+{
+ return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+ char *s;
+ va_list v;
+ va_start (v,p);
+ s = g (p, va_arg (v,int));
+ va_end (v);
+ return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
+ function prototypes and stuff, but not '\xHH' hex character constants.
+ These don't provoke an error unfortunately, instead are silently treated
+ as 'x'. The following induces an error, until -std is added to get
+ proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
+ array size at least. It's necessary to write '\x00'==0 to get something
+ that's true only with -std. */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+ inside strings and character constants. */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
+ ;
+ return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+ test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+ x)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+ xno)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+ *)
+ CC="$CC $ac_cv_prog_cc_c89"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5
+$as_echo_n "checking whether $CC understands -c and -o together... " >&6; }
+if ${am_cv_prog_cc_c_o+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ # Make sure it works both with $CC and with simple cc.
+ # Following AC_PROG_CC_C_O, we do the test twice because some
+ # compilers refuse to overwrite an existing .o file with -o,
+ # though they will create one.
+ am_cv_prog_cc_c_o=yes
+ for am_i in 1 2; do
+ if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5
+ ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } \
+ && test -f conftest2.$ac_objext; then
+ : OK
+ else
+ am_cv_prog_cc_c_o=no
+ break
+ fi
+ done
+ rm -f core conftest*
+ unset am_i
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5
+$as_echo "$am_cv_prog_cc_c_o" >&6; }
+if test "$am_cv_prog_cc_c_o" != yes; then
+ # Losing compiler, so override with the script.
+ # FIXME: It is wrong to rewrite CC.
+ # But if we don't then we get into trouble of one sort or another.
+ # A longer-term fix would be to have automake use am__CC in this case,
+ # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+ CC="$am_aux_dir/compile $CC"
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+DEPDIR="${am__leading_dot}deps"
+
+ac_config_commands="$ac_config_commands depfiles"
+
+
+am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+ @echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
+$as_echo_n "checking for style of include used by $am_make... " >&6; }
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from 'make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+ am__include=include
+ am__quote=
+ _am_result=GNU
+ ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+ echo '.include "confinc"' > confmf
+ case `$am_make -s -f confmf 2> /dev/null` in #(
+ *the\ am__doit\ target*)
+ am__include=.include
+ am__quote="\""
+ _am_result=BSD
+ ;;
+ esac
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
+$as_echo "$_am_result" >&6; }
+rm -f confinc confmf
+
+# Check whether --enable-dependency-tracking was given.
+if test "${enable_dependency_tracking+set}" = set; then :
+ enableval=$enable_dependency_tracking;
+fi
+
+if test "x$enable_dependency_tracking" != xno; then
+ am_depcomp="$ac_aux_dir/depcomp"
+ AMDEPBACKSLASH='\'
+ am__nodep='_no'
+fi
+ if test "x$enable_dependency_tracking" != xno; then
+ AMDEP_TRUE=
+ AMDEP_FALSE='#'
+else
+ AMDEP_TRUE='#'
+ AMDEP_FALSE=
+fi
+
+
+
+depcc="$CC" am_compiler_list=
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
+$as_echo_n "checking dependency style of $depcc... " >&6; }
+if ${am_cv_CC_dependencies_compiler_type+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+ # We make a subdir and do the tests there. Otherwise we can end up
+ # making bogus files that we don't know about and never remove. For
+ # instance it was reported that on HP-UX the gcc test will end up
+ # making a dummy file named 'D' -- because '-MD' means "put the output
+ # in D".
+ rm -rf conftest.dir
+ mkdir conftest.dir
+ # Copy depcomp to subdir because otherwise we won't find it if we're
+ # using a relative directory.
+ cp "$am_depcomp" conftest.dir
+ cd conftest.dir
+ # We will build objects and dependencies in a subdirectory because
+ # it helps to detect inapplicable dependency modes. For instance
+ # both Tru64's cc and ICC support -MD to output dependencies as a
+ # side effect of compilation, but ICC will put the dependencies in
+ # the current directory while Tru64 will put them in the object
+ # directory.
+ mkdir sub
+
+ am_cv_CC_dependencies_compiler_type=none
+ if test "$am_compiler_list" = ""; then
+ am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
+ fi
+ am__universal=false
+ case " $depcc " in #(
+ *\ -arch\ *\ -arch\ *) am__universal=true ;;
+ esac
+
+ for depmode in $am_compiler_list; do
+ # Setup a source with many dependencies, because some compilers
+ # like to wrap large dependency lists on column 80 (with \), and
+ # we should not choose a depcomp mode which is confused by this.
+ #
+ # We need to recreate these files for each test, as the compiler may
+ # overwrite some of them when testing with obscure command lines.
+ # This happens at least with the AIX C compiler.
+ : > sub/conftest.c
+ for i in 1 2 3 4 5 6; do
+ echo '#include "conftst'$i'.h"' >> sub/conftest.c
+ # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+ # Solaris 10 /bin/sh.
+ echo '/* dummy */' > sub/conftst$i.h
+ done
+ echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+ # We check with '-c' and '-o' for the sake of the "dashmstdout"
+ # mode. It turns out that the SunPro C++ compiler does not properly
+ # handle '-M -o', and we need to detect this. Also, some Intel
+ # versions had trouble with output in subdirs.
+ am__obj=sub/conftest.${OBJEXT-o}
+ am__minus_obj="-o $am__obj"
+ case $depmode in
+ gcc)
+ # This depmode causes a compiler race in universal mode.
+ test "$am__universal" = false || continue
+ ;;
+ nosideeffect)
+ # After this tag, mechanisms are not by side-effect, so they'll
+ # only be used when explicitly requested.
+ if test "x$enable_dependency_tracking" = xyes; then
+ continue
+ else
+ break
+ fi
+ ;;
+ msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+ # This compiler won't grok '-c -o', but also, the minuso test has
+ # not run yet. These depmodes are late enough in the game, and
+ # so weak that their functioning should not be impacted.
+ am__obj=conftest.${OBJEXT-o}
+ am__minus_obj=
+ ;;
+ none) break ;;
+ esac
+ if depmode=$depmode \
+ source=sub/conftest.c object=$am__obj \
+ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+ >/dev/null 2>conftest.err &&
+ grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+ ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+ # icc doesn't choke on unknown options, it will just issue warnings
+ # or remarks (even with -Werror). So we grep stderr for any message
+ # that says an option was ignored or not supported.
+ # When given -MP, icc 7.0 and 7.1 complain thusly:
+ # icc: Command line warning: ignoring option '-M'; no argument required
+ # The diagnosis changed in icc 8.0:
+ # icc: Command line remark: option '-MP' not supported
+ if (grep 'ignoring option' conftest.err ||
+ grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+ am_cv_CC_dependencies_compiler_type=$depmode
+ break
+ fi
+ fi
+ done
+
+ cd ..
+ rm -rf conftest.dir
+else
+ am_cv_CC_dependencies_compiler_type=none
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5
+$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; }
+CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
+
+ if
+ test "x$enable_dependency_tracking" != xno \
+ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
+ am__fastdepCC_TRUE=
+ am__fastdepCC_FALSE='#'
+else
+ am__fastdepCC_TRUE='#'
+ am__fastdepCC_FALSE=
+fi
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5
+$as_echo_n "checking for $CC option to accept ISO C99... " >&6; }
+if ${ac_cv_prog_cc_c99+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_prog_cc_c99=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <stdio.h>
+
+// Check varargs macros. These examples are taken from C99 6.10.3.5.
+#define debug(...) fprintf (stderr, __VA_ARGS__)
+#define showlist(...) puts (#__VA_ARGS__)
+#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
+static void
+test_varargs_macros (void)
+{
+ int x = 1234;
+ int y = 5678;
+ debug ("Flag");
+ debug ("X = %d\n", x);
+ showlist (The first, second, and third items.);
+ report (x>y, "x is %d but y is %d", x, y);
+}
+
+// Check long long types.
+#define BIG64 18446744073709551615ull
+#define BIG32 4294967295ul
+#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
+#if !BIG_OK
+ your preprocessor is broken;
+#endif
+#if BIG_OK
+#else
+ your preprocessor is broken;
+#endif
+static long long int bignum = -9223372036854775807LL;
+static unsigned long long int ubignum = BIG64;
+
+struct incomplete_array
+{
+ int datasize;
+ double data[];
+};
+
+struct named_init {
+ int number;
+ const wchar_t *name;
+ double average;
+};
+
+typedef const char *ccp;
+
+static inline int
+test_restrict (ccp restrict text)
+{
+ // See if C++-style comments work.
+ // Iterate through items via the restricted pointer.
+ // Also check for declarations in for loops.
+ for (unsigned int i = 0; *(text+i) != '\0'; ++i)
+ continue;
+ return 0;
+}
+
+// Check varargs and va_copy.
+static void
+test_varargs (const char *format, ...)
+{
+ va_list args;
+ va_start (args, format);
+ va_list args_copy;
+ va_copy (args_copy, args);
+
+ const char *str;
+ int number;
+ float fnumber;
+
+ while (*format)
+ {
+ switch (*format++)
+ {
+ case 's': // string
+ str = va_arg (args_copy, const char *);
+ break;
+ case 'd': // int
+ number = va_arg (args_copy, int);
+ break;
+ case 'f': // float
+ fnumber = va_arg (args_copy, double);
+ break;
+ default:
+ break;
+ }
+ }
+ va_end (args_copy);
+ va_end (args);
+}
+
+int
+main ()
+{
+
+ // Check bool.
+ _Bool success = false;
+
+ // Check restrict.
+ if (test_restrict ("String literal") == 0)
+ success = true;
+ char *restrict newvar = "Another string";
+
+ // Check varargs.
+ test_varargs ("s, d' f .", "string", 65, 34.234);
+ test_varargs_macros ();
+
+ // Check flexible array members.
+ struct incomplete_array *ia =
+ malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
+ ia->datasize = 10;
+ for (int i = 0; i < ia->datasize; ++i)
+ ia->data[i] = i * 1.234;
+
+ // Check named initializers.
+ struct named_init ni = {
+ .number = 34,
+ .name = L"Test wide string",
+ .average = 543.34343,
+ };
+
+ ni.number = 58;
+
+ int dynamic_array[ni.number];
+ dynamic_array[ni.number - 1] = 543;
+
+ // work around unused variable warnings
+ return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x'
+ || dynamic_array[ni.number - 1] != 543);
+
+ ;
+ return 0;
+}
+_ACEOF
+for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_c99=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+ test "x$ac_cv_prog_cc_c99" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c99" in
+ x)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+ xno)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+ *)
+ CC="$CC $ac_cv_prog_cc_c99"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
+$as_echo "$ac_cv_prog_cc_c99" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c99" != xno; then :
+
+fi
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+ if ${ac_cv_prog_CPP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # Double quotes because CPP needs to be expanded
+ for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+ do
+ ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+ break
+fi
+
+ done
+ ac_cv_prog_CPP=$CPP
+
+fi
+ CPP=$ac_cv_prog_CPP
+else
+ ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$GREP"; then
+ ac_path_GREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in grep ggrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+ # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'GREP' >> "conftest.nl"
+ "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_GREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_GREP="$ac_path_GREP"
+ ac_path_GREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_GREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_GREP"; then
+ as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+ then ac_cv_path_EGREP="$GREP -E"
+ else
+ if test -z "$EGREP"; then
+ ac_path_EGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in egrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+ # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'EGREP' >> "conftest.nl"
+ "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_EGREP="$ac_path_EGREP"
+ ac_path_EGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_EGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_EGREP"; then
+ as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_EGREP=$EGREP
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+
+for ac_prog in 'bison -y' byacc
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; 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_YACC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$YACC"; then
+ ac_cv_prog_YACC="$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_YACC="$ac_prog"
+ $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
+
+fi
+fi
+YACC=$ac_cv_prog_YACC
+if test -n "$YACC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $YACC" >&5
+$as_echo "$YACC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$YACC" && break
+done
+test -n "$YACC" || YACC="yacc"
+
+
+
+
+
+
+
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
+set dummy ${ac_tool_prefix}pkg-config; 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_path_PKG_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $PKG_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ 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_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $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
+
+ ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_PKG_CONFIG"; then
+ ac_pt_PKG_CONFIG=$PKG_CONFIG
+ # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; 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_path_ac_pt_PKG_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $ac_pt_PKG_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ 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_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $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
+
+ ;;
+esac
+fi
+ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
+if test -n "$ac_pt_PKG_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_pt_PKG_CONFIG" = x; then
+ PKG_CONFIG=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ PKG_CONFIG=$ac_pt_PKG_CONFIG
+ fi
+else
+ PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+fi
+
+fi
+if test -n "$PKG_CONFIG"; then
+ _pkg_min_version=0.9.0
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+ if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ PKG_CONFIG=""
+ fi
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_header_stdc=yes
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "free" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+ if test "$cross_compiling" = yes; then :
+ :
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+ (('a' <= (c) && (c) <= 'i') \
+ || ('j' <= (c) && (c) <= 'r') \
+ || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (XOR (islower (i), ISLOWER (i))
+ || toupper (i) != TOUPPER (i))
+ return 2;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+ inttypes.h stdint.h unistd.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "minix/config.h" "ac_cv_header_minix_config_h" "$ac_includes_default"
+if test "x$ac_cv_header_minix_config_h" = xyes; then :
+ MINIX=yes
+else
+ MINIX=
+fi
+
+
+ if test "$MINIX" = yes; then
+
+$as_echo "#define _POSIX_SOURCE 1" >>confdefs.h
+
+
+$as_echo "#define _POSIX_1_SOURCE 2" >>confdefs.h
+
+
+$as_echo "#define _MINIX 1" >>confdefs.h
+
+ fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether it is safe to define __EXTENSIONS__" >&5
+$as_echo_n "checking whether it is safe to define __EXTENSIONS__... " >&6; }
+if ${ac_cv_safe_to_define___extensions__+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+# define __EXTENSIONS__ 1
+ $ac_includes_default
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_safe_to_define___extensions__=yes
+else
+ ac_cv_safe_to_define___extensions__=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_safe_to_define___extensions__" >&5
+$as_echo "$ac_cv_safe_to_define___extensions__" >&6; }
+ test $ac_cv_safe_to_define___extensions__ = yes &&
+ $as_echo "#define __EXTENSIONS__ 1" >>confdefs.h
+
+ $as_echo "#define _ALL_SOURCE 1" >>confdefs.h
+
+ $as_echo "#define _GNU_SOURCE 1" >>confdefs.h
+
+ $as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h
+
+ $as_echo "#define _TANDEM_SOURCE 1" >>confdefs.h
+
+
+
+# Default tmux.conf goes in /etc not ${prefix}/etc.
+test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc
+
+# Is this --enable-debug?
+case "x$VERSION" in xnext*) enable_debug=yes;; esac
+# Check whether --enable-debug was given.
+if test "${enable_debug+set}" = set; then :
+ enableval=$enable_debug;
+fi
+
+ if test "x$enable_debug" = xyes; then
+ IS_DEBUG_TRUE=
+ IS_DEBUG_FALSE='#'
+else
+ IS_DEBUG_TRUE='#'
+ IS_DEBUG_FALSE=
+fi
+
+
+# Is this a static build?
+# Check whether --enable-static was given.
+if test "${enable_static+set}" = set; then :
+ enableval=$enable_static;
+fi
+
+if test "x$enable_static" = xyes; then
+ case "$host_os" in
+ *darwin*)
+ as_fn_error $? "static linking is not supported on macOS" "$LINENO" 5
+ ;;
+ esac
+ test "x$PKG_CONFIG" != x && PKG_CONFIG="$PKG_CONFIG --static"
+ AM_LDFLAGS="-static $AM_LDFLAGS"
+ LDFLAGS="$AM_LDFLAGS $SAVED_LDFLAGS"
+fi
+
+# Allow default TERM to be set.
+
+# Check whether --with-TERM was given.
+if test "${with_TERM+set}" = set; then :
+ withval=$with_TERM; DEFAULT_TERM=$withval
+else
+ DEFAULT_TERM=
+
+fi
+
+case "x$DEFAULT_TERM" in
+ xscreen*|xtmux*|x)
+ ;;
+ *)
+ as_fn_error $? "\"unsuitable TERM (must be screen* or tmux*)\"" "$LINENO" 5
+ ;;
+esac
+
+# Do we need fuzzers?
+ if test "x$enable_fuzzing" = xyes; then
+ NEED_FUZZING_TRUE=
+ NEED_FUZZING_FALSE='#'
+else
+ NEED_FUZZING_TRUE='#'
+ NEED_FUZZING_FALSE=
+fi
+
+
+# Is this gcc?
+ if test "x$GCC" = xyes -a "x$enable_fuzzing" != xyes; then
+ IS_GCC_TRUE=
+ IS_GCC_FALSE='#'
+else
+ IS_GCC_TRUE='#'
+ IS_GCC_FALSE=
+fi
+
+
+# Is this Sun CC?
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #ifdef __SUNPRO_C
+ yes
+ #endif
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "yes" >/dev/null 2>&1; then :
+ found_suncc=yes
+else
+ found_suncc=no
+
+fi
+rm -f conftest*
+
+ if test "x$found_suncc" = xyes; then
+ IS_SUNCC_TRUE=
+ IS_SUNCC_FALSE='#'
+else
+ IS_SUNCC_TRUE='#'
+ IS_SUNCC_FALSE=
+fi
+
+
+# Check for various headers. Alternatives included from compat.h.
+for ac_header in \
+ bitstring.h \
+ dirent.h \
+ fcntl.h \
+ inttypes.h \
+ libproc.h \
+ libutil.h \
+ ndir.h \
+ paths.h \
+ pty.h \
+ stdint.h \
+ sys/dir.h \
+ sys/ndir.h \
+ sys/tree.h \
+ ucred.h \
+ util.h \
+
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+# Look for sys_signame.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing sys_signame" >&5
+$as_echo_n "checking for library containing sys_signame... " >&6; }
+if ${ac_cv_search_sys_signame+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char sys_signame ();
+int
+main ()
+{
+return sys_signame ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' ; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_sys_signame=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_sys_signame+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_sys_signame+:} false; then :
+
+else
+ ac_cv_search_sys_signame=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_sys_signame" >&5
+$as_echo "$ac_cv_search_sys_signame" >&6; }
+ac_res=$ac_cv_search_sys_signame
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ $as_echo "#define HAVE_SYS_SIGNAME 1" >>confdefs.h
+
+fi
+
+
+# Look for fmod.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fmod in -lm" >&5
+$as_echo_n "checking for fmod in -lm... " >&6; }
+if ${ac_cv_lib_m_fmod+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lm $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char fmod ();
+int
+main ()
+{
+return fmod ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_m_fmod=yes
+else
+ ac_cv_lib_m_fmod=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_fmod" >&5
+$as_echo "$ac_cv_lib_m_fmod" >&6; }
+if test "x$ac_cv_lib_m_fmod" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBM 1
+_ACEOF
+
+ LIBS="-lm $LIBS"
+
+fi
+
+
+# Look for library needed for flock.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing flock" >&5
+$as_echo_n "checking for library containing flock... " >&6; }
+if ${ac_cv_search_flock+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char flock ();
+int
+main ()
+{
+return flock ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' bsd; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_flock=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_flock+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_flock+:} false; then :
+
+else
+ ac_cv_search_flock=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_flock" >&5
+$as_echo "$ac_cv_search_flock" >&6; }
+ac_res=$ac_cv_search_flock
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+
+# Check for functions that are replaced or omitted.
+for ac_func in \
+ dirfd \
+ flock \
+ prctl \
+ proc_pidinfo \
+ getpeerucred \
+ sysconf \
+
+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
+
+
+# Check for functions with a compatibility implementation.
+ac_fn_c_check_func "$LINENO" "asprintf" "ac_cv_func_asprintf"
+if test "x$ac_cv_func_asprintf" = xyes; then :
+ $as_echo "#define HAVE_ASPRINTF 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" asprintf.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS asprintf.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "cfmakeraw" "ac_cv_func_cfmakeraw"
+if test "x$ac_cv_func_cfmakeraw" = xyes; then :
+ $as_echo "#define HAVE_CFMAKERAW 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" cfmakeraw.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS cfmakeraw.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "clock_gettime" "ac_cv_func_clock_gettime"
+if test "x$ac_cv_func_clock_gettime" = xyes; then :
+ $as_echo "#define HAVE_CLOCK_GETTIME 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" clock_gettime.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS clock_gettime.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "closefrom" "ac_cv_func_closefrom"
+if test "x$ac_cv_func_closefrom" = xyes; then :
+ $as_echo "#define HAVE_CLOSEFROM 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" closefrom.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS closefrom.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero"
+if test "x$ac_cv_func_explicit_bzero" = xyes; then :
+ $as_echo "#define HAVE_EXPLICIT_BZERO 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" explicit_bzero.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS explicit_bzero.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "fgetln" "ac_cv_func_fgetln"
+if test "x$ac_cv_func_fgetln" = xyes; then :
+ $as_echo "#define HAVE_FGETLN 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" fgetln.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS fgetln.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "freezero" "ac_cv_func_freezero"
+if test "x$ac_cv_func_freezero" = xyes; then :
+ $as_echo "#define HAVE_FREEZERO 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" freezero.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS freezero.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "getdtablecount" "ac_cv_func_getdtablecount"
+if test "x$ac_cv_func_getdtablecount" = xyes; then :
+ $as_echo "#define HAVE_GETDTABLECOUNT 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" getdtablecount.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS getdtablecount.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "getdtablesize" "ac_cv_func_getdtablesize"
+if test "x$ac_cv_func_getdtablesize" = xyes; then :
+ $as_echo "#define HAVE_GETDTABLESIZE 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" getdtablesize.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS getdtablesize.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "getpeereid" "ac_cv_func_getpeereid"
+if test "x$ac_cv_func_getpeereid" = xyes; then :
+ $as_echo "#define HAVE_GETPEEREID 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" getpeereid.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS getpeereid.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "getline" "ac_cv_func_getline"
+if test "x$ac_cv_func_getline" = xyes; then :
+ $as_echo "#define HAVE_GETLINE 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" getline.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS getline.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "getprogname" "ac_cv_func_getprogname"
+if test "x$ac_cv_func_getprogname" = xyes; then :
+ $as_echo "#define HAVE_GETPROGNAME 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" getprogname.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS getprogname.$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
+
+else
+ case " $LIBOBJS " in
+ *" memmem.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS memmem.$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
+
+else
+ case " $LIBOBJS " in
+ *" setenv.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS setenv.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "setproctitle" "ac_cv_func_setproctitle"
+if test "x$ac_cv_func_setproctitle" = xyes; then :
+ $as_echo "#define HAVE_SETPROCTITLE 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" setproctitle.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS setproctitle.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "strcasestr" "ac_cv_func_strcasestr"
+if test "x$ac_cv_func_strcasestr" = xyes; then :
+ $as_echo "#define HAVE_STRCASESTR 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" strcasestr.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strcasestr.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "strlcat" "ac_cv_func_strlcat"
+if test "x$ac_cv_func_strlcat" = xyes; then :
+ $as_echo "#define HAVE_STRLCAT 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" strlcat.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strlcat.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy"
+if test "x$ac_cv_func_strlcpy" = xyes; then :
+ $as_echo "#define HAVE_STRLCPY 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" strlcpy.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strlcpy.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "strndup" "ac_cv_func_strndup"
+if test "x$ac_cv_func_strndup" = xyes; then :
+ $as_echo "#define HAVE_STRNDUP 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" strndup.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strndup.$ac_objext"
+ ;;
+esac
+
+fi
+
+ac_fn_c_check_func "$LINENO" "strsep" "ac_cv_func_strsep"
+if test "x$ac_cv_func_strsep" = xyes; then :
+ $as_echo "#define HAVE_STRSEP 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" strsep.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strsep.$ac_objext"
+ ;;
+esac
+
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working strnlen" >&5
+$as_echo_n "checking for working strnlen... " >&6; }
+if ${ac_cv_func_strnlen_working+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "$cross_compiling" = yes; then :
+ # Guess no on AIX systems, yes otherwise.
+ case "$host_os" in
+ aix*) ac_cv_func_strnlen_working=no;;
+ *) ac_cv_func_strnlen_working=yes;;
+ esac
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_includes_default
+int
+main ()
+{
+
+#define S "foobar"
+#define S_LEN (sizeof S - 1)
+
+ /* At least one implementation is buggy: that of AIX 4.3 would
+ give strnlen (S, 1) == 3. */
+
+ int i;
+ for (i = 0; i < S_LEN + 1; ++i)
+ {
+ int expected = i <= S_LEN ? i : S_LEN;
+ if (strnlen (S, i) != expected)
+ return 1;
+ }
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ ac_cv_func_strnlen_working=yes
+else
+ ac_cv_func_strnlen_working=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_strnlen_working" >&5
+$as_echo "$ac_cv_func_strnlen_working" >&6; }
+test $ac_cv_func_strnlen_working = no && case " $LIBOBJS " in
+ *" strnlen.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strnlen.$ac_objext"
+ ;;
+esac
+
+
+
+# Check if strtonum works.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for working strtonum" >&5
+$as_echo_n "checking for working strtonum... " >&6; }
+if test "$cross_compiling" = yes; then :
+ case " $LIBOBJS " in
+ *" strtonum.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strtonum.$ac_objext"
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+int
+main ()
+{
+return (strtonum("0", 0, 1, NULL) == 0 ? 0 : 1);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ $as_echo "#define HAVE_STRTONUM 1" >>confdefs.h
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ case " $LIBOBJS " in
+ *" strtonum.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS strtonum.$ac_objext"
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+
+# Clang sanitizers wrap reallocarray even if it isn't available on the target
+# system. When compiled it always returns NULL and crashes the program. To
+# detect this we need a more complicated test.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for working reallocarray" >&5
+$as_echo_n "checking for working reallocarray... " >&6; }
+if test "$cross_compiling" = yes; then :
+ case " $LIBOBJS " in
+ *" reallocarray.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS reallocarray.$ac_objext"
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+int
+main ()
+{
+return (reallocarray(NULL, 1, 1) == NULL);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ case " $LIBOBJS " in
+ *" reallocarray.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS reallocarray.$ac_objext"
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for working recallocarray" >&5
+$as_echo_n "checking for working recallocarray... " >&6; }
+if test "$cross_compiling" = yes; then :
+ case " $LIBOBJS " in
+ *" recallocarray.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS recallocarray.$ac_objext"
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+int
+main ()
+{
+return (recallocarray(NULL, 1, 1, 1) == NULL);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ case " $LIBOBJS " in
+ *" recallocarray.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS recallocarray.$ac_objext"
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+
+# Look for clock_gettime. Must come before event_init.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
+$as_echo_n "checking for library containing clock_gettime... " >&6; }
+if ${ac_cv_search_clock_gettime+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char clock_gettime ();
+int
+main ()
+{
+return clock_gettime ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' rt; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_clock_gettime=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_clock_gettime+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_clock_gettime+:} false; then :
+
+else
+ ac_cv_search_clock_gettime=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_clock_gettime" >&5
+$as_echo "$ac_cv_search_clock_gettime" >&6; }
+ac_res=$ac_cv_search_clock_gettime
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+
+# Always use our getopt because 1) glibc's doesn't enforce argument order 2)
+# musl does not set optarg to NULL for flags without arguments (although it is
+# not required to, but it is helpful) 3) there are probably other weird
+# implementations.
+case " $LIBOBJS " in
+ *" getopt.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS getopt.$ac_objext"
+ ;;
+esac
+
+
+# Look for libevent. Try libevent_core or libevent with pkg-config first then
+# look for the library.
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libevent_core >= 2" >&5
+$as_echo_n "checking for libevent_core >= 2... " >&6; }
+
+if test -n "$LIBEVENT_CORE_CFLAGS"; then
+ pkg_cv_LIBEVENT_CORE_CFLAGS="$LIBEVENT_CORE_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libevent_core >= 2\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libevent_core >= 2") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBEVENT_CORE_CFLAGS=`$PKG_CONFIG --cflags "libevent_core >= 2" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$LIBEVENT_CORE_LIBS"; then
+ pkg_cv_LIBEVENT_CORE_LIBS="$LIBEVENT_CORE_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libevent_core >= 2\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libevent_core >= 2") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBEVENT_CORE_LIBS=`$PKG_CONFIG --libs "libevent_core >= 2" 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
+ LIBEVENT_CORE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libevent_core >= 2" 2>&1`
+ else
+ LIBEVENT_CORE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libevent_core >= 2" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$LIBEVENT_CORE_PKG_ERRORS" >&5
+
+ found_libevent=no
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ found_libevent=no
+
+else
+ LIBEVENT_CORE_CFLAGS=$pkg_cv_LIBEVENT_CORE_CFLAGS
+ LIBEVENT_CORE_LIBS=$pkg_cv_LIBEVENT_CORE_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ AM_CPPFLAGS="$LIBEVENT_CORE_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBEVENT_CORE_LIBS $LIBS"
+ found_libevent=yes
+
+fi
+if test x$found_libevent = xno; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libevent >= 2" >&5
+$as_echo_n "checking for libevent >= 2... " >&6; }
+
+if test -n "$LIBEVENT_CFLAGS"; then
+ pkg_cv_LIBEVENT_CFLAGS="$LIBEVENT_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libevent >= 2\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libevent >= 2") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBEVENT_CFLAGS=`$PKG_CONFIG --cflags "libevent >= 2" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$LIBEVENT_LIBS"; then
+ pkg_cv_LIBEVENT_LIBS="$LIBEVENT_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libevent >= 2\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libevent >= 2") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBEVENT_LIBS=`$PKG_CONFIG --libs "libevent >= 2" 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
+ LIBEVENT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libevent >= 2" 2>&1`
+ else
+ LIBEVENT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libevent >= 2" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$LIBEVENT_PKG_ERRORS" >&5
+
+ found_libevent=no
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ found_libevent=no
+
+else
+ LIBEVENT_CFLAGS=$pkg_cv_LIBEVENT_CFLAGS
+ LIBEVENT_LIBS=$pkg_cv_LIBEVENT_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBEVENT_LIBS $LIBS"
+ found_libevent=yes
+
+fi
+fi
+if test x$found_libevent = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing event_init" >&5
+$as_echo_n "checking for library containing event_init... " >&6; }
+if ${ac_cv_search_event_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char event_init ();
+int
+main ()
+{
+return event_init ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' event_core event event-1.4; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_event_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_event_init+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_event_init+:} false; then :
+
+else
+ ac_cv_search_event_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_event_init" >&5
+$as_echo "$ac_cv_search_event_init" >&6; }
+ac_res=$ac_cv_search_event_init
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ found_libevent=yes
+else
+ found_libevent=no
+
+fi
+
+fi
+ac_fn_c_check_header_mongrel "$LINENO" "event2/event.h" "ac_cv_header_event2_event_h" "$ac_includes_default"
+if test "x$ac_cv_header_event2_event_h" = xyes; then :
+ $as_echo "#define HAVE_EVENT2_EVENT_H 1" >>confdefs.h
+
+else
+
+ ac_fn_c_check_header_mongrel "$LINENO" "event.h" "ac_cv_header_event_h" "$ac_includes_default"
+if test "x$ac_cv_header_event_h" = xyes; then :
+ $as_echo "#define HAVE_EVENT_H 1" >>confdefs.h
+
+else
+ found_libevent=no
+
+fi
+
+
+
+
+fi
+
+
+if test "x$found_libevent" = xno; then
+ as_fn_error $? "\"libevent not found\"" "$LINENO" 5
+fi
+
+# Look for ncurses or curses. Try pkg-config first then directly for the
+# library.
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for tinfo" >&5
+$as_echo_n "checking for tinfo... " >&6; }
+
+if test -n "$LIBTINFO_CFLAGS"; then
+ pkg_cv_LIBTINFO_CFLAGS="$LIBTINFO_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"tinfo\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "tinfo") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBTINFO_CFLAGS=`$PKG_CONFIG --cflags "tinfo" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$LIBTINFO_LIBS"; then
+ pkg_cv_LIBTINFO_LIBS="$LIBTINFO_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"tinfo\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "tinfo") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBTINFO_LIBS=`$PKG_CONFIG --libs "tinfo" 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
+ LIBTINFO_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "tinfo" 2>&1`
+ else
+ LIBTINFO_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "tinfo" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$LIBTINFO_PKG_ERRORS" >&5
+
+ found_ncurses=no
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ found_ncurses=no
+
+else
+ LIBTINFO_CFLAGS=$pkg_cv_LIBTINFO_CFLAGS
+ LIBTINFO_LIBS=$pkg_cv_LIBTINFO_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBTINFO_LIBS $LIBS"
+ found_ncurses=yes
+
+fi
+if test "x$found_ncurses" = xno; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ncurses" >&5
+$as_echo_n "checking for ncurses... " >&6; }
+
+if test -n "$LIBNCURSES_CFLAGS"; then
+ pkg_cv_LIBNCURSES_CFLAGS="$LIBNCURSES_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncurses\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "ncurses") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBNCURSES_CFLAGS=`$PKG_CONFIG --cflags "ncurses" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$LIBNCURSES_LIBS"; then
+ pkg_cv_LIBNCURSES_LIBS="$LIBNCURSES_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncurses\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "ncurses") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBNCURSES_LIBS=`$PKG_CONFIG --libs "ncurses" 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
+ LIBNCURSES_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ncurses" 2>&1`
+ else
+ LIBNCURSES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ncurses" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$LIBNCURSES_PKG_ERRORS" >&5
+
+ found_ncurses=no
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ found_ncurses=no
+
+else
+ LIBNCURSES_CFLAGS=$pkg_cv_LIBNCURSES_CFLAGS
+ LIBNCURSES_LIBS=$pkg_cv_LIBNCURSES_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBNCURSES_LIBS $LIBS"
+ found_ncurses=yes
+
+fi
+fi
+if test "x$found_ncurses" = xno; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ncursesw" >&5
+$as_echo_n "checking for ncursesw... " >&6; }
+
+if test -n "$LIBNCURSESW_CFLAGS"; then
+ pkg_cv_LIBNCURSESW_CFLAGS="$LIBNCURSESW_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncursesw\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "ncursesw") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBNCURSESW_CFLAGS=`$PKG_CONFIG --cflags "ncursesw" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$LIBNCURSESW_LIBS"; then
+ pkg_cv_LIBNCURSESW_LIBS="$LIBNCURSESW_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ncursesw\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "ncursesw") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_LIBNCURSESW_LIBS=`$PKG_CONFIG --libs "ncursesw" 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
+ LIBNCURSESW_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ncursesw" 2>&1`
+ else
+ LIBNCURSESW_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ncursesw" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$LIBNCURSESW_PKG_ERRORS" >&5
+
+ found_ncurses=no
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ found_ncurses=no
+
+else
+ LIBNCURSESW_CFLAGS=$pkg_cv_LIBNCURSESW_CFLAGS
+ LIBNCURSESW_LIBS=$pkg_cv_LIBNCURSESW_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ AM_CPPFLAGS="$LIBNCURSESW_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBNCURSESW_LIBS $LIBS"
+ found_ncurses=yes
+
+fi
+fi
+if test "x$found_ncurses" = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing setupterm" >&5
+$as_echo_n "checking for library containing setupterm... " >&6; }
+if ${ac_cv_search_setupterm+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char setupterm ();
+int
+main ()
+{
+return setupterm ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' tinfo ncurses ncursesw; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_setupterm=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_setupterm+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_setupterm+:} false; then :
+
+else
+ ac_cv_search_setupterm=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_setupterm" >&5
+$as_echo "$ac_cv_search_setupterm" >&6; }
+ac_res=$ac_cv_search_setupterm
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ found_ncurses=yes
+else
+ found_ncurses=no
+
+fi
+
+ if test "x$found_ncurses" = xyes; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ncurses.h" "ac_cv_header_ncurses_h" "$ac_includes_default"
+if test "x$ac_cv_header_ncurses_h" = xyes; then :
+ LIBS="$LIBS -lncurses"
+else
+ found_ncurses=no
+
+fi
+
+
+ fi
+fi
+if test "x$found_ncurses" = xyes; then
+ CPPFLAGS="$CPPFLAGS -DHAVE_NCURSES_H"
+ $as_echo "#define HAVE_NCURSES_H 1" >>confdefs.h
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for setupterm in -lcurses" >&5
+$as_echo_n "checking for setupterm in -lcurses... " >&6; }
+if ${ac_cv_lib_curses_setupterm+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lcurses $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char setupterm ();
+int
+main ()
+{
+return setupterm ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_curses_setupterm=yes
+else
+ ac_cv_lib_curses_setupterm=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curses_setupterm" >&5
+$as_echo "$ac_cv_lib_curses_setupterm" >&6; }
+if test "x$ac_cv_lib_curses_setupterm" = xyes; then :
+ found_curses=yes
+else
+ found_curses=no
+
+fi
+
+ ac_fn_c_check_header_mongrel "$LINENO" "curses.h" "ac_cv_header_curses_h" "$ac_includes_default"
+if test "x$ac_cv_header_curses_h" = xyes; then :
+
+else
+ found_curses=no
+
+fi
+
+
+ if test "x$found_curses" = xyes; then
+ LIBS="$LIBS -lcurses"
+ CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H"
+ $as_echo "#define HAVE_CURSES_H 1" >>confdefs.h
+
+ else
+ as_fn_error $? "\"curses not found\"" "$LINENO" 5
+ fi
+fi
+
+# Look for utempter.
+# Check whether --enable-utempter was given.
+if test "${enable_utempter+set}" = set; then :
+ enableval=$enable_utempter;
+fi
+
+if test "x$enable_utempter" = xyes; then
+ ac_fn_c_check_header_mongrel "$LINENO" "utempter.h" "ac_cv_header_utempter_h" "$ac_includes_default"
+if test "x$ac_cv_header_utempter_h" = xyes; then :
+ enable_utempter=yes
+else
+ enable_utempter=no
+fi
+
+
+ if test "x$enable_utempter" = xyes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing utempter_add_record" >&5
+$as_echo_n "checking for library containing utempter_add_record... " >&6; }
+if ${ac_cv_search_utempter_add_record+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char utempter_add_record ();
+int
+main ()
+{
+return utempter_add_record ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' utempter; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_utempter_add_record=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_utempter_add_record+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_utempter_add_record+:} false; then :
+
+else
+ ac_cv_search_utempter_add_record=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_utempter_add_record" >&5
+$as_echo "$ac_cv_search_utempter_add_record" >&6; }
+ac_res=$ac_cv_search_utempter_add_record
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ enable_utempter=yes
+else
+ enable_utempter=no
+
+fi
+
+ fi
+ if test "x$enable_utempter" = xyes; then
+ $as_echo "#define HAVE_UTEMPTER 1" >>confdefs.h
+
+ else
+ as_fn_error $? "\"utempter not found\"" "$LINENO" 5
+ fi
+fi
+
+# Look for utf8proc.
+# Check whether --enable-utf8proc was given.
+if test "${enable_utf8proc+set}" = set; then :
+ enableval=$enable_utf8proc;
+fi
+
+if test "x$enable_utf8proc" = xyes; then
+ 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
+else
+ enable_utf8proc=no
+fi
+
+
+ if test "x$enable_utf8proc" = xyes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing utf8proc_charwidth" >&5
+$as_echo_n "checking for library containing utf8proc_charwidth... " >&6; }
+if ${ac_cv_search_utf8proc_charwidth+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char utf8proc_charwidth ();
+int
+main ()
+{
+return utf8proc_charwidth ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' utf8proc; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_utf8proc_charwidth=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_utf8proc_charwidth+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_utf8proc_charwidth+:} false; then :
+
+else
+ ac_cv_search_utf8proc_charwidth=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_utf8proc_charwidth" >&5
+$as_echo "$ac_cv_search_utf8proc_charwidth" >&6; }
+ac_res=$ac_cv_search_utf8proc_charwidth
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ enable_utf8proc=yes
+else
+ enable_utf8proc=no
+
+fi
+
+ fi
+ if test "x$enable_utf8proc" = xyes; then
+ $as_echo "#define HAVE_UTF8PROC 1" >>confdefs.h
+
+ else
+ as_fn_error $? "\"utf8proc not found\"" "$LINENO" 5
+ fi
+fi
+ if test "x$enable_utf8proc" = xyes; then
+ HAVE_UTF8PROC_TRUE=
+ HAVE_UTF8PROC_FALSE='#'
+else
+ HAVE_UTF8PROC_TRUE='#'
+ HAVE_UTF8PROC_FALSE=
+fi
+
+
+# Check for systemd support.
+# Check whether --enable-systemd was given.
+if test "${enable_systemd+set}" = set; then :
+ enableval=$enable_systemd;
+fi
+
+if test x"$enable_systemd" = xyes; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libsystemd" >&5
+$as_echo_n "checking for libsystemd... " >&6; }
+
+if test -n "$SYSTEMD_CFLAGS"; then
+ pkg_cv_SYSTEMD_CFLAGS="$SYSTEMD_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsystemd\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libsystemd") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_SYSTEMD_CFLAGS=`$PKG_CONFIG --cflags "libsystemd" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$SYSTEMD_LIBS"; then
+ pkg_cv_SYSTEMD_LIBS="$SYSTEMD_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsystemd\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libsystemd") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_SYSTEMD_LIBS=`$PKG_CONFIG --libs "libsystemd" 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
+ SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libsystemd" 2>&1`
+ else
+ SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libsystemd" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$SYSTEMD_PKG_ERRORS" >&5
+
+ found_systemd=no
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ found_systemd=no
+
+else
+ SYSTEMD_CFLAGS=$pkg_cv_SYSTEMD_CFLAGS
+ SYSTEMD_LIBS=$pkg_cv_SYSTEMD_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ AM_CPPFLAGS="$SYSTEMD_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+ LIBS="$SYSTEMD_LIBS $LIBS"
+ found_systemd=yes
+
+fi
+ if test "x$found_systemd" = xyes; then
+ $as_echo "#define HAVE_SYSTEMD 1" >>confdefs.h
+
+ else
+ as_fn_error $? "\"systemd not found\"" "$LINENO" 5
+ fi
+fi
+ if test "x$found_systemd" = xyes; then
+ HAVE_SYSTEMD_TRUE=
+ HAVE_SYSTEMD_FALSE='#'
+else
+ HAVE_SYSTEMD_TRUE='#'
+ HAVE_SYSTEMD_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
+/* end confdefs.h. */
+
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ #include <resolv.h>
+
+int
+main ()
+{
+
+ b64_ntop(NULL, 0, NULL, 0);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ found_b64_ntop=yes
+else
+ found_b64_ntop=no
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_b64_ntop" >&5
+$as_echo "$found_b64_ntop" >&6; }
+OLD_LIBS="$LIBS"
+if test "x$found_b64_ntop" = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for b64_ntop with -lresolv" >&5
+$as_echo_n "checking for b64_ntop with -lresolv... " >&6; }
+ LIBS="$OLD_LIBS -lresolv"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ #include <resolv.h>
+
+int
+main ()
+{
+
+ b64_ntop(NULL, 0, NULL, 0);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ found_b64_ntop=yes
+else
+ found_b64_ntop=no
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_b64_ntop" >&5
+$as_echo "$found_b64_ntop" >&6; }
+fi
+if test "x$found_b64_ntop" = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for b64_ntop with -lnetwork" >&5
+$as_echo_n "checking for b64_ntop with -lnetwork... " >&6; }
+ LIBS="$OLD_LIBS -lnetwork"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ #include <resolv.h>
+
+int
+main ()
+{
+
+ b64_ntop(NULL, 0, NULL, 0);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ found_b64_ntop=yes
+else
+ found_b64_ntop=no
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_b64_ntop" >&5
+$as_echo "$found_b64_ntop" >&6; }
+fi
+if test "x$found_b64_ntop" = xyes; then
+ $as_echo "#define HAVE_B64_NTOP 1" >>confdefs.h
+
+else
+ LIBS="$OLD_LIBS"
+ case " $LIBOBJS " in
+ *" base64.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS base64.$ac_objext"
+ ;;
+esac
+
+fi
+
+# Look for networking libraries.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing inet_ntoa" >&5
+$as_echo_n "checking for library containing inet_ntoa... " >&6; }
+if ${ac_cv_search_inet_ntoa+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char inet_ntoa ();
+int
+main ()
+{
+return inet_ntoa ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' nsl; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_inet_ntoa=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_inet_ntoa+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_inet_ntoa+:} false; then :
+
+else
+ ac_cv_search_inet_ntoa=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_inet_ntoa" >&5
+$as_echo "$ac_cv_search_inet_ntoa" >&6; }
+ac_res=$ac_cv_search_inet_ntoa
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing socket" >&5
+$as_echo_n "checking for library containing socket... " >&6; }
+if ${ac_cv_search_socket+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char socket ();
+int
+main ()
+{
+return socket ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' socket; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_socket=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_socket+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_socket+:} false; then :
+
+else
+ ac_cv_search_socket=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_socket" >&5
+$as_echo "$ac_cv_search_socket" >&6; }
+ac_res=$ac_cv_search_socket
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lxnet" >&5
+$as_echo_n "checking for socket in -lxnet... " >&6; }
+if ${ac_cv_lib_xnet_socket+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lxnet $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char socket ();
+int
+main ()
+{
+return socket ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_xnet_socket=yes
+else
+ ac_cv_lib_xnet_socket=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xnet_socket" >&5
+$as_echo "$ac_cv_lib_xnet_socket" >&6; }
+if test "x$ac_cv_lib_xnet_socket" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBXNET 1
+_ACEOF
+
+ LIBS="-lxnet $LIBS"
+
+fi
+
+
+# Check if using glibc and have malloc_trim(3). The glibc free(3) is pretty bad
+# about returning memory to the kernel unless the application tells it when to
+# with malloc_trim(3).
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if free doesn't work very well" >&5
+$as_echo_n "checking if free doesn't work very well... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <stdlib.h>
+ #ifdef __GLIBC__
+ #include <malloc.h>
+ int main(void) {
+ malloc_trim (0);
+ exit(0);
+ }
+ #else
+ no
+ #endif
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ found_malloc_trim=yes
+else
+ found_malloc_trim=no
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_malloc_trim" >&5
+$as_echo "$found_malloc_trim" >&6; }
+if test "x$found_malloc_trim" = xyes; then
+ $as_echo "#define HAVE_MALLOC_TRIM 1" >>confdefs.h
+
+fi
+
+# Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95
+# (_XOPEN_SOURCE and _XOPEN_SOURCE_EXTENDED) (see xopen_networking(7)). On
+# others, UNIX 03 (_XOPEN_SOURCE 600, see standards(7) on Solaris).
+XOPEN_DEFINES=
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CMSG_DATA" >&5
+$as_echo_n "checking for CMSG_DATA... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <sys/socket.h>
+ #ifdef CMSG_DATA
+ yes
+ #endif
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "yes" >/dev/null 2>&1; then :
+ found_cmsg_data=yes
+else
+ found_cmsg_data=no
+
+fi
+rm -f conftest*
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_cmsg_data" >&5
+$as_echo "$found_cmsg_data" >&6; }
+if test "x$found_cmsg_data" = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if CMSG_DATA needs _XOPEN_SOURCE_EXTENDED" >&5
+$as_echo_n "checking if CMSG_DATA needs _XOPEN_SOURCE_EXTENDED... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #define _XOPEN_SOURCE 1
+ #define _XOPEN_SOURCE_EXTENDED 1
+ #include <sys/socket.h>
+ #ifdef CMSG_DATA
+ yes
+ #endif
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "yes" >/dev/null 2>&1; then :
+ found_cmsg_data=yes
+else
+ found_cmsg_data=no
+
+fi
+rm -f conftest*
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_cmsg_data" >&5
+$as_echo "$found_cmsg_data" >&6; }
+ if test "x$found_cmsg_data" = xyes; then
+ XOPEN_DEFINES="-D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED"
+ fi
+fi
+if test "x$found_cmsg_data" = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if CMSG_DATA needs _XOPEN_SOURCE 600" >&5
+$as_echo_n "checking if CMSG_DATA needs _XOPEN_SOURCE 600... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #define _XOPEN_SOURCE 600
+ #include <sys/socket.h>
+ #ifdef CMSG_DATA
+ yes
+ #endif
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "yes" >/dev/null 2>&1; then :
+ found_cmsg_data=yes
+else
+ found_cmsg_data=no
+
+fi
+rm -f conftest*
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $found_cmsg_data" >&5
+$as_echo "$found_cmsg_data" >&6; }
+ if test "x$found_cmsg_data" = xyes; then
+ XOPEN_DEFINES="-D_XOPEN_SOURCE=600"
+ else
+ as_fn_error $? "\"CMSG_DATA not found\"" "$LINENO" 5
+ fi
+fi
+
+
+# Look for err and friends in err.h.
+ac_fn_c_check_func "$LINENO" "err" "ac_cv_func_err"
+if test "x$ac_cv_func_err" = xyes; then :
+ found_err_h=yes
+else
+ found_err_h=no
+fi
+
+ac_fn_c_check_func "$LINENO" "errx" "ac_cv_func_errx"
+if test "x$ac_cv_func_errx" = xyes; then :
+
+else
+ found_err_h=no
+fi
+
+ac_fn_c_check_func "$LINENO" "warn" "ac_cv_func_warn"
+if test "x$ac_cv_func_warn" = xyes; then :
+
+else
+ found_err_h=no
+fi
+
+ac_fn_c_check_func "$LINENO" "warnx" "ac_cv_func_warnx"
+if test "x$ac_cv_func_warnx" = xyes; then :
+
+else
+ found_err_h=no
+fi
+
+if test "x$found_err_h" = xyes; then
+ ac_fn_c_check_header_mongrel "$LINENO" "err.h" "ac_cv_header_err_h" "$ac_includes_default"
+if test "x$ac_cv_header_err_h" = xyes; then :
+
+else
+ found_err_h=no
+fi
+
+
+else
+ case " $LIBOBJS " in
+ *" err.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS err.$ac_objext"
+ ;;
+esac
+
+fi
+
+# Look for imsg_init in libutil.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing imsg_init" >&5
+$as_echo_n "checking for library containing imsg_init... " >&6; }
+if ${ac_cv_search_imsg_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char imsg_init ();
+int
+main ()
+{
+return imsg_init ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' util; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_imsg_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_imsg_init+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_imsg_init+:} false; then :
+
+else
+ ac_cv_search_imsg_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_imsg_init" >&5
+$as_echo "$ac_cv_search_imsg_init" >&6; }
+ac_res=$ac_cv_search_imsg_init
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ found_imsg_init=yes
+else
+ found_imsg_init=no
+fi
+
+if test "x$found_imsg_init" = xyes; then
+ $as_echo "#define HAVE_IMSG 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" imsg.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS imsg.$ac_objext"
+ ;;
+esac
+
+ case " $LIBOBJS " in
+ *" imsg-buffer.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS imsg-buffer.$ac_objext"
+ ;;
+esac
+
+fi
+
+# Look for daemon, compat/daemon.c used if missing. Solaris 10 has it in
+# libresolv, but no declaration anywhere, so check for declaration as well as
+# function.
+ac_fn_c_check_func "$LINENO" "daemon" "ac_cv_func_daemon"
+if test "x$ac_cv_func_daemon" = xyes; then :
+ found_daemon=yes
+else
+ found_daemon=no
+fi
+
+ac_fn_c_check_decl "$LINENO" "daemon" "ac_cv_have_decl_daemon" "
+ #include <stdlib.h>
+ #include <unistd.h>
+
+
+"
+if test "x$ac_cv_have_decl_daemon" = xyes; then :
+
+else
+ found_daemon=no
+fi
+
+if test "x$found_daemon" = xyes; then
+ $as_echo "#define HAVE_DAEMON 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" daemon.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS daemon.$ac_objext"
+ ;;
+esac
+
+fi
+
+# Look for stravis, compat/{vis,unvis}.c used if missing.
+ac_fn_c_check_func "$LINENO" "stravis" "ac_cv_func_stravis"
+if test "x$ac_cv_func_stravis" = xyes; then :
+ found_stravis=yes
+else
+ found_stravis=no
+fi
+
+if test "x$found_stravis" = xyes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if strnvis is broken" >&5
+$as_echo_n "checking if strnvis is broken... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <vis.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "strnvis\(char \*, const char \*, size_t, int\)" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+else
+ found_stravis=no
+fi
+rm -f conftest*
+
+ if test "x$found_stravis" = xno; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ fi
+fi
+if test "x$found_stravis" = xyes; then
+ ac_fn_c_check_decl "$LINENO" "VIS_DQ" "ac_cv_have_decl_VIS_DQ" "
+ #include <stdlib.h>
+ #include <vis.h>
+
+
+"
+if test "x$ac_cv_have_decl_VIS_DQ" = xyes; then :
+
+else
+ found_stravis=no
+fi
+
+fi
+if test "x$found_stravis" = xyes; then
+ $as_echo "#define HAVE_VIS 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" vis.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS vis.$ac_objext"
+ ;;
+esac
+
+ case " $LIBOBJS " in
+ *" unvis.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS unvis.$ac_objext"
+ ;;
+esac
+
+fi
+
+# Look for fdforkpty and forkpty in libutil.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing fdforkpty" >&5
+$as_echo_n "checking for library containing fdforkpty... " >&6; }
+if ${ac_cv_search_fdforkpty+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char fdforkpty ();
+int
+main ()
+{
+return fdforkpty ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' util; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_fdforkpty=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_fdforkpty+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_fdforkpty+:} false; then :
+
+else
+ ac_cv_search_fdforkpty=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_fdforkpty" >&5
+$as_echo "$ac_cv_search_fdforkpty" >&6; }
+ac_res=$ac_cv_search_fdforkpty
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ found_fdforkpty=yes
+else
+ found_fdforkpty=no
+fi
+
+if test "x$found_fdforkpty" = xyes; then
+ $as_echo "#define HAVE_FDFORKPTY 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" fdforkpty.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS fdforkpty.$ac_objext"
+ ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing forkpty" >&5
+$as_echo_n "checking for library containing forkpty... " >&6; }
+if ${ac_cv_search_forkpty+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char forkpty ();
+int
+main ()
+{
+return forkpty ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' util; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_forkpty=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_forkpty+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_forkpty+:} false; then :
+
+else
+ ac_cv_search_forkpty=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_forkpty" >&5
+$as_echo "$ac_cv_search_forkpty" >&6; }
+ac_res=$ac_cv_search_forkpty
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+ found_forkpty=yes
+else
+ found_forkpty=no
+fi
+
+if test "x$found_forkpty" = xyes; then
+ $as_echo "#define HAVE_FORKPTY 1" >>confdefs.h
+
+fi
+ if test "x$found_forkpty" = xno; then
+ NEED_FORKPTY_TRUE=
+ NEED_FORKPTY_FALSE='#'
+else
+ NEED_FORKPTY_TRUE='#'
+ NEED_FORKPTY_FALSE=
+fi
+
+
+# Look for kinfo_getfile in libutil.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing kinfo_getfile" >&5
+$as_echo_n "checking for library containing kinfo_getfile... " >&6; }
+if ${ac_cv_search_kinfo_getfile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char kinfo_getfile ();
+int
+main ()
+{
+return kinfo_getfile ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' util util-freebsd; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_kinfo_getfile=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_kinfo_getfile+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_kinfo_getfile+:} false; then :
+
+else
+ ac_cv_search_kinfo_getfile=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_kinfo_getfile" >&5
+$as_echo "$ac_cv_search_kinfo_getfile" >&6; }
+ac_res=$ac_cv_search_kinfo_getfile
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+
+# Look for a suitable queue.h.
+ac_fn_c_check_decl "$LINENO" "TAILQ_CONCAT" "ac_cv_have_decl_TAILQ_CONCAT" "#include <sys/queue.h>
+
+"
+if test "x$ac_cv_have_decl_TAILQ_CONCAT" = xyes; then :
+ found_queue_h=yes
+else
+ found_queue_h=no
+fi
+
+ac_fn_c_check_decl "$LINENO" "TAILQ_PREV" "ac_cv_have_decl_TAILQ_PREV" "#include <sys/queue.h>
+
+"
+if test "x$ac_cv_have_decl_TAILQ_PREV" = xyes; then :
+
+else
+ found_queue_h=no
+fi
+
+ac_fn_c_check_decl "$LINENO" "TAILQ_REPLACE" "ac_cv_have_decl_TAILQ_REPLACE" "#include <sys/queue.h>
+
+"
+if test "x$ac_cv_have_decl_TAILQ_REPLACE" = xyes; then :
+
+else
+ found_queue_h=no
+fi
+
+if test "x$found_queue_h" = xyes; then
+ $as_echo "#define HAVE_QUEUE_H 1" >>confdefs.h
+
+fi
+
+# Look for __progname.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __progname" >&5
+$as_echo_n "checking for __progname... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ extern char *__progname;
+ int main(void) {
+ const char *cp = __progname;
+ printf("%s\n", cp);
+ exit(0);
+ }
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ $as_echo "#define HAVE___PROGNAME 1" >>confdefs.h
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+
+# Look for program_invocation_short_name.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for program_invocation_short_name" >&5
+$as_echo_n "checking for program_invocation_short_name... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ int main(void) {
+ const char *cp = program_invocation_short_name;
+ printf("%s\n", cp);
+ exit(0);
+ }
+
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ $as_echo "#define HAVE_PROGRAM_INVOCATION_SHORT_NAME 1" >>confdefs.h
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+
+# Look for prctl(PR_SET_NAME).
+ac_fn_c_check_decl "$LINENO" "PR_SET_NAME" "ac_cv_have_decl_PR_SET_NAME" "#include <sys/prctl.h>
+
+"
+if test "x$ac_cv_have_decl_PR_SET_NAME" = xyes; then :
+ $as_echo "#define HAVE_PR_SET_NAME 1" >>confdefs.h
+
+fi
+
+
+# Look for setsockopt(SO_PEERCRED).
+ac_fn_c_check_decl "$LINENO" "SO_PEERCRED" "ac_cv_have_decl_SO_PEERCRED" "#include <sys/socket.h>
+
+"
+if test "x$ac_cv_have_decl_SO_PEERCRED" = xyes; then :
+ $as_echo "#define HAVE_SO_PEERCRED 1" >>confdefs.h
+
+fi
+
+
+# Look for fcntl(F_CLOSEM).
+ac_fn_c_check_decl "$LINENO" "F_CLOSEM" "ac_cv_have_decl_F_CLOSEM" "#include <fcntl.h>
+
+"
+if test "x$ac_cv_have_decl_F_CLOSEM" = xyes; then :
+ $as_echo "#define HAVE_FCNTL_CLOSEM 1" >>confdefs.h
+
+fi
+
+
+# Look for /proc/$$.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /proc/\$\$" >&5
+$as_echo_n "checking for /proc/\$\$... " >&6; }
+if test -d /proc/$$; then
+ $as_echo "#define HAVE_PROC_PID 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+# Try to figure out what the best value for TERM might be.
+if test "x$DEFAULT_TERM" = x; then
+ DEFAULT_TERM=screen
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking TERM" >&5
+$as_echo_n "checking TERM... " >&6; }
+ if test "$cross_compiling" = yes; then :
+ DEFAULT_TERM=screen
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ #if defined(HAVE_CURSES_H)
+ #include <curses.h>
+ #elif defined(HAVE_NCURSES_H)
+ #include <ncurses.h>
+ #endif
+ #include <term.h>
+ int main(void) {
+ if (setupterm("screen-256color", -1, NULL) != OK)
+ exit(1);
+ exit(0);
+ }
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ DEFAULT_TERM=screen-256color
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+ if test "$cross_compiling" = yes; then :
+ DEFAULT_TERM=screen
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ #if defined(HAVE_CURSES_H)
+ #include <curses.h>
+ #elif defined(HAVE_NCURSES_H)
+ #include <ncurses.h>
+ #endif
+ #include <term.h>
+ int main(void) {
+ if (setupterm("tmux", -1, NULL) != OK)
+ exit(1);
+ exit(0);
+ }
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ DEFAULT_TERM=tmux
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+ if test "$cross_compiling" = yes; then :
+ DEFAULT_TERM=screen
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <stdio.h>
+ #include <stdlib.h>
+ #if defined(HAVE_CURSES_H)
+ #include <curses.h>
+ #elif defined(HAVE_NCURSES_H)
+ #include <ncurses.h>
+ #endif
+ #include <term.h>
+ int main(void) {
+ if (setupterm("tmux-256color", -1, NULL) != OK)
+ exit(1);
+ exit(0);
+ }
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ DEFAULT_TERM=tmux-256color
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DEFAULT_TERM" >&5
+$as_echo "$DEFAULT_TERM" >&6; }
+fi
+
+
+# Man page defaults to mdoc.
+MANFORMAT=mdoc
+
+
+# Figure out the platform.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking platform" >&5
+$as_echo_n "checking platform... " >&6; }
+case "$host_os" in
+ *aix*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: aix" >&5
+$as_echo "aix" >&6; }
+ PLATFORM=aix
+ ;;
+ *darwin*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: darwin" >&5
+$as_echo "darwin" >&6; }
+ PLATFORM=darwin
+ #
+ # macOS uses __dead2 instead of __dead, like FreeBSD. But it defines
+ # __dead away so it needs to be removed before we can replace it.
+ #
+ $as_echo "#define BROKEN___DEAD 1" >>confdefs.h
+
+ #
+ # macOS CMSG_FIRSTHDR is broken, so redefine it with a working one.
+ # daemon works but has some stupid side effects, so use our internal
+ # version which has a workaround.
+ #
+ $as_echo "#define BROKEN_CMSG_FIRSTHDR 1" >>confdefs.h
+
+ case " $LIBOBJS " in
+ *" daemon.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS daemon.$ac_objext"
+ ;;
+esac
+
+ case " $LIBOBJS " in
+ *" daemon-darwin.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS daemon-darwin.$ac_objext"
+ ;;
+esac
+
+ #
+ # macOS wcwidth(3) is bad, so complain and suggest using utf8proc
+ # instead.
+ #
+ if test "x$enable_utf8proc" = x; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: " >&5
+$as_echo "$as_me: " >&6;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: macOS library support for Unicode is very poor," >&5
+$as_echo "$as_me: macOS library support for Unicode is very poor," >&6;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: particularly for complex codepoints like emojis;" >&5
+$as_echo "$as_me: particularly for complex codepoints like emojis;" >&6;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: to use these correctly, configuring with" >&5
+$as_echo "$as_me: to use these correctly, configuring with" >&6;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: --enable-utf8proc is recommended. To build" >&5
+$as_echo "$as_me: --enable-utf8proc is recommended. To build" >&6;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: without anyway, use --disable-utf8proc" >&5
+$as_echo "$as_me: without anyway, use --disable-utf8proc" >&6;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: " >&5
+$as_echo "$as_me: " >&6;}
+ as_fn_error $? "must give --enable-utf8proc or --disable-utf8proc" "$LINENO" 5
+ fi
+ ;;
+ *dragonfly*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: dragonfly" >&5
+$as_echo "dragonfly" >&6; }
+ PLATFORM=dragonfly
+ ;;
+ *linux*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: linux" >&5
+$as_echo "linux" >&6; }
+ PLATFORM=linux
+ ;;
+ *freebsd*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: freebsd" >&5
+$as_echo "freebsd" >&6; }
+ PLATFORM=freebsd
+ ;;
+ *netbsd*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: netbsd" >&5
+$as_echo "netbsd" >&6; }
+ PLATFORM=netbsd
+ ;;
+ *openbsd*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: openbsd" >&5
+$as_echo "openbsd" >&6; }
+ PLATFORM=openbsd
+ ;;
+ *sunos*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: sunos" >&5
+$as_echo "sunos" >&6; }
+ PLATFORM=sunos
+ ;;
+ *solaris*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: sunos" >&5
+$as_echo "sunos" >&6; }
+ PLATFORM=sunos
+ case `/usr/bin/nroff --version 2>&1` in
+ *GNU*)
+ # Solaris 11.4 and later use GNU groff.
+ MANFORMAT=mdoc
+ ;;
+ *)
+ # Solaris 2.0 to 11.3 use AT&T nroff.
+ MANFORMAT=man
+ ;;
+ esac
+ ;;
+ *hpux*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: hpux" >&5
+$as_echo "hpux" >&6; }
+ PLATFORM=hpux
+ ;;
+ *cygwin*|*msys*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: cygwin" >&5
+$as_echo "cygwin" >&6; }
+ PLATFORM=cygwin
+ ;;
+ *haiku*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: haiku" >&5
+$as_echo "haiku" >&6; }
+ PLATFORM=haiku
+ ;;
+ *)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unknown" >&5
+$as_echo "unknown" >&6; }
+ PLATFORM=unknown
+ ;;
+esac
+
+ if test "x$PLATFORM" = xaix; then
+ IS_AIX_TRUE=
+ IS_AIX_FALSE='#'
+else
+ IS_AIX_TRUE='#'
+ IS_AIX_FALSE=
+fi
+
+ if test "x$PLATFORM" = xdarwin; then
+ IS_DARWIN_TRUE=
+ IS_DARWIN_FALSE='#'
+else
+ IS_DARWIN_TRUE='#'
+ IS_DARWIN_FALSE=
+fi
+
+ if test "x$PLATFORM" = xdragonfly; then
+ IS_DRAGONFLY_TRUE=
+ IS_DRAGONFLY_FALSE='#'
+else
+ IS_DRAGONFLY_TRUE='#'
+ IS_DRAGONFLY_FALSE=
+fi
+
+ if test "x$PLATFORM" = xlinux; then
+ IS_LINUX_TRUE=
+ IS_LINUX_FALSE='#'
+else
+ IS_LINUX_TRUE='#'
+ IS_LINUX_FALSE=
+fi
+
+ if test "x$PLATFORM" = xfreebsd; then
+ IS_FREEBSD_TRUE=
+ IS_FREEBSD_FALSE='#'
+else
+ IS_FREEBSD_TRUE='#'
+ IS_FREEBSD_FALSE=
+fi
+
+ if test "x$PLATFORM" = xnetbsd; then
+ IS_NETBSD_TRUE=
+ IS_NETBSD_FALSE='#'
+else
+ IS_NETBSD_TRUE='#'
+ IS_NETBSD_FALSE=
+fi
+
+ if test "x$PLATFORM" = xopenbsd; then
+ IS_OPENBSD_TRUE=
+ IS_OPENBSD_FALSE='#'
+else
+ IS_OPENBSD_TRUE='#'
+ IS_OPENBSD_FALSE=
+fi
+
+ if test "x$PLATFORM" = xsunos; then
+ IS_SUNOS_TRUE=
+ IS_SUNOS_FALSE='#'
+else
+ IS_SUNOS_TRUE='#'
+ IS_SUNOS_FALSE=
+fi
+
+ if test "x$PLATFORM" = xhpux; then
+ IS_HPUX_TRUE=
+ IS_HPUX_FALSE='#'
+else
+ IS_HPUX_TRUE='#'
+ IS_HPUX_FALSE=
+fi
+
+ if test "x$PLATFORM" = xhaiku; then
+ IS_HAIKU_TRUE=
+ IS_HAIKU_FALSE='#'
+else
+ IS_HAIKU_TRUE='#'
+ IS_HAIKU_FALSE=
+fi
+
+ if test "x$PLATFORM" = xunknown; then
+ IS_UNKNOWN_TRUE=
+ IS_UNKNOWN_FALSE='#'
+else
+ IS_UNKNOWN_TRUE='#'
+ IS_UNKNOWN_FALSE=
+fi
+
+
+# Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user
+# variables.
+
+CPPFLAGS="$SAVED_CPPFLAGS"
+
+CFLAGS="$SAVED_CFLAGS"
+
+LDFLAGS="$SAVED_LDFLAGS"
+
+# autoconf should create a Makefile.
+ac_config_files="$ac_config_files Makefile"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+ for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+
+ (set) 2>&1 |
+ case $as_nl`(ac_space=' '; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ # `set' does not quote correctly, so add quotes: double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \.
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;; #(
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+) |
+ sed '
+ /^ac_cv_env_/b end
+ t clear
+ :clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+ if test -w "$cache_file"; then
+ if test "x$cache_file" != "x/dev/null"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+ if test ! -f "$cache_file" || test -h "$cache_file"; then
+ cat confcache >"$cache_file"
+ else
+ case $cache_file in #(
+ */* | ?:*)
+ mv -f confcache "$cache_file"$$ &&
+ mv -f "$cache_file"$$ "$cache_file" ;; #(
+ *)
+ mv -f confcache "$cache_file" ;;
+ esac
+ fi
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then branch to the quote section. Otherwise,
+# look for a macro that doesn't take arguments.
+ac_script='
+:mline
+/\\$/{
+ N
+ s,\\\n,,
+ b mline
+}
+t clear
+:clear
+s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g
+t quote
+s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g
+t quote
+b any
+:quote
+s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g
+s/\[/\\&/g
+s/\]/\\&/g
+s/\$/$$/g
+H
+:any
+${
+ g
+ s/^\n//
+ s/\n/ /g
+ p
+}
+'
+DEFS=`sed -n "$ac_script" confdefs.h`
+
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+ # 1. Remove the extension, and $U if already installed.
+ ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+ ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+ # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR
+ # will be set to the directory where LIBOBJS objects are built.
+ as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+ as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5
+$as_echo_n "checking that generated files are newer than configure... " >&6; }
+ if test -n "$am_sleep_pid"; then
+ # Hide warnings about reused PIDs.
+ wait $am_sleep_pid 2>/dev/null
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5
+$as_echo "done" >&6; }
+ if test -n "$EXEEXT"; then
+ am__EXEEXT_TRUE=
+ am__EXEEXT_FALSE='#'
+else
+ am__EXEEXT_TRUE='#'
+ am__EXEEXT_FALSE=
+fi
+
+if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then
+ as_fn_error $? "conditional \"AMDEP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
+ as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_DEBUG_TRUE}" && test -z "${IS_DEBUG_FALSE}"; then
+ as_fn_error $? "conditional \"IS_DEBUG\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${NEED_FUZZING_TRUE}" && test -z "${NEED_FUZZING_FALSE}"; then
+ as_fn_error $? "conditional \"NEED_FUZZING\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_GCC_TRUE}" && test -z "${IS_GCC_FALSE}"; then
+ as_fn_error $? "conditional \"IS_GCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_SUNCC_TRUE}" && test -z "${IS_SUNCC_FALSE}"; then
+ as_fn_error $? "conditional \"IS_SUNCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${HAVE_UTF8PROC_TRUE}" && test -z "${HAVE_UTF8PROC_FALSE}"; then
+ as_fn_error $? "conditional \"HAVE_UTF8PROC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+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 "${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
+fi
+if test -z "${IS_AIX_TRUE}" && test -z "${IS_AIX_FALSE}"; then
+ as_fn_error $? "conditional \"IS_AIX\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_DARWIN_TRUE}" && test -z "${IS_DARWIN_FALSE}"; then
+ as_fn_error $? "conditional \"IS_DARWIN\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_DRAGONFLY_TRUE}" && test -z "${IS_DRAGONFLY_FALSE}"; then
+ as_fn_error $? "conditional \"IS_DRAGONFLY\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_LINUX_TRUE}" && test -z "${IS_LINUX_FALSE}"; then
+ as_fn_error $? "conditional \"IS_LINUX\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_FREEBSD_TRUE}" && test -z "${IS_FREEBSD_FALSE}"; then
+ as_fn_error $? "conditional \"IS_FREEBSD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_NETBSD_TRUE}" && test -z "${IS_NETBSD_FALSE}"; then
+ as_fn_error $? "conditional \"IS_NETBSD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_OPENBSD_TRUE}" && test -z "${IS_OPENBSD_FALSE}"; then
+ as_fn_error $? "conditional \"IS_OPENBSD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_SUNOS_TRUE}" && test -z "${IS_SUNOS_FALSE}"; then
+ as_fn_error $? "conditional \"IS_SUNOS\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_HPUX_TRUE}" && test -z "${IS_HPUX_FALSE}"; then
+ as_fn_error $? "conditional \"IS_HPUX\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_HAIKU_TRUE}" && test -z "${IS_HAIKU_FALSE}"; then
+ as_fn_error $? "conditional \"IS_HAIKU\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${IS_UNKNOWN_TRUE}" && test -z "${IS_UNKNOWN_FALSE}"; then
+ as_fn_error $? "conditional \"IS_UNKNOWN\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='print -r --'
+ as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='printf %s\n'
+ as_echo_n='printf %s'
+else
+ if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+ as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+ as_echo_n='/usr/ucb/echo -n'
+ else
+ as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+ as_echo_n_body='eval
+ arg=$1;
+ case $arg in #(
+ *"$as_nl"*)
+ expr "X$arg" : "X\\(.*\\)$as_nl";
+ arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+ esac;
+ expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+ '
+ export as_echo_n_body
+ as_echo_n='sh -c $as_echo_n_body as_echo'
+ fi
+ export as_echo_body
+ as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" "" $as_nl"
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there. '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ $as_echo "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# 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
+generated by GNU Autoconf 2.69. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration. Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number and configuration settings, then exit
+ --config print configuration, then exit
+ -q, --quiet, --silent
+ do not print progress messages
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Configuration commands:
+$config_commands
+
+Report bugs to the package provider."
+
+_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
+configured by $0, generated by GNU Autoconf 2.69,
+ with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=?*)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+ ac_shift=:
+ ;;
+ --*=)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=
+ ac_shift=:
+ ;;
+ *)
+ ac_option=$1
+ ac_optarg=$2
+ ac_shift=shift
+ ;;
+ esac
+
+ case $ac_option in
+ # Handling of the options.
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ ac_cs_recheck=: ;;
+ --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+ $as_echo "$ac_cs_version"; exit ;;
+ --config | --confi | --conf | --con | --co | --c )
+ $as_echo "$ac_cs_config"; exit ;;
+ --debug | --debu | --deb | --de | --d | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ '') as_fn_error $? "missing file argument" ;;
+ esac
+ as_fn_append CONFIG_FILES " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --he | --h | --help | --hel | -h )
+ $as_echo "$ac_cs_usage"; exit ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil | --si | --s)
+ ac_cs_silent=: ;;
+
+ # This is an error.
+ -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+ *) as_fn_append ac_config_targets " $1"
+ ac_need_defaults=false ;;
+
+ esac
+ shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+ exec 6>/dev/null
+ ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+ set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+ shift
+ \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+ CONFIG_SHELL='$SHELL'
+ export CONFIG_SHELL
+ exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+ $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+ case $ac_config_target in
+ "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
+ "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+
+ *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+ esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+ test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience. Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+ tmp= ac_tmp=
+ trap 'exit_status=$?
+ : "${ac_tmp:=$tmp}"
+ { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+ trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+ tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+ test -d "$tmp"
+} ||
+{
+ tmp=./conf$$-$RANDOM
+ (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+ eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+ ac_cs_awk_cr='\\r'
+else
+ ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+ echo "cat >conf$$subs.awk <<_ACEOF" &&
+ echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+ echo "_ACEOF"
+} >conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+ . ./conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+ ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+ if test $ac_delim_n = $ac_delim_num; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+ N
+ s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+ for (key in S) S_is_set[key] = 1
+ FS = ""
+
+}
+{
+ line = $ 0
+ nfields = split(line, field, "@")
+ substed = 0
+ len = length(field[1])
+ for (i = 2; i < nfields; i++) {
+ key = field[i]
+ keylen = length(key)
+ if (S_is_set[key]) {
+ value = S[key]
+ line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+ len += length(value) + length(field[++i])
+ substed = 1
+ } else
+ len += 1 + keylen
+ }
+
+ print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+ sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+ cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
+h
+s///
+s/^/:/
+s/[ ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[ ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[ ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+
+eval set X " :F $CONFIG_FILES :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+ case $ac_tag in
+ :[FHLC]) ac_mode=$ac_tag; continue;;
+ esac
+ case $ac_mode$ac_tag in
+ :[FHL]*:*);;
+ :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+ :[FH]-) ac_tag=-:-;;
+ :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+ esac
+ ac_save_IFS=$IFS
+ IFS=:
+ set x $ac_tag
+ IFS=$ac_save_IFS
+ shift
+ ac_file=$1
+ shift
+
+ case $ac_mode in
+ :L) ac_source=$1;;
+ :[FH])
+ ac_file_inputs=
+ for ac_f
+ do
+ case $ac_f in
+ -) ac_f="$ac_tmp/stdin";;
+ *) # Look for the file first in the build tree, then in the source tree
+ # (if the path is not absolute). The absolute path cannot be DOS-style,
+ # because $ac_f cannot contain `:'.
+ test -f "$ac_f" ||
+ case $ac_f in
+ [\\/$]*) false;;
+ *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+ esac ||
+ as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+ esac
+ case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+ as_fn_append ac_file_inputs " '$ac_f'"
+ done
+
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ configure_input='Generated from '`
+ $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+ `' by configure.'
+ if test x"$ac_file" != x-; then
+ configure_input="$ac_file. $configure_input"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+ fi
+ # Neutralize special characters interpreted by sed in replacement strings.
+ case $configure_input in #(
+ *\&* | *\|* | *\\* )
+ ac_sed_conf_input=`$as_echo "$configure_input" |
+ sed 's/[\\\\&|]/\\\\&/g'`;; #(
+ *) ac_sed_conf_input=$configure_input;;
+ esac
+
+ case $ac_tag in
+ *:-:* | *:-) cat >"$ac_tmp/stdin" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+ esac
+ ;;
+ esac
+
+ ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ as_dir="$ac_dir"; as_fn_mkdir_p
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+ case $ac_mode in
+ :F)
+ #
+ # CONFIG_FILE
+ #
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+ esac
+ ac_MKDIR_P=$MKDIR_P
+ case $MKDIR_P in
+ [\\/$]* | ?:[\\/]* ) ;;
+ */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+ esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+ p
+ q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ ac_datarootdir_hack='
+ s&@datadir@&$datadir&g
+ s&@docdir@&$docdir&g
+ s&@infodir@&$infodir&g
+ s&@localedir@&$localedir&g
+ s&@mandir@&$mandir&g
+ s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+ { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+ { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
+ "$ac_tmp/out"`; test -z "$ac_out"; } &&
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&2;}
+
+ rm -f "$ac_tmp/stdin"
+ case $ac_file in
+ -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+ *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+ esac \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+
+
+ :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+$as_echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+ esac
+
+
+ case $ac_file$ac_mode in
+ "depfiles":C) test x"$AMDEP_TRUE" != x"" || {
+ # Older Autoconf quotes --file arguments for eval, but not when files
+ # are listed without --file. Let's play safe and only enable the eval
+ # if we detect the quoting.
+ case $CONFIG_FILES in
+ *\'*) eval set x "$CONFIG_FILES" ;;
+ *) set x $CONFIG_FILES ;;
+ esac
+ shift
+ for mf
+ do
+ # Strip MF so we end up with the name of the file.
+ mf=`echo "$mf" | sed -e 's/:.*$//'`
+ # Check whether this is an Automake generated Makefile or not.
+ # We used to match only the files named 'Makefile.in', but
+ # some people rename them; so instead we look at the file content.
+ # Grep'ing the first line is not enough: some people post-process
+ # each Makefile.in and add a new line on top of each file to say so.
+ # Grep'ing the whole file is not good either: AIX grep has a line
+ # limit of 2048, but all sed's we know have understand at least 4000.
+ if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+ dirpart=`$as_dirname -- "$mf" ||
+$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$mf" : 'X\(//\)[^/]' \| \
+ X"$mf" : 'X\(//\)$' \| \
+ X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$mf" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ else
+ continue
+ fi
+ # Extract the definition of DEPDIR, am__include, and am__quote
+ # from the Makefile without running 'make'.
+ DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+ test -z "$DEPDIR" && continue
+ am__include=`sed -n 's/^am__include = //p' < "$mf"`
+ test -z "$am__include" && continue
+ am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+ # Find all dependency output files, they are included files with
+ # $(DEPDIR) in their names. We invoke sed twice because it is the
+ # simplest approach to changing $(DEPDIR) to its actual value in the
+ # expansion.
+ for file in `sed -n "
+ s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
+ # Make sure the directory exists.
+ test -f "$dirpart/$file" && continue
+ fdir=`$as_dirname -- "$file" ||
+$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$file" : 'X\(//\)[^/]' \| \
+ X"$file" : 'X\(//\)$' \| \
+ X"$file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ as_dir=$dirpart/$fdir; as_fn_mkdir_p
+ # echo "creating $dirpart/$file"
+ echo '# dummy' > "$dirpart/$file"
+ done
+ done
+}
+ ;;
+
+ esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+ as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ ac_config_status_args=
+ test "$silent" = yes &&
+ ac_config_status_args="$ac_config_status_args --quiet"
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..2b8b3b1
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,929 @@
+# configure.ac
+
+AC_INIT([tmux], 3.3a)
+AC_PREREQ([2.60])
+
+AC_CONFIG_AUX_DIR(etc)
+AC_CONFIG_LIBOBJ_DIR(compat)
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+
+AC_CANONICAL_HOST
+
+# When CFLAGS isn't set at this stage and gcc is detected by the macro below,
+# autoconf will automatically use CFLAGS="-O2 -g". Prevent that by using an
+# empty default.
+: ${CFLAGS=""}
+
+# Save user CPPFLAGS, CFLAGS and LDFLAGS. We need to change them because
+# AC_CHECK_HEADER doesn't give us any other way to update the include
+# paths. But for Makefile.am we want to use AM_CPPFLAGS and friends.
+SAVED_CFLAGS="$CFLAGS"
+SAVED_CPPFLAGS="$CPPFLAGS"
+SAVED_LDFLAGS="$LDFLAGS"
+
+# Is this oss-fuzz build?
+AC_ARG_ENABLE(
+ fuzzing,
+ AS_HELP_STRING(--enable-fuzzing, build fuzzers)
+)
+AC_ARG_VAR(
+ FUZZING_LIBS,
+ AS_HELP_STRING(libraries to link fuzzing targets with)
+)
+
+# Set up convenient fuzzing defaults before initializing compiler.
+if test "x$enable_fuzzing" = xyes; then
+ AC_DEFINE(NEED_FUZZING)
+ test "x$CC" = x && CC=clang
+ test "x$FUZZING_LIBS" = x && \
+ FUZZING_LIBS="-fsanitize=fuzzer"
+ test "x$SAVED_CFLAGS" = x && \
+ AM_CFLAGS="-g -fsanitize=fuzzer-no-link,address"
+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
+AC_PROG_CPP
+AC_PROG_EGREP
+AC_PROG_INSTALL
+AC_PROG_YACC
+PKG_PROG_PKG_CONFIG
+AC_USE_SYSTEM_EXTENSIONS
+
+# Default tmux.conf goes in /etc not ${prefix}/etc.
+test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc
+
+# Is this --enable-debug?
+case "x$VERSION" in xnext*) enable_debug=yes;; esac
+AC_ARG_ENABLE(
+ debug,
+ AS_HELP_STRING(--enable-debug, enable debug build flags),
+)
+AM_CONDITIONAL(IS_DEBUG, test "x$enable_debug" = xyes)
+
+# Is this a static build?
+AC_ARG_ENABLE(
+ static,
+ AS_HELP_STRING(--enable-static, create a static build)
+)
+if test "x$enable_static" = xyes; then
+ case "$host_os" in
+ *darwin*)
+ AC_MSG_ERROR([static linking is not supported on macOS])
+ ;;
+ esac
+ test "x$PKG_CONFIG" != x && PKG_CONFIG="$PKG_CONFIG --static"
+ AM_LDFLAGS="-static $AM_LDFLAGS"
+ LDFLAGS="$AM_LDFLAGS $SAVED_LDFLAGS"
+fi
+
+# Allow default TERM to be set.
+AC_ARG_WITH(
+ TERM,
+ AS_HELP_STRING(--with-TERM, set default TERM),
+ [DEFAULT_TERM=$withval],
+ [DEFAULT_TERM=]
+)
+case "x$DEFAULT_TERM" in
+ xscreen*|xtmux*|x)
+ ;;
+ *)
+ AC_MSG_ERROR("unsuitable TERM (must be screen* or tmux*)")
+ ;;
+esac
+
+# Do we need fuzzers?
+AM_CONDITIONAL(NEED_FUZZING, test "x$enable_fuzzing" = xyes)
+
+# Is this gcc?
+AM_CONDITIONAL(IS_GCC, test "x$GCC" = xyes -a "x$enable_fuzzing" != xyes)
+
+# Is this Sun CC?
+AC_EGREP_CPP(
+ yes,
+ [
+ #ifdef __SUNPRO_C
+ yes
+ #endif
+ ],
+ found_suncc=yes,
+ found_suncc=no
+)
+AM_CONDITIONAL(IS_SUNCC, test "x$found_suncc" = xyes)
+
+# Check for various headers. Alternatives included from compat.h.
+AC_CHECK_HEADERS([ \
+ bitstring.h \
+ dirent.h \
+ fcntl.h \
+ inttypes.h \
+ libproc.h \
+ libutil.h \
+ ndir.h \
+ paths.h \
+ pty.h \
+ stdint.h \
+ sys/dir.h \
+ sys/ndir.h \
+ sys/tree.h \
+ ucred.h \
+ util.h \
+])
+
+# Look for sys_signame.
+AC_SEARCH_LIBS(sys_signame, , AC_DEFINE(HAVE_SYS_SIGNAME))
+
+# Look for fmod.
+AC_CHECK_LIB(m, fmod)
+
+# Look for library needed for flock.
+AC_SEARCH_LIBS(flock, bsd)
+
+# Check for functions that are replaced or omitted.
+AC_CHECK_FUNCS([ \
+ dirfd \
+ flock \
+ prctl \
+ proc_pidinfo \
+ getpeerucred \
+ sysconf \
+])
+
+# Check for functions with a compatibility implementation.
+AC_REPLACE_FUNCS([ \
+ asprintf \
+ cfmakeraw \
+ clock_gettime \
+ closefrom \
+ explicit_bzero \
+ fgetln \
+ freezero \
+ getdtablecount \
+ getdtablesize \
+ getpeereid \
+ getline \
+ getprogname \
+ memmem \
+ setenv \
+ setproctitle \
+ strcasestr \
+ strlcat \
+ strlcpy \
+ strndup \
+ strsep \
+])
+AC_FUNC_STRNLEN
+
+# Check if strtonum works.
+AC_MSG_CHECKING([for working strtonum])
+AC_RUN_IFELSE([AC_LANG_PROGRAM(
+ [#include <stdlib.h>],
+ [return (strtonum("0", 0, 1, NULL) == 0 ? 0 : 1);]
+ )],
+ [AC_DEFINE(HAVE_STRTONUM) AC_MSG_RESULT(yes)],
+ [AC_LIBOBJ(strtonum) AC_MSG_RESULT(no)],
+ [AC_LIBOBJ(strtonum) AC_MSG_RESULT(no)]
+)
+
+# Clang sanitizers wrap reallocarray even if it isn't available on the target
+# system. When compiled it always returns NULL and crashes the program. To
+# detect this we need a more complicated test.
+AC_MSG_CHECKING([for working reallocarray])
+AC_RUN_IFELSE([AC_LANG_PROGRAM(
+ [#include <stdlib.h>],
+ [return (reallocarray(NULL, 1, 1) == NULL);]
+ )],
+ AC_MSG_RESULT(yes),
+ [AC_LIBOBJ(reallocarray) AC_MSG_RESULT([no])],
+ [AC_LIBOBJ(reallocarray) AC_MSG_RESULT([no])]
+)
+AC_MSG_CHECKING([for working recallocarray])
+AC_RUN_IFELSE([AC_LANG_PROGRAM(
+ [#include <stdlib.h>],
+ [return (recallocarray(NULL, 1, 1, 1) == NULL);]
+ )],
+ AC_MSG_RESULT(yes),
+ [AC_LIBOBJ(recallocarray) AC_MSG_RESULT([no])],
+ [AC_LIBOBJ(recallocarray) AC_MSG_RESULT([no])]
+)
+
+# Look for clock_gettime. Must come before event_init.
+AC_SEARCH_LIBS(clock_gettime, rt)
+
+# Always use our getopt because 1) glibc's doesn't enforce argument order 2)
+# musl does not set optarg to NULL for flags without arguments (although it is
+# not required to, but it is helpful) 3) there are probably other weird
+# implementations.
+AC_LIBOBJ(getopt)
+
+# Look for libevent. Try libevent_core or libevent with pkg-config first then
+# look for the library.
+PKG_CHECK_MODULES(
+ LIBEVENT_CORE,
+ [libevent_core >= 2],
+ [
+ AM_CPPFLAGS="$LIBEVENT_CORE_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBEVENT_CORE_LIBS $LIBS"
+ found_libevent=yes
+ ],
+ found_libevent=no
+)
+if test x$found_libevent = xno; then
+ PKG_CHECK_MODULES(
+ LIBEVENT,
+ [libevent >= 2],
+ [
+ AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBEVENT_LIBS $LIBS"
+ found_libevent=yes
+ ],
+ found_libevent=no
+ )
+fi
+if test x$found_libevent = xno; then
+ AC_SEARCH_LIBS(
+ event_init,
+ [event_core event event-1.4],
+ found_libevent=yes,
+ found_libevent=no
+ )
+fi
+AC_CHECK_HEADER(
+ event2/event.h,
+ AC_DEFINE(HAVE_EVENT2_EVENT_H),
+ [
+ AC_CHECK_HEADER(
+ event.h,
+ AC_DEFINE(HAVE_EVENT_H),
+ found_libevent=no
+ )
+ ]
+)
+if test "x$found_libevent" = xno; then
+ AC_MSG_ERROR("libevent not found")
+fi
+
+# Look for ncurses or curses. Try pkg-config first then directly for the
+# library.
+PKG_CHECK_MODULES(
+ LIBTINFO,
+ tinfo,
+ [
+ AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBTINFO_LIBS $LIBS"
+ found_ncurses=yes
+ ],
+ found_ncurses=no
+)
+if test "x$found_ncurses" = xno; then
+ PKG_CHECK_MODULES(
+ LIBNCURSES,
+ ncurses,
+ [
+ AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBNCURSES_LIBS $LIBS"
+ found_ncurses=yes
+ ],
+ found_ncurses=no
+ )
+fi
+if test "x$found_ncurses" = xno; then
+ PKG_CHECK_MODULES(
+ LIBNCURSESW,
+ ncursesw,
+ [
+ AM_CPPFLAGS="$LIBNCURSESW_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS"
+ LIBS="$LIBNCURSESW_LIBS $LIBS"
+ found_ncurses=yes
+ ],
+ found_ncurses=no
+ )
+fi
+if test "x$found_ncurses" = xno; then
+ AC_SEARCH_LIBS(
+ setupterm,
+ [tinfo ncurses ncursesw],
+ found_ncurses=yes,
+ found_ncurses=no
+ )
+ if test "x$found_ncurses" = xyes; then
+ AC_CHECK_HEADER(
+ ncurses.h,
+ LIBS="$LIBS -lncurses",
+ found_ncurses=no
+ )
+ fi
+fi
+if test "x$found_ncurses" = xyes; then
+ CPPFLAGS="$CPPFLAGS -DHAVE_NCURSES_H"
+ AC_DEFINE(HAVE_NCURSES_H)
+else
+ AC_CHECK_LIB(
+ curses,
+ setupterm,
+ found_curses=yes,
+ found_curses=no
+ )
+ AC_CHECK_HEADER(
+ curses.h,
+ ,
+ found_curses=no
+ )
+ if test "x$found_curses" = xyes; then
+ LIBS="$LIBS -lcurses"
+ CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H"
+ AC_DEFINE(HAVE_CURSES_H)
+ else
+ AC_MSG_ERROR("curses not found")
+ fi
+fi
+
+# Look for utempter.
+AC_ARG_ENABLE(
+ utempter,
+ AS_HELP_STRING(--enable-utempter, use utempter if it is installed)
+)
+if test "x$enable_utempter" = xyes; then
+ AC_CHECK_HEADER(utempter.h, enable_utempter=yes, enable_utempter=no)
+ if test "x$enable_utempter" = xyes; then
+ AC_SEARCH_LIBS(
+ utempter_add_record,
+ utempter,
+ enable_utempter=yes,
+ enable_utempter=no
+ )
+ fi
+ if test "x$enable_utempter" = xyes; then
+ AC_DEFINE(HAVE_UTEMPTER)
+ else
+ AC_MSG_ERROR("utempter not found")
+ fi
+fi
+
+# Look for utf8proc.
+AC_ARG_ENABLE(
+ utf8proc,
+ AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed)
+)
+if test "x$enable_utf8proc" = xyes; then
+ AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no)
+ if test "x$enable_utf8proc" = xyes; then
+ AC_SEARCH_LIBS(
+ utf8proc_charwidth,
+ utf8proc,
+ enable_utf8proc=yes,
+ enable_utf8proc=no
+ )
+ fi
+ if test "x$enable_utf8proc" = xyes; then
+ AC_DEFINE(HAVE_UTF8PROC)
+ else
+ AC_MSG_ERROR("utf8proc not found")
+ fi
+fi
+AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes])
+
+# Check for systemd support.
+AC_ARG_ENABLE(
+ systemd,
+ AS_HELP_STRING(--enable-systemd, enable systemd integration)
+)
+if test x"$enable_systemd" = xyes; then
+ PKG_CHECK_MODULES(
+ SYSTEMD,
+ libsystemd,
+ [
+ AM_CPPFLAGS="$SYSTEMD_CFLAGS $AM_CPPFLAGS"
+ CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS"
+ LIBS="$SYSTEMD_LIBS $LIBS"
+ found_systemd=yes
+ ],
+ found_systemd=no
+ )
+ if test "x$found_systemd" = xyes; then
+ AC_DEFINE(HAVE_SYSTEMD)
+ else
+ AC_MSG_ERROR("systemd not found")
+ fi
+fi
+AM_CONDITIONAL(HAVE_SYSTEMD, [test "x$found_systemd" = xyes])
+
+# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
+AC_MSG_CHECKING(for b64_ntop)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ #include <resolv.h>
+ ],
+ [
+ b64_ntop(NULL, 0, NULL, 0);
+ ])],
+ found_b64_ntop=yes,
+ found_b64_ntop=no
+)
+AC_MSG_RESULT($found_b64_ntop)
+OLD_LIBS="$LIBS"
+if test "x$found_b64_ntop" = xno; then
+ AC_MSG_CHECKING(for b64_ntop with -lresolv)
+ LIBS="$OLD_LIBS -lresolv"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ #include <resolv.h>
+ ],
+ [
+ b64_ntop(NULL, 0, NULL, 0);
+ ])],
+ found_b64_ntop=yes,
+ found_b64_ntop=no
+ )
+ AC_MSG_RESULT($found_b64_ntop)
+fi
+if test "x$found_b64_ntop" = xno; then
+ AC_MSG_CHECKING(for b64_ntop with -lnetwork)
+ LIBS="$OLD_LIBS -lnetwork"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ #include <resolv.h>
+ ],
+ [
+ b64_ntop(NULL, 0, NULL, 0);
+ ])],
+ found_b64_ntop=yes,
+ found_b64_ntop=no
+ )
+ AC_MSG_RESULT($found_b64_ntop)
+fi
+if test "x$found_b64_ntop" = xyes; then
+ AC_DEFINE(HAVE_B64_NTOP)
+else
+ LIBS="$OLD_LIBS"
+ AC_LIBOBJ(base64)
+fi
+
+# Look for networking libraries.
+AC_SEARCH_LIBS(inet_ntoa, nsl)
+AC_SEARCH_LIBS(socket, socket)
+AC_CHECK_LIB(xnet, socket)
+
+# Check if using glibc and have malloc_trim(3). The glibc free(3) is pretty bad
+# about returning memory to the kernel unless the application tells it when to
+# with malloc_trim(3).
+AC_MSG_CHECKING(if free doesn't work very well)
+AC_LINK_IFELSE([AC_LANG_SOURCE(
+ [
+ #include <stdlib.h>
+ #ifdef __GLIBC__
+ #include <malloc.h>
+ int main(void) {
+ malloc_trim (0);
+ exit(0);
+ }
+ #else
+ no
+ #endif
+ ])],
+ found_malloc_trim=yes,
+ found_malloc_trim=no
+)
+AC_MSG_RESULT($found_malloc_trim)
+if test "x$found_malloc_trim" = xyes; then
+ AC_DEFINE(HAVE_MALLOC_TRIM)
+fi
+
+# Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95
+# (_XOPEN_SOURCE and _XOPEN_SOURCE_EXTENDED) (see xopen_networking(7)). On
+# others, UNIX 03 (_XOPEN_SOURCE 600, see standards(7) on Solaris).
+XOPEN_DEFINES=
+AC_MSG_CHECKING(for CMSG_DATA)
+AC_EGREP_CPP(
+ yes,
+ [
+ #include <sys/socket.h>
+ #ifdef CMSG_DATA
+ yes
+ #endif
+ ],
+ found_cmsg_data=yes,
+ found_cmsg_data=no
+)
+AC_MSG_RESULT($found_cmsg_data)
+if test "x$found_cmsg_data" = xno; then
+ AC_MSG_CHECKING(if CMSG_DATA needs _XOPEN_SOURCE_EXTENDED)
+ AC_EGREP_CPP(
+ yes,
+ [
+ #define _XOPEN_SOURCE 1
+ #define _XOPEN_SOURCE_EXTENDED 1
+ #include <sys/socket.h>
+ #ifdef CMSG_DATA
+ yes
+ #endif
+ ],
+ found_cmsg_data=yes,
+ found_cmsg_data=no
+ )
+ AC_MSG_RESULT($found_cmsg_data)
+ if test "x$found_cmsg_data" = xyes; then
+ XOPEN_DEFINES="-D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED"
+ fi
+fi
+if test "x$found_cmsg_data" = xno; then
+ AC_MSG_CHECKING(if CMSG_DATA needs _XOPEN_SOURCE 600)
+ AC_EGREP_CPP(
+ yes,
+ [
+ #define _XOPEN_SOURCE 600
+ #include <sys/socket.h>
+ #ifdef CMSG_DATA
+ yes
+ #endif
+ ],
+ found_cmsg_data=yes,
+ found_cmsg_data=no
+ )
+ AC_MSG_RESULT($found_cmsg_data)
+ if test "x$found_cmsg_data" = xyes; then
+ XOPEN_DEFINES="-D_XOPEN_SOURCE=600"
+ else
+ AC_MSG_ERROR("CMSG_DATA not found")
+ fi
+fi
+AC_SUBST(XOPEN_DEFINES)
+
+# Look for err and friends in err.h.
+AC_CHECK_FUNC(err, found_err_h=yes, found_err_h=no)
+AC_CHECK_FUNC(errx, , found_err_h=no)
+AC_CHECK_FUNC(warn, , found_err_h=no)
+AC_CHECK_FUNC(warnx, , found_err_h=no)
+if test "x$found_err_h" = xyes; then
+ AC_CHECK_HEADER(err.h, , found_err_h=no)
+else
+ AC_LIBOBJ(err)
+fi
+
+# Look for imsg_init in libutil.
+AC_SEARCH_LIBS(imsg_init, util, found_imsg_init=yes, found_imsg_init=no)
+if test "x$found_imsg_init" = xyes; then
+ AC_DEFINE(HAVE_IMSG)
+else
+ AC_LIBOBJ(imsg)
+ AC_LIBOBJ(imsg-buffer)
+fi
+
+# Look for daemon, compat/daemon.c used if missing. Solaris 10 has it in
+# libresolv, but no declaration anywhere, so check for declaration as well as
+# function.
+AC_CHECK_FUNC(daemon, found_daemon=yes, found_daemon=no)
+AC_CHECK_DECL(
+ daemon,
+ ,
+ found_daemon=no,
+ [
+ #include <stdlib.h>
+ #include <unistd.h>
+ ]
+)
+if test "x$found_daemon" = xyes; then
+ AC_DEFINE(HAVE_DAEMON)
+else
+ AC_LIBOBJ(daemon)
+fi
+
+# Look for stravis, compat/{vis,unvis}.c used if missing.
+AC_CHECK_FUNC(stravis, found_stravis=yes, found_stravis=no)
+if test "x$found_stravis" = xyes; then
+ AC_MSG_CHECKING(if strnvis is broken)
+ AC_EGREP_HEADER([strnvis\(char \*, const char \*, size_t, int\)],
+ vis.h,
+ AC_MSG_RESULT(no),
+ [found_stravis=no])
+ if test "x$found_stravis" = xno; then
+ AC_MSG_RESULT(yes)
+ fi
+fi
+if test "x$found_stravis" = xyes; then
+ AC_CHECK_DECL(
+ VIS_DQ,
+ ,
+ found_stravis=no,
+ [
+ #include <stdlib.h>
+ #include <vis.h>
+ ]
+)
+fi
+if test "x$found_stravis" = xyes; then
+ AC_DEFINE(HAVE_VIS)
+else
+ AC_LIBOBJ(vis)
+ AC_LIBOBJ(unvis)
+fi
+
+# Look for fdforkpty and forkpty in libutil.
+AC_SEARCH_LIBS(fdforkpty, util, found_fdforkpty=yes, found_fdforkpty=no)
+if test "x$found_fdforkpty" = xyes; then
+ AC_DEFINE(HAVE_FDFORKPTY)
+else
+ AC_LIBOBJ(fdforkpty)
+fi
+AC_SEARCH_LIBS(forkpty, util, found_forkpty=yes, found_forkpty=no)
+if test "x$found_forkpty" = xyes; then
+ AC_DEFINE(HAVE_FORKPTY)
+fi
+AM_CONDITIONAL(NEED_FORKPTY, test "x$found_forkpty" = xno)
+
+# Look for kinfo_getfile in libutil.
+AC_SEARCH_LIBS(kinfo_getfile, [util util-freebsd])
+
+# Look for a suitable queue.h.
+AC_CHECK_DECL(
+ TAILQ_CONCAT,
+ found_queue_h=yes,
+ found_queue_h=no,
+ [#include <sys/queue.h>]
+)
+AC_CHECK_DECL(
+ TAILQ_PREV,
+ ,
+ found_queue_h=no,
+ [#include <sys/queue.h>]
+)
+AC_CHECK_DECL(
+ TAILQ_REPLACE,
+ ,
+ found_queue_h=no,
+ [#include <sys/queue.h>]
+)
+if test "x$found_queue_h" = xyes; then
+ AC_DEFINE(HAVE_QUEUE_H)
+fi
+
+# Look for __progname.
+AC_MSG_CHECKING(for __progname)
+AC_LINK_IFELSE([AC_LANG_SOURCE(
+ [
+ #include <stdio.h>
+ #include <stdlib.h>
+ extern char *__progname;
+ int main(void) {
+ const char *cp = __progname;
+ printf("%s\n", cp);
+ exit(0);
+ }
+ ])],
+ [AC_DEFINE(HAVE___PROGNAME) AC_MSG_RESULT(yes)],
+ AC_MSG_RESULT(no)
+)
+
+# Look for program_invocation_short_name.
+AC_MSG_CHECKING(for program_invocation_short_name)
+AC_LINK_IFELSE([AC_LANG_SOURCE(
+ [
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ int main(void) {
+ const char *cp = program_invocation_short_name;
+ printf("%s\n", cp);
+ exit(0);
+ }
+ ])],
+ [AC_DEFINE(HAVE_PROGRAM_INVOCATION_SHORT_NAME) AC_MSG_RESULT(yes)],
+ AC_MSG_RESULT(no)
+)
+
+# Look for prctl(PR_SET_NAME).
+AC_CHECK_DECL(
+ PR_SET_NAME,
+ AC_DEFINE(HAVE_PR_SET_NAME),
+ ,
+ [#include <sys/prctl.h>]
+)
+
+# Look for setsockopt(SO_PEERCRED).
+AC_CHECK_DECL(
+ SO_PEERCRED,
+ AC_DEFINE(HAVE_SO_PEERCRED),
+ ,
+ [#include <sys/socket.h>]
+)
+
+# Look for fcntl(F_CLOSEM).
+AC_CHECK_DECL(
+ F_CLOSEM,
+ AC_DEFINE(HAVE_FCNTL_CLOSEM),
+ ,
+ [#include <fcntl.h>]
+)
+
+# Look for /proc/$$.
+AC_MSG_CHECKING(for /proc/\$\$)
+if test -d /proc/$$; then
+ AC_DEFINE(HAVE_PROC_PID)
+ AC_MSG_RESULT(yes)
+else
+ AC_MSG_RESULT(no)
+fi
+
+# Try to figure out what the best value for TERM might be.
+if test "x$DEFAULT_TERM" = x; then
+ DEFAULT_TERM=screen
+ AC_MSG_CHECKING(TERM)
+ AC_RUN_IFELSE([AC_LANG_SOURCE(
+ [
+ #include <stdio.h>
+ #include <stdlib.h>
+ #if defined(HAVE_CURSES_H)
+ #include <curses.h>
+ #elif defined(HAVE_NCURSES_H)
+ #include <ncurses.h>
+ #endif
+ #include <term.h>
+ int main(void) {
+ if (setupterm("screen-256color", -1, NULL) != OK)
+ exit(1);
+ exit(0);
+ }
+ ])],
+ [DEFAULT_TERM=screen-256color],
+ ,
+ [DEFAULT_TERM=screen]
+ )
+ AC_RUN_IFELSE([AC_LANG_SOURCE(
+ [
+ #include <stdio.h>
+ #include <stdlib.h>
+ #if defined(HAVE_CURSES_H)
+ #include <curses.h>
+ #elif defined(HAVE_NCURSES_H)
+ #include <ncurses.h>
+ #endif
+ #include <term.h>
+ int main(void) {
+ if (setupterm("tmux", -1, NULL) != OK)
+ exit(1);
+ exit(0);
+ }
+ ])],
+ [DEFAULT_TERM=tmux],
+ ,
+ [DEFAULT_TERM=screen]
+ )
+ AC_RUN_IFELSE([AC_LANG_SOURCE(
+ [
+ #include <stdio.h>
+ #include <stdlib.h>
+ #if defined(HAVE_CURSES_H)
+ #include <curses.h>
+ #elif defined(HAVE_NCURSES_H)
+ #include <ncurses.h>
+ #endif
+ #include <term.h>
+ int main(void) {
+ if (setupterm("tmux-256color", -1, NULL) != OK)
+ exit(1);
+ exit(0);
+ }
+ ])],
+ [DEFAULT_TERM=tmux-256color],
+ ,
+ [DEFAULT_TERM=screen]
+ )
+ AC_MSG_RESULT($DEFAULT_TERM)
+fi
+AC_SUBST(DEFAULT_TERM)
+
+# Man page defaults to mdoc.
+MANFORMAT=mdoc
+AC_SUBST(MANFORMAT)
+
+# Figure out the platform.
+AC_MSG_CHECKING(platform)
+case "$host_os" in
+ *aix*)
+ AC_MSG_RESULT(aix)
+ PLATFORM=aix
+ ;;
+ *darwin*)
+ AC_MSG_RESULT(darwin)
+ PLATFORM=darwin
+ #
+ # macOS uses __dead2 instead of __dead, like FreeBSD. But it defines
+ # __dead away so it needs to be removed before we can replace it.
+ #
+ AC_DEFINE(BROKEN___DEAD)
+ #
+ # macOS CMSG_FIRSTHDR is broken, so redefine it with a working one.
+ # daemon works but has some stupid side effects, so use our internal
+ # version which has a workaround.
+ #
+ AC_DEFINE(BROKEN_CMSG_FIRSTHDR)
+ AC_LIBOBJ(daemon)
+ AC_LIBOBJ(daemon-darwin)
+ #
+ # macOS wcwidth(3) is bad, so complain and suggest using utf8proc
+ # instead.
+ #
+ if test "x$enable_utf8proc" = x; then
+ AC_MSG_NOTICE([])
+ AC_MSG_NOTICE([ macOS library support for Unicode is very poor,])
+ AC_MSG_NOTICE([ particularly for complex codepoints like emojis;])
+ AC_MSG_NOTICE([ to use these correctly, configuring with])
+ AC_MSG_NOTICE([ --enable-utf8proc is recommended. To build])
+ AC_MSG_NOTICE([ without anyway, use --disable-utf8proc])
+ AC_MSG_NOTICE([])
+ AC_MSG_ERROR([must give --enable-utf8proc or --disable-utf8proc])
+ fi
+ ;;
+ *dragonfly*)
+ AC_MSG_RESULT(dragonfly)
+ PLATFORM=dragonfly
+ ;;
+ *linux*)
+ AC_MSG_RESULT(linux)
+ PLATFORM=linux
+ ;;
+ *freebsd*)
+ AC_MSG_RESULT(freebsd)
+ PLATFORM=freebsd
+ ;;
+ *netbsd*)
+ AC_MSG_RESULT(netbsd)
+ PLATFORM=netbsd
+ ;;
+ *openbsd*)
+ AC_MSG_RESULT(openbsd)
+ PLATFORM=openbsd
+ ;;
+ *sunos*)
+ AC_MSG_RESULT(sunos)
+ PLATFORM=sunos
+ ;;
+ *solaris*)
+ AC_MSG_RESULT(sunos)
+ PLATFORM=sunos
+ case `/usr/bin/nroff --version 2>&1` in
+ *GNU*)
+ # Solaris 11.4 and later use GNU groff.
+ MANFORMAT=mdoc
+ ;;
+ *)
+ # Solaris 2.0 to 11.3 use AT&T nroff.
+ MANFORMAT=man
+ ;;
+ esac
+ ;;
+ *hpux*)
+ AC_MSG_RESULT(hpux)
+ PLATFORM=hpux
+ ;;
+ *cygwin*|*msys*)
+ AC_MSG_RESULT(cygwin)
+ PLATFORM=cygwin
+ ;;
+ *haiku*)
+ AC_MSG_RESULT(haiku)
+ PLATFORM=haiku
+ ;;
+ *)
+ AC_MSG_RESULT(unknown)
+ PLATFORM=unknown
+ ;;
+esac
+AC_SUBST(PLATFORM)
+AM_CONDITIONAL(IS_AIX, test "x$PLATFORM" = xaix)
+AM_CONDITIONAL(IS_DARWIN, test "x$PLATFORM" = xdarwin)
+AM_CONDITIONAL(IS_DRAGONFLY, test "x$PLATFORM" = xdragonfly)
+AM_CONDITIONAL(IS_LINUX, test "x$PLATFORM" = xlinux)
+AM_CONDITIONAL(IS_FREEBSD, test "x$PLATFORM" = xfreebsd)
+AM_CONDITIONAL(IS_NETBSD, test "x$PLATFORM" = xnetbsd)
+AM_CONDITIONAL(IS_OPENBSD, test "x$PLATFORM" = xopenbsd)
+AM_CONDITIONAL(IS_SUNOS, test "x$PLATFORM" = xsunos)
+AM_CONDITIONAL(IS_HPUX, test "x$PLATFORM" = xhpux)
+AM_CONDITIONAL(IS_HAIKU, test "x$PLATFORM" = xhaiku)
+AM_CONDITIONAL(IS_UNKNOWN, test "x$PLATFORM" = xunknown)
+
+# Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user
+# variables.
+AC_SUBST(AM_CPPFLAGS)
+CPPFLAGS="$SAVED_CPPFLAGS"
+AC_SUBST(AM_CFLAGS)
+CFLAGS="$SAVED_CFLAGS"
+AC_SUBST(AM_LDFLAGS)
+LDFLAGS="$SAVED_LDFLAGS"
+
+# autoconf should create a Makefile.
+AC_CONFIG_FILES(Makefile)
+AC_OUTPUT
diff --git a/control-notify.c b/control-notify.c
new file mode 100644
index 0000000..6ff0e43
--- /dev/null
+++ b/control-notify.c
@@ -0,0 +1,236 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2012 George Nachman <tmux@georgester.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 "tmux.h"
+
+#define CONTROL_SHOULD_NOTIFY_CLIENT(c) \
+ ((c) != NULL && ((c)->flags & CLIENT_CONTROL))
+
+void
+control_notify_pane_mode_changed(int pane)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ continue;
+
+ control_write(c, "%%pane-mode-changed %%%u", pane);
+ }
+}
+
+void
+control_notify_window_layout_changed(struct window *w)
+{
+ struct client *c;
+ struct session *s;
+ struct winlink *wl;
+ const char *template;
+ char *cp;
+
+ template = "%layout-change #{window_id} #{window_layout} "
+ "#{window_visible_layout} #{window_raw_flags}";
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
+ continue;
+ s = c->session;
+
+ if (winlink_find_by_window_id(&s->windows, w->id) == NULL)
+ continue;
+
+ /*
+ * When the last pane in a window is closed it won't have a
+ * layout root and we don't need to inform the client about the
+ * layout change because the whole window will go away soon.
+ */
+ if (w->layout_root == NULL)
+ continue;
+
+ wl = winlink_find_by_window(&s->windows, w);
+ if (wl != NULL) {
+ cp = format_single(NULL, template, c, NULL, wl, NULL);
+ control_write(c, "%s", cp);
+ free(cp);
+ }
+ }
+}
+
+void
+control_notify_window_pane_changed(struct window *w)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ continue;
+
+ control_write(c, "%%window-pane-changed @%u %%%u", w->id,
+ w->active->id);
+ }
+}
+
+void
+control_notify_window_unlinked(__unused struct session *s, struct window *w)
+{
+ struct client *c;
+ struct session *cs;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
+ continue;
+ cs = c->session;
+
+ if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
+ control_write(c, "%%window-close @%u", w->id);
+ else
+ control_write(c, "%%unlinked-window-close @%u", w->id);
+ }
+}
+
+void
+control_notify_window_linked(__unused struct session *s, struct window *w)
+{
+ struct client *c;
+ struct session *cs;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
+ continue;
+ cs = c->session;
+
+ if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
+ control_write(c, "%%window-add @%u", w->id);
+ else
+ control_write(c, "%%unlinked-window-add @%u", w->id);
+ }
+}
+
+void
+control_notify_window_renamed(struct window *w)
+{
+ struct client *c;
+ struct session *cs;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
+ continue;
+ cs = c->session;
+
+ if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
+ control_write(c, "%%window-renamed @%u %s", w->id,
+ w->name);
+ } else {
+ control_write(c, "%%unlinked-window-renamed @%u %s",
+ w->id, w->name);
+ }
+ }
+}
+
+void
+control_notify_client_session_changed(struct client *cc)
+{
+ struct client *c;
+ struct session *s;
+
+ if (cc->session == NULL)
+ return;
+ s = cc->session;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
+ continue;
+
+ if (cc == c) {
+ control_write(c, "%%session-changed $%u %s", s->id,
+ s->name);
+ } else {
+ control_write(c, "%%client-session-changed %s $%u %s",
+ cc->name, s->id, s->name);
+ }
+ }
+}
+
+void
+control_notify_client_detached(struct client *cc)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ control_write(c, "%%client-detached %s", cc->name);
+ }
+}
+
+void
+control_notify_session_renamed(struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ continue;
+
+ control_write(c, "%%session-renamed $%u %s", s->id, s->name);
+ }
+}
+
+void
+control_notify_session_created(__unused struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ continue;
+
+ control_write(c, "%%sessions-changed");
+ }
+}
+
+void
+control_notify_session_closed(__unused struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ continue;
+
+ control_write(c, "%%sessions-changed");
+ }
+}
+
+void
+control_notify_session_window_changed(struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
+ continue;
+
+ control_write(c, "%%session-window-changed $%u @%u", s->id,
+ s->curw->window->id);
+ }
+}
diff --git a/control.c b/control.c
new file mode 100644
index 0000000..73286e0
--- /dev/null
+++ b/control.c
@@ -0,0 +1,1107 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2012 George Nachman <tmux@georgester.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 <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Block of data to output. Each client has one "all" queue of blocks and
+ * another queue for each pane (in struct client_offset). %output blocks are
+ * added to both queues and other output lines (notifications) added only to
+ * the client queue.
+ *
+ * When a client becomes writeable, data from blocks on the pane queue are sent
+ * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written,
+ * it is removed from both pane and client queues and if this means non-%output
+ * blocks are now at the head of the client queue, they are written.
+ *
+ * This means a %output block holds up any subsequent non-%output blocks until
+ * it is written which enforces ordering even if the client cannot accept the
+ * entire block in one go.
+ */
+struct control_block {
+ size_t size;
+ char *line;
+ uint64_t t;
+
+ TAILQ_ENTRY(control_block) entry;
+ TAILQ_ENTRY(control_block) all_entry;
+};
+
+/* Control client pane. */
+struct control_pane {
+ u_int pane;
+
+ /*
+ * Offsets into the pane data. The first (offset) is the data we have
+ * written; the second (queued) the data we have queued (pointed to by
+ * a block).
+ */
+ struct window_pane_offset offset;
+ struct window_pane_offset queued;
+
+ int flags;
+#define CONTROL_PANE_OFF 0x1
+#define CONTROL_PANE_PAUSED 0x2
+
+ int pending_flag;
+ TAILQ_ENTRY(control_pane) pending_entry;
+
+ TAILQ_HEAD(, control_block) blocks;
+
+ RB_ENTRY(control_pane) entry;
+};
+RB_HEAD(control_panes, control_pane);
+
+/* Subscription pane. */
+struct control_sub_pane {
+ u_int pane;
+ u_int idx;
+ char *last;
+
+ RB_ENTRY(control_sub_pane) entry;
+};
+RB_HEAD(control_sub_panes, control_sub_pane);
+
+/* Subscription window. */
+struct control_sub_window {
+ u_int window;
+ u_int idx;
+ char *last;
+
+ RB_ENTRY(control_sub_window) entry;
+};
+RB_HEAD(control_sub_windows, control_sub_window);
+
+/* Control client subscription. */
+struct control_sub {
+ char *name;
+ char *format;
+
+ enum control_sub_type type;
+ u_int id;
+
+ char *last;
+ struct control_sub_panes panes;
+ struct control_sub_windows windows;
+
+ RB_ENTRY(control_sub) entry;
+};
+RB_HEAD(control_subs, control_sub);
+
+/* Control client state. */
+struct control_state {
+ struct control_panes panes;
+
+ TAILQ_HEAD(, control_pane) pending_list;
+ u_int pending_count;
+
+ TAILQ_HEAD(, control_block) all_blocks;
+
+ struct bufferevent *read_event;
+ struct bufferevent *write_event;
+
+ struct control_subs subs;
+ struct event subs_timer;
+};
+
+/* Low and high watermarks. */
+#define CONTROL_BUFFER_LOW 512
+#define CONTROL_BUFFER_HIGH 8192
+
+/* Minimum to write to each client. */
+#define CONTROL_WRITE_MINIMUM 32
+
+/* Maximum age for clients that are not using pause mode. */
+#define CONTROL_MAXIMUM_AGE 300000
+
+/* Flags to ignore client. */
+#define CONTROL_IGNORE_FLAGS \
+ (CLIENT_CONTROL_NOOUTPUT| \
+ CLIENT_UNATTACHEDFLAGS)
+
+/* Compare client panes. */
+static int
+control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
+{
+ if (cp1->pane < cp2->pane)
+ return (-1);
+ if (cp1->pane > cp2->pane)
+ return (1);
+ return (0);
+}
+RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
+
+/* Compare client subs. */
+static int
+control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2)
+{
+ return (strcmp(csub1->name, csub2->name));
+}
+RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp);
+
+/* Compare client subscription panes. */
+static int
+control_sub_pane_cmp(struct control_sub_pane *csp1,
+ struct control_sub_pane *csp2)
+{
+ if (csp1->pane < csp2->pane)
+ return (-1);
+ if (csp1->pane > csp2->pane)
+ return (1);
+ if (csp1->idx < csp2->idx)
+ return (-1);
+ if (csp1->idx > csp2->idx)
+ return (1);
+ return (0);
+}
+RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry,
+ control_sub_pane_cmp);
+
+/* Compare client subscription windows. */
+static int
+control_sub_window_cmp(struct control_sub_window *csw1,
+ struct control_sub_window *csw2)
+{
+ if (csw1->window < csw2->window)
+ return (-1);
+ if (csw1->window > csw2->window)
+ return (1);
+ if (csw1->idx < csw2->idx)
+ return (-1);
+ if (csw1->idx > csw2->idx)
+ return (1);
+ return (0);
+}
+RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry,
+ control_sub_window_cmp);
+
+/* Free a subscription. */
+static void
+control_free_sub(struct control_state *cs, struct control_sub *csub)
+{
+ struct control_sub_pane *csp, *csp1;
+ struct control_sub_window *csw, *csw1;
+
+ RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) {
+ RB_REMOVE(control_sub_panes, &csub->panes, csp);
+ free(csp);
+ }
+ RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
+ RB_REMOVE(control_sub_windows, &csub->windows, csw);
+ free(csw);
+ }
+ free(csub->last);
+
+ RB_REMOVE(control_subs, &cs->subs, csub);
+ free(csub->name);
+ free(csub->format);
+ free(csub);
+}
+
+/* Free a block. */
+static void
+control_free_block(struct control_state *cs, struct control_block *cb)
+{
+ free(cb->line);
+ TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
+ free(cb);
+}
+
+/* Get pane offsets for this client. */
+static struct control_pane *
+control_get_pane(struct client *c, struct window_pane *wp)
+{
+ struct control_state *cs = c->control_state;
+ struct control_pane cp = { .pane = wp->id };
+
+ return (RB_FIND(control_panes, &cs->panes, &cp));
+}
+
+/* Add pane offsets for this client. */
+static struct control_pane *
+control_add_pane(struct client *c, struct window_pane *wp)
+{
+ struct control_state *cs = c->control_state;
+ struct control_pane *cp;
+
+ cp = control_get_pane(c, wp);
+ if (cp != NULL)
+ return (cp);
+
+ cp = xcalloc(1, sizeof *cp);
+ cp->pane = wp->id;
+ RB_INSERT(control_panes, &cs->panes, cp);
+
+ memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
+ memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
+ TAILQ_INIT(&cp->blocks);
+
+ return (cp);
+}
+
+/* Discard output for a pane. */
+static void
+control_discard_pane(struct client *c, struct control_pane *cp)
+{
+ struct control_state *cs = c->control_state;
+ struct control_block *cb, *cb1;
+
+ TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
+ TAILQ_REMOVE(&cp->blocks, cb, entry);
+ control_free_block(cs, cb);
+ }
+}
+
+/* Get actual pane for this client. */
+static struct window_pane *
+control_window_pane(struct client *c, u_int pane)
+{
+ struct window_pane *wp;
+
+ if (c->session == NULL)
+ return (NULL);
+ if ((wp = window_pane_find_by_id(pane)) == NULL)
+ return (NULL);
+ if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
+ return (NULL);
+ return (wp);
+}
+
+/* Reset control offsets. */
+void
+control_reset_offsets(struct client *c)
+{
+ struct control_state *cs = c->control_state;
+ struct control_pane *cp, *cp1;
+
+ RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
+ RB_REMOVE(control_panes, &cs->panes, cp);
+ free(cp);
+ }
+
+ TAILQ_INIT(&cs->pending_list);
+ cs->pending_count = 0;
+}
+
+/* Get offsets for client. */
+struct window_pane_offset *
+control_pane_offset(struct client *c, struct window_pane *wp, int *off)
+{
+ struct control_state *cs = c->control_state;
+ struct control_pane *cp;
+
+ if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
+ *off = 0;
+ return (NULL);
+ }
+
+ cp = control_get_pane(c, wp);
+ if (cp == NULL || (cp->flags & CONTROL_PANE_PAUSED)) {
+ *off = 0;
+ return (NULL);
+ }
+ if (cp->flags & CONTROL_PANE_OFF) {
+ *off = 1;
+ return (NULL);
+ }
+ *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW);
+ return (&cp->offset);
+}
+
+/* Set pane as on. */
+void
+control_set_pane_on(struct client *c, struct window_pane *wp)
+{
+ struct control_pane *cp;
+
+ cp = control_get_pane(c, wp);
+ if (cp != NULL && (cp->flags & CONTROL_PANE_OFF)) {
+ cp->flags &= ~CONTROL_PANE_OFF;
+ memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
+ memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
+ }
+}
+
+/* Set pane as off. */
+void
+control_set_pane_off(struct client *c, struct window_pane *wp)
+{
+ struct control_pane *cp;
+
+ cp = control_add_pane(c, wp);
+ cp->flags |= CONTROL_PANE_OFF;
+}
+
+/* Continue a paused pane. */
+void
+control_continue_pane(struct client *c, struct window_pane *wp)
+{
+ struct control_pane *cp;
+
+ cp = control_get_pane(c, wp);
+ if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) {
+ cp->flags &= ~CONTROL_PANE_PAUSED;
+ memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
+ memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
+ control_write(c, "%%continue %%%u", wp->id);
+ }
+}
+
+/* Pause a pane. */
+void
+control_pause_pane(struct client *c, struct window_pane *wp)
+{
+ struct control_pane *cp;
+
+ cp = control_add_pane(c, wp);
+ if (~cp->flags & CONTROL_PANE_PAUSED) {
+ cp->flags |= CONTROL_PANE_PAUSED;
+ control_discard_pane(c, cp);
+ control_write(c, "%%pause %%%u", wp->id);
+ }
+}
+
+/* Write a line. */
+static void printflike(2, 0)
+control_vwrite(struct client *c, const char *fmt, va_list ap)
+{
+ struct control_state *cs = c->control_state;
+ char *s;
+
+ xvasprintf(&s, fmt, ap);
+ log_debug("%s: %s: writing line: %s", __func__, c->name, s);
+
+ bufferevent_write(cs->write_event, s, strlen(s));
+ bufferevent_write(cs->write_event, "\n", 1);
+
+ bufferevent_enable(cs->write_event, EV_WRITE);
+ free(s);
+}
+
+/* Write a line. */
+void
+control_write(struct client *c, const char *fmt, ...)
+{
+ struct control_state *cs = c->control_state;
+ struct control_block *cb;
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ if (TAILQ_EMPTY(&cs->all_blocks)) {
+ control_vwrite(c, fmt, ap);
+ va_end(ap);
+ return;
+ }
+
+ cb = xcalloc(1, sizeof *cb);
+ xvasprintf(&cb->line, fmt, ap);
+ TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
+ cb->t = get_timer();
+
+ log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
+ bufferevent_enable(cs->write_event, EV_WRITE);
+
+ va_end(ap);
+}
+
+/* Check age for this pane. */
+static int
+control_check_age(struct client *c, struct window_pane *wp,
+ struct control_pane *cp)
+{
+ struct control_block *cb;
+ uint64_t t, age;
+
+ cb = TAILQ_FIRST(&cp->blocks);
+ if (cb == NULL)
+ return (0);
+ t = get_timer();
+ if (cb->t >= t)
+ return (0);
+
+ age = t - cb->t;
+ log_debug("%s: %s: %%%u is %llu behind", __func__, c->name, wp->id,
+ (unsigned long long)age);
+
+ if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
+ if (age < c->pause_age)
+ return (0);
+ cp->flags |= CONTROL_PANE_PAUSED;
+ control_discard_pane(c, cp);
+ control_write(c, "%%pause %%%u", wp->id);
+ } else {
+ if (age < CONTROL_MAXIMUM_AGE)
+ return (0);
+ c->exit_message = xstrdup("too far behind");
+ c->flags |= CLIENT_EXIT;
+ control_discard(c);
+ }
+ return (1);
+}
+
+/* Write output from a pane. */
+void
+control_write_output(struct client *c, struct window_pane *wp)
+{
+ struct control_state *cs = c->control_state;
+ struct control_pane *cp;
+ struct control_block *cb;
+ size_t new_size;
+
+ if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
+ return;
+
+ if (c->flags & CONTROL_IGNORE_FLAGS) {
+ cp = control_get_pane(c, wp);
+ if (cp != NULL)
+ goto ignore;
+ return;
+ }
+ cp = control_add_pane(c, wp);
+ if (cp->flags & (CONTROL_PANE_OFF|CONTROL_PANE_PAUSED))
+ goto ignore;
+ if (control_check_age(c, wp, cp))
+ return;
+
+ window_pane_get_new_data(wp, &cp->queued, &new_size);
+ if (new_size == 0)
+ return;
+ window_pane_update_used_data(wp, &cp->queued, new_size);
+
+ cb = xcalloc(1, sizeof *cb);
+ cb->size = new_size;
+ TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
+ cb->t = get_timer();
+
+ TAILQ_INSERT_TAIL(&cp->blocks, cb, entry);
+ log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name,
+ cb->size, wp->id);
+
+ if (!cp->pending_flag) {
+ log_debug("%s: %s: %%%u now pending", __func__, c->name,
+ wp->id);
+ TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry);
+ cp->pending_flag = 1;
+ cs->pending_count++;
+ }
+ bufferevent_enable(cs->write_event, EV_WRITE);
+ return;
+
+ignore:
+ log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id);
+ window_pane_update_used_data(wp, &cp->offset, SIZE_MAX);
+ window_pane_update_used_data(wp, &cp->queued, SIZE_MAX);
+}
+
+/* Control client error callback. */
+static enum cmd_retval
+control_error(struct cmdq_item *item, void *data)
+{
+ struct client *c = cmdq_get_client(item);
+ char *error = data;
+
+ cmdq_guard(item, "begin", 1);
+ control_write(c, "parse error: %s", error);
+ cmdq_guard(item, "error", 1);
+
+ free(error);
+ return (CMD_RETURN_NORMAL);
+}
+
+/* Control client error callback. */
+static void
+control_error_callback(__unused struct bufferevent *bufev,
+ __unused short what, void *data)
+{
+ struct client *c = data;
+
+ c->flags |= CLIENT_EXIT;
+}
+
+/* Control client input callback. Read lines and fire commands. */
+static void
+control_read_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct client *c = data;
+ struct control_state *cs = c->control_state;
+ struct evbuffer *buffer = cs->read_event->input;
+ char *line, *error;
+ struct cmdq_state *state;
+ enum cmd_parse_status status;
+
+ for (;;) {
+ line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
+ if (line == NULL)
+ break;
+ log_debug("%s: %s: %s", __func__, c->name, line);
+ if (*line == '\0') { /* empty line detach */
+ free(line);
+ c->flags |= CLIENT_EXIT;
+ break;
+ }
+
+ state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
+ status = cmd_parse_and_append(line, NULL, c, state, &error);
+ if (status == CMD_PARSE_ERROR)
+ cmdq_append(c, cmdq_get_callback(control_error, error));
+ cmdq_free_state(state);
+
+ free(line);
+ }
+}
+
+/* Does this control client have outstanding data to write? */
+int
+control_all_done(struct client *c)
+{
+ struct control_state *cs = c->control_state;
+
+ if (!TAILQ_EMPTY(&cs->all_blocks))
+ return (0);
+ return (EVBUFFER_LENGTH(cs->write_event->output) == 0);
+}
+
+/* Flush all blocks until output. */
+static void
+control_flush_all_blocks(struct client *c)
+{
+ struct control_state *cs = c->control_state;
+ struct control_block *cb, *cb1;
+
+ TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) {
+ if (cb->size != 0)
+ break;
+ log_debug("%s: %s: flushing line: %s", __func__, c->name,
+ cb->line);
+
+ bufferevent_write(cs->write_event, cb->line, strlen(cb->line));
+ bufferevent_write(cs->write_event, "\n", 1);
+ control_free_block(cs, cb);
+ }
+}
+
+/* Append data to buffer. */
+static struct evbuffer *
+control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
+ struct evbuffer *message, struct window_pane *wp, size_t size)
+{
+ u_char *new_data;
+ size_t new_size;
+ u_int i;
+
+ if (message == NULL) {
+ message = evbuffer_new();
+ if (message == NULL)
+ fatalx("out of memory");
+ if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
+ evbuffer_add_printf(message,
+ "%%extended-output %%%u %llu : ", wp->id,
+ (unsigned long long)age);
+ } else
+ evbuffer_add_printf(message, "%%output %%%u ", wp->id);
+ }
+
+ new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
+ if (new_size < size)
+ fatalx("not enough data: %zu < %zu", new_size, size);
+ for (i = 0; i < size; i++) {
+ if (new_data[i] < ' ' || new_data[i] == '\\')
+ evbuffer_add_printf(message, "\\%03o", new_data[i]);
+ else
+ evbuffer_add_printf(message, "%c", new_data[i]);
+ }
+ window_pane_update_used_data(wp, &cp->offset, size);
+ return (message);
+}
+
+/* Write buffer. */
+static void
+control_write_data(struct client *c, struct evbuffer *message)
+{
+ struct control_state *cs = c->control_state;
+
+ log_debug("%s: %s: %.*s", __func__, c->name,
+ (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message));
+
+ evbuffer_add(message, "\n", 1);
+ bufferevent_write_buffer(cs->write_event, message);
+ evbuffer_free(message);
+}
+
+/* Write output to client. */
+static int
+control_write_pending(struct client *c, struct control_pane *cp, size_t limit)
+{
+ struct control_state *cs = c->control_state;
+ struct window_pane *wp = NULL;
+ struct evbuffer *message = NULL;
+ size_t used = 0, size;
+ struct control_block *cb, *cb1;
+ uint64_t age, t = get_timer();
+
+ wp = control_window_pane(c, cp->pane);
+ if (wp == NULL || wp->fd == -1) {
+ TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
+ TAILQ_REMOVE(&cp->blocks, cb, entry);
+ control_free_block(cs, cb);
+ }
+ control_flush_all_blocks(c);
+ return (0);
+ }
+
+ while (used != limit && !TAILQ_EMPTY(&cp->blocks)) {
+ if (control_check_age(c, wp, cp)) {
+ if (message != NULL)
+ evbuffer_free(message);
+ message = NULL;
+ break;
+ }
+
+ cb = TAILQ_FIRST(&cp->blocks);
+ if (cb->t < t)
+ age = t - cb->t;
+ else
+ age = 0;
+ log_debug("%s: %s: output block %zu (age %llu) for %%%u "
+ "(used %zu/%zu)", __func__, c->name, cb->size,
+ (unsigned long long)age, cp->pane, used, limit);
+
+ size = cb->size;
+ if (size > limit - used)
+ size = limit - used;
+ used += size;
+
+ message = control_append_data(c, cp, age, message, wp, size);
+
+ cb->size -= size;
+ if (cb->size == 0) {
+ TAILQ_REMOVE(&cp->blocks, cb, entry);
+ control_free_block(cs, cb);
+
+ cb = TAILQ_FIRST(&cs->all_blocks);
+ if (cb != NULL && cb->size == 0) {
+ if (wp != NULL && message != NULL) {
+ control_write_data(c, message);
+ message = NULL;
+ }
+ control_flush_all_blocks(c);
+ }
+ }
+ }
+ if (message != NULL)
+ control_write_data(c, message);
+ return (!TAILQ_EMPTY(&cp->blocks));
+}
+
+/* Control client write callback. */
+static void
+control_write_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct client *c = data;
+ struct control_state *cs = c->control_state;
+ struct control_pane *cp, *cp1;
+ struct evbuffer *evb = cs->write_event->output;
+ size_t space, limit;
+
+ control_flush_all_blocks(c);
+
+ while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) {
+ if (cs->pending_count == 0)
+ break;
+ space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb);
+ log_debug("%s: %s: %zu bytes available, %u panes", __func__,
+ c->name, space, cs->pending_count);
+
+ limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */
+ if (limit < CONTROL_WRITE_MINIMUM)
+ limit = CONTROL_WRITE_MINIMUM;
+
+ TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) {
+ if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH)
+ break;
+ if (control_write_pending(c, cp, limit))
+ continue;
+ TAILQ_REMOVE(&cs->pending_list, cp, pending_entry);
+ cp->pending_flag = 0;
+ cs->pending_count--;
+ }
+ }
+ if (EVBUFFER_LENGTH(evb) == 0)
+ bufferevent_disable(cs->write_event, EV_WRITE);
+}
+
+/* Initialize for control mode. */
+void
+control_start(struct client *c)
+{
+ struct control_state *cs;
+
+ if (c->flags & CLIENT_CONTROLCONTROL) {
+ close(c->out_fd);
+ c->out_fd = -1;
+ } else
+ setblocking(c->out_fd, 0);
+ setblocking(c->fd, 0);
+
+ cs = c->control_state = xcalloc(1, sizeof *cs);
+ RB_INIT(&cs->panes);
+ TAILQ_INIT(&cs->pending_list);
+ TAILQ_INIT(&cs->all_blocks);
+ RB_INIT(&cs->subs);
+
+ cs->read_event = bufferevent_new(c->fd, control_read_callback,
+ control_write_callback, control_error_callback, c);
+ bufferevent_enable(cs->read_event, EV_READ);
+
+ if (c->flags & CLIENT_CONTROLCONTROL)
+ cs->write_event = cs->read_event;
+ else {
+ cs->write_event = bufferevent_new(c->out_fd, NULL,
+ control_write_callback, control_error_callback, c);
+ }
+ bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
+ 0);
+
+ if (c->flags & CLIENT_CONTROLCONTROL) {
+ bufferevent_write(cs->write_event, "\033P1000p", 7);
+ bufferevent_enable(cs->write_event, EV_WRITE);
+ }
+}
+
+/* Discard all output for a client. */
+void
+control_discard(struct client *c)
+{
+ struct control_state *cs = c->control_state;
+ struct control_pane *cp;
+
+ RB_FOREACH(cp, control_panes, &cs->panes)
+ control_discard_pane(c, cp);
+ bufferevent_disable(cs->read_event, EV_READ);
+}
+
+/* Stop control mode. */
+void
+control_stop(struct client *c)
+{
+ struct control_state *cs = c->control_state;
+ struct control_block *cb, *cb1;
+ struct control_sub *csub, *csub1;
+
+ if (~c->flags & CLIENT_CONTROLCONTROL)
+ bufferevent_free(cs->write_event);
+ bufferevent_free(cs->read_event);
+
+ RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1)
+ control_free_sub(cs, csub);
+ if (evtimer_initialized(&cs->subs_timer))
+ evtimer_del(&cs->subs_timer);
+
+ TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
+ control_free_block(cs, cb);
+ control_reset_offsets(c);
+
+ free(cs);
+}
+
+/* Check session subscription. */
+static void
+control_check_subs_session(struct client *c, struct control_sub *csub)
+{
+ struct session *s = c->session;
+ struct format_tree *ft;
+ char *value;
+
+ ft = format_create_defaults(NULL, c, s, NULL, NULL);
+ value = format_expand(ft, csub->format);
+ format_free(ft);
+
+ if (csub->last != NULL && strcmp(value, csub->last) == 0) {
+ free(value);
+ return;
+ }
+ control_write(c,
+ "%%subscription-changed %s $%u - - - : %s",
+ csub->name, s->id, value);
+ free(csub->last);
+ csub->last = value;
+}
+
+/* Check pane subscription. */
+static void
+control_check_subs_pane(struct client *c, struct control_sub *csub)
+{
+ struct session *s = c->session;
+ struct window_pane *wp;
+ struct window *w;
+ struct winlink *wl;
+ struct format_tree *ft;
+ char *value;
+ struct control_sub_pane *csp, find;
+
+ wp = window_pane_find_by_id(csub->id);
+ if (wp == NULL || wp->fd == -1)
+ return;
+ w = wp->window;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->session != s)
+ continue;
+
+ ft = format_create_defaults(NULL, c, s, wl, wp);
+ value = format_expand(ft, csub->format);
+ format_free(ft);
+
+ find.pane = wp->id;
+ find.idx = wl->idx;
+
+ csp = RB_FIND(control_sub_panes, &csub->panes, &find);
+ if (csp == NULL) {
+ csp = xcalloc(1, sizeof *csp);
+ csp->pane = wp->id;
+ csp->idx = wl->idx;
+ RB_INSERT(control_sub_panes, &csub->panes, csp);
+ }
+
+ if (csp->last != NULL && strcmp(value, csp->last) == 0) {
+ free(value);
+ continue;
+ }
+ control_write(c,
+ "%%subscription-changed %s $%u @%u %u %%%u : %s",
+ csub->name, s->id, w->id, wl->idx, wp->id, value);
+ free(csp->last);
+ csp->last = value;
+ }
+}
+
+/* Check all panes subscription. */
+static void
+control_check_subs_all_panes(struct client *c, struct control_sub *csub)
+{
+ struct session *s = c->session;
+ struct window_pane *wp;
+ struct window *w;
+ struct winlink *wl;
+ struct format_tree *ft;
+ char *value;
+ struct control_sub_pane *csp, find;
+
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ w = wl->window;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ ft = format_create_defaults(NULL, c, s, wl, wp);
+ value = format_expand(ft, csub->format);
+ format_free(ft);
+
+ find.pane = wp->id;
+ find.idx = wl->idx;
+
+ csp = RB_FIND(control_sub_panes, &csub->panes, &find);
+ if (csp == NULL) {
+ csp = xcalloc(1, sizeof *csp);
+ csp->pane = wp->id;
+ csp->idx = wl->idx;
+ RB_INSERT(control_sub_panes, &csub->panes, csp);
+ }
+
+ if (csp->last != NULL &&
+ strcmp(value, csp->last) == 0) {
+ free(value);
+ continue;
+ }
+ control_write(c,
+ "%%subscription-changed %s $%u @%u %u %%%u : %s",
+ csub->name, s->id, w->id, wl->idx, wp->id, value);
+ free(csp->last);
+ csp->last = value;
+ }
+ }
+}
+
+/* Check window subscription. */
+static void
+control_check_subs_window(struct client *c, struct control_sub *csub)
+{
+ struct session *s = c->session;
+ struct window *w;
+ struct winlink *wl;
+ struct format_tree *ft;
+ char *value;
+ struct control_sub_window *csw, find;
+
+ w = window_find_by_id(csub->id);
+ if (w == NULL)
+ return;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->session != s)
+ continue;
+
+ ft = format_create_defaults(NULL, c, s, wl, NULL);
+ value = format_expand(ft, csub->format);
+ format_free(ft);
+
+ find.window = w->id;
+ find.idx = wl->idx;
+
+ csw = RB_FIND(control_sub_windows, &csub->windows, &find);
+ if (csw == NULL) {
+ csw = xcalloc(1, sizeof *csw);
+ csw->window = w->id;
+ csw->idx = wl->idx;
+ RB_INSERT(control_sub_windows, &csub->windows, csw);
+ }
+
+ if (csw->last != NULL && strcmp(value, csw->last) == 0) {
+ free(value);
+ continue;
+ }
+ control_write(c,
+ "%%subscription-changed %s $%u @%u %u - : %s",
+ csub->name, s->id, w->id, wl->idx, value);
+ free(csw->last);
+ csw->last = value;
+ }
+}
+
+/* Check all windows subscription. */
+static void
+control_check_subs_all_windows(struct client *c, struct control_sub *csub)
+{
+ struct session *s = c->session;
+ struct window *w;
+ struct winlink *wl;
+ struct format_tree *ft;
+ char *value;
+ struct control_sub_window *csw, find;
+
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ w = wl->window;
+
+ ft = format_create_defaults(NULL, c, s, wl, NULL);
+ value = format_expand(ft, csub->format);
+ format_free(ft);
+
+ find.window = w->id;
+ find.idx = wl->idx;
+
+ csw = RB_FIND(control_sub_windows, &csub->windows, &find);
+ if (csw == NULL) {
+ csw = xcalloc(1, sizeof *csw);
+ csw->window = w->id;
+ csw->idx = wl->idx;
+ RB_INSERT(control_sub_windows, &csub->windows, csw);
+ }
+
+ if (csw->last != NULL && strcmp(value, csw->last) == 0) {
+ free(value);
+ continue;
+ }
+ control_write(c,
+ "%%subscription-changed %s $%u @%u %u - : %s",
+ csub->name, s->id, w->id, wl->idx, value);
+ free(csw->last);
+ csw->last = value;
+ }
+}
+
+/* Check subscriptions timer. */
+static void
+control_check_subs_timer(__unused int fd, __unused short events, void *data)
+{
+ struct client *c = data;
+ struct control_state *cs = c->control_state;
+ struct control_sub *csub, *csub1;
+ struct timeval tv = { .tv_sec = 1 };
+
+ log_debug("%s: timer fired", __func__);
+ evtimer_add(&cs->subs_timer, &tv);
+
+ RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
+ switch (csub->type) {
+ case CONTROL_SUB_SESSION:
+ control_check_subs_session(c, csub);
+ break;
+ case CONTROL_SUB_PANE:
+ control_check_subs_pane(c, csub);
+ break;
+ case CONTROL_SUB_ALL_PANES:
+ control_check_subs_all_panes(c, csub);
+ break;
+ case CONTROL_SUB_WINDOW:
+ control_check_subs_window(c, csub);
+ break;
+ case CONTROL_SUB_ALL_WINDOWS:
+ control_check_subs_all_windows(c, csub);
+ break;
+ }
+ }
+}
+
+/* Add a subscription. */
+void
+control_add_sub(struct client *c, const char *name, enum control_sub_type type,
+ int id, const char *format)
+{
+ struct control_state *cs = c->control_state;
+ struct control_sub *csub, find;
+ struct timeval tv = { .tv_sec = 1 };
+
+ find.name = (char *)name;
+ if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
+ control_free_sub(cs, csub);
+
+ csub = xcalloc(1, sizeof *csub);
+ csub->name = xstrdup(name);
+ csub->type = type;
+ csub->id = id;
+ csub->format = xstrdup(format);
+ RB_INSERT(control_subs, &cs->subs, csub);
+
+ RB_INIT(&csub->panes);
+ RB_INIT(&csub->windows);
+
+ if (!evtimer_initialized(&cs->subs_timer))
+ evtimer_set(&cs->subs_timer, control_check_subs_timer, c);
+ if (!evtimer_pending(&cs->subs_timer, NULL))
+ evtimer_add(&cs->subs_timer, &tv);
+}
+
+/* Remove a subscription. */
+void
+control_remove_sub(struct client *c, const char *name)
+{
+ struct control_state *cs = c->control_state;
+ struct control_sub *csub, find;
+
+ find.name = (char *)name;
+ if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
+ control_free_sub(cs, csub);
+ if (RB_EMPTY(&cs->subs))
+ evtimer_del(&cs->subs_timer);
+}
diff --git a/environ.c b/environ.c
new file mode 100644
index 0000000..74d672e
--- /dev/null
+++ b/environ.c
@@ -0,0 +1,272 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 <fnmatch.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Environment - manipulate a set of environment variables.
+ */
+
+RB_HEAD(environ, environ_entry);
+static int environ_cmp(struct environ_entry *, struct environ_entry *);
+RB_GENERATE_STATIC(environ, environ_entry, entry, environ_cmp);
+
+static int
+environ_cmp(struct environ_entry *envent1, struct environ_entry *envent2)
+{
+ return (strcmp(envent1->name, envent2->name));
+}
+
+/* Initialise the environment. */
+struct environ *
+environ_create(void)
+{
+ struct environ *env;
+
+ env = xcalloc(1, sizeof *env);
+ RB_INIT(env);
+
+ return (env);
+}
+
+/* Free an environment. */
+void
+environ_free(struct environ *env)
+{
+ struct environ_entry *envent, *envent1;
+
+ RB_FOREACH_SAFE(envent, environ, env, envent1) {
+ RB_REMOVE(environ, env, envent);
+ free(envent->name);
+ free(envent->value);
+ free(envent);
+ }
+ free(env);
+}
+
+struct environ_entry *
+environ_first(struct environ *env)
+{
+ return (RB_MIN(environ, env));
+}
+
+struct environ_entry *
+environ_next(struct environ_entry *envent)
+{
+ return (RB_NEXT(environ, env, envent));
+}
+
+/* Copy one environment into another. */
+void
+environ_copy(struct environ *srcenv, struct environ *dstenv)
+{
+ struct environ_entry *envent;
+
+ RB_FOREACH(envent, environ, srcenv) {
+ if (envent->value == NULL)
+ environ_clear(dstenv, envent->name);
+ else {
+ environ_set(dstenv, envent->name, envent->flags,
+ "%s", envent->value);
+ }
+ }
+}
+
+/* Find an environment variable. */
+struct environ_entry *
+environ_find(struct environ *env, const char *name)
+{
+ struct environ_entry envent;
+
+ envent.name = (char *) name;
+ return (RB_FIND(environ, env, &envent));
+}
+
+/* Set an environment variable. */
+void
+environ_set(struct environ *env, const char *name, int flags, const char *fmt,
+ ...)
+{
+ struct environ_entry *envent;
+ va_list ap;
+
+ va_start(ap, fmt);
+ if ((envent = environ_find(env, name)) != NULL) {
+ envent->flags = flags;
+ free(envent->value);
+ xvasprintf(&envent->value, fmt, ap);
+ } else {
+ envent = xmalloc(sizeof *envent);
+ envent->name = xstrdup(name);
+ envent->flags = flags;
+ xvasprintf(&envent->value, fmt, ap);
+ RB_INSERT(environ, env, envent);
+ }
+ va_end(ap);
+}
+
+/* Clear an environment variable. */
+void
+environ_clear(struct environ *env, const char *name)
+{
+ struct environ_entry *envent;
+
+ if ((envent = environ_find(env, name)) != NULL) {
+ free(envent->value);
+ envent->value = NULL;
+ } else {
+ envent = xmalloc(sizeof *envent);
+ envent->name = xstrdup(name);
+ envent->flags = 0;
+ envent->value = NULL;
+ RB_INSERT(environ, env, envent);
+ }
+}
+
+/* Set an environment variable from a NAME=VALUE string. */
+void
+environ_put(struct environ *env, const char *var, int flags)
+{
+ char *name, *value;
+
+ value = strchr(var, '=');
+ if (value == NULL)
+ return;
+ value++;
+
+ name = xstrdup(var);
+ name[strcspn(name, "=")] = '\0';
+
+ environ_set(env, name, flags, "%s", value);
+ free(name);
+}
+
+/* Unset an environment variable. */
+void
+environ_unset(struct environ *env, const char *name)
+{
+ struct environ_entry *envent;
+
+ if ((envent = environ_find(env, name)) == NULL)
+ return;
+ RB_REMOVE(environ, env, envent);
+ free(envent->name);
+ free(envent->value);
+ free(envent);
+}
+
+/* Copy variables from a destination into a source environment. */
+void
+environ_update(struct options *oo, struct environ *src, struct environ *dst)
+{
+ struct environ_entry *envent;
+ struct options_entry *o;
+ struct options_array_item *a;
+ union options_value *ov;
+
+ o = options_get(oo, "update-environment");
+ if (o == NULL)
+ return;
+ 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;
+ }
+ if (envent == NULL)
+ environ_clear(dst, ov->string);
+ else
+ environ_set(dst, envent->name, 0, "%s", envent->value);
+ a = options_array_next(a);
+ }
+}
+
+/* Push environment into the real environment - use after fork(). */
+void
+environ_push(struct environ *env)
+{
+ struct environ_entry *envent;
+
+ environ = xcalloc(1, sizeof *environ);
+ RB_FOREACH(envent, environ, env) {
+ if (envent->value != NULL &&
+ *envent->name != '\0' &&
+ (~envent->flags & ENVIRON_HIDDEN))
+ setenv(envent->name, envent->value, 1);
+ }
+}
+
+/* Log the environment. */
+void
+environ_log(struct environ *env, const char *fmt, ...)
+{
+ struct environ_entry *envent;
+ va_list ap;
+ char *prefix;
+
+ va_start(ap, fmt);
+ vasprintf(&prefix, fmt, ap);
+ va_end(ap);
+
+ RB_FOREACH(envent, environ, env) {
+ if (envent->value != NULL && *envent->name != '\0') {
+ log_debug("%s%s=%s", prefix, envent->name,
+ envent->value);
+ }
+ }
+
+ free(prefix);
+}
+
+/* Create initial environment for new child. */
+struct environ *
+environ_for_session(struct session *s, int no_TERM)
+{
+ struct environ *env;
+ const char *value;
+ int idx;
+
+ env = environ_create();
+ environ_copy(global_environ, env);
+ if (s != NULL)
+ environ_copy(s->environ, env);
+
+ if (!no_TERM) {
+ value = options_get_string(global_options, "default-terminal");
+ environ_set(env, "TERM", 0, "%s", value);
+ environ_set(env, "TERM_PROGRAM", 0, "%s", "tmux");
+ environ_set(env, "TERM_PROGRAM_VERSION", 0, "%s", getversion());
+ }
+
+ if (s != NULL)
+ idx = s->id;
+ else
+ idx = -1;
+ environ_set(env, "TMUX", 0, "%s,%ld,%d", socket_path, (long)getpid(),
+ idx);
+
+ return (env);
+}
diff --git a/etc/compile b/etc/compile
new file mode 100755
index 0000000..2ab71e4
--- /dev/null
+++ b/etc/compile
@@ -0,0 +1,348 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand '-c -o'.
+
+scriptversion=2016-01-11.22; # UTC
+
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+nl='
+'
+
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent tools from complaining about whitespace usage.
+IFS=" "" $nl"
+
+file_conv=
+
+# func_file_conv build_file lazy
+# Convert a $build file to $host form and store it in $file
+# Currently only supports Windows hosts. If the determined conversion
+# type is listed in (the comma separated) LAZY, no conversion will
+# take place.
+func_file_conv ()
+{
+ file=$1
+ case $file in
+ / | /[!/]*) # absolute file, and not a UNC file
+ if test -z "$file_conv"; then
+ # lazily determine how to convert abs files
+ case `uname -s` in
+ MINGW*)
+ file_conv=mingw
+ ;;
+ CYGWIN*)
+ file_conv=cygwin
+ ;;
+ *)
+ file_conv=wine
+ ;;
+ esac
+ fi
+ case $file_conv/,$2, in
+ *,$file_conv,*)
+ ;;
+ mingw/*)
+ file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
+ ;;
+ cygwin/*)
+ file=`cygpath -m "$file" || echo "$file"`
+ ;;
+ wine/*)
+ file=`winepath -w "$file" || echo "$file"`
+ ;;
+ esac
+ ;;
+ esac
+}
+
+# func_cl_dashL linkdir
+# Make cl look for libraries in LINKDIR
+func_cl_dashL ()
+{
+ func_file_conv "$1"
+ if test -z "$lib_path"; then
+ lib_path=$file
+ else
+ lib_path="$lib_path;$file"
+ fi
+ linker_opts="$linker_opts -LIBPATH:$file"
+}
+
+# func_cl_dashl library
+# Do a library search-path lookup for cl
+func_cl_dashl ()
+{
+ lib=$1
+ found=no
+ save_IFS=$IFS
+ IFS=';'
+ for dir in $lib_path $LIB
+ do
+ IFS=$save_IFS
+ if $shared && test -f "$dir/$lib.dll.lib"; then
+ found=yes
+ lib=$dir/$lib.dll.lib
+ break
+ fi
+ if test -f "$dir/$lib.lib"; then
+ found=yes
+ lib=$dir/$lib.lib
+ break
+ fi
+ if test -f "$dir/lib$lib.a"; then
+ found=yes
+ lib=$dir/lib$lib.a
+ break
+ fi
+ done
+ IFS=$save_IFS
+
+ if test "$found" != yes; then
+ lib=$lib.lib
+ fi
+}
+
+# func_cl_wrapper cl arg...
+# Adjust compile command to suit cl
+func_cl_wrapper ()
+{
+ # Assume a capable shell
+ lib_path=
+ shared=:
+ linker_opts=
+ for arg
+ do
+ if test -n "$eat"; then
+ eat=
+ else
+ case $1 in
+ -o)
+ # configure might choose to run compile as 'compile cc -o foo foo.c'.
+ eat=1
+ case $2 in
+ *.o | *.[oO][bB][jJ])
+ func_file_conv "$2"
+ set x "$@" -Fo"$file"
+ shift
+ ;;
+ *)
+ func_file_conv "$2"
+ set x "$@" -Fe"$file"
+ shift
+ ;;
+ esac
+ ;;
+ -I)
+ eat=1
+ func_file_conv "$2" mingw
+ set x "$@" -I"$file"
+ shift
+ ;;
+ -I*)
+ func_file_conv "${1#-I}" mingw
+ set x "$@" -I"$file"
+ shift
+ ;;
+ -l)
+ eat=1
+ func_cl_dashl "$2"
+ set x "$@" "$lib"
+ shift
+ ;;
+ -l*)
+ func_cl_dashl "${1#-l}"
+ set x "$@" "$lib"
+ shift
+ ;;
+ -L)
+ eat=1
+ func_cl_dashL "$2"
+ ;;
+ -L*)
+ func_cl_dashL "${1#-L}"
+ ;;
+ -static)
+ shared=false
+ ;;
+ -Wl,*)
+ arg=${1#-Wl,}
+ save_ifs="$IFS"; IFS=','
+ for flag in $arg; do
+ IFS="$save_ifs"
+ linker_opts="$linker_opts $flag"
+ done
+ IFS="$save_ifs"
+ ;;
+ -Xlinker)
+ eat=1
+ linker_opts="$linker_opts $2"
+ ;;
+ -*)
+ set x "$@" "$1"
+ shift
+ ;;
+ *.cc | *.CC | *.cxx | *.CXX | *.[cC]++)
+ func_file_conv "$1"
+ set x "$@" -Tp"$file"
+ shift
+ ;;
+ *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO])
+ func_file_conv "$1" mingw
+ set x "$@" "$file"
+ shift
+ ;;
+ *)
+ set x "$@" "$1"
+ shift
+ ;;
+ esac
+ fi
+ shift
+ done
+ if test -n "$linker_opts"; then
+ linker_opts="-link$linker_opts"
+ fi
+ exec "$@" $linker_opts
+ exit 1
+}
+
+eat=
+
+case $1 in
+ '')
+ echo "$0: No command. Try '$0 --help' for more information." 1>&2
+ exit 1;
+ ;;
+ -h | --h*)
+ cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand '-c -o'.
+Remove '-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file 'INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+ exit $?
+ ;;
+ -v | --v*)
+ echo "compile $scriptversion"
+ exit $?
+ ;;
+ cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \
+ icl | *[/\\]icl | icl.exe | *[/\\]icl.exe )
+ func_cl_wrapper "$@" # Doesn't return...
+ ;;
+esac
+
+ofile=
+cfile=
+
+for arg
+do
+ if test -n "$eat"; then
+ eat=
+ else
+ case $1 in
+ -o)
+ # configure might choose to run compile as 'compile cc -o foo foo.c'.
+ # So we strip '-o arg' only if arg is an object.
+ eat=1
+ case $2 in
+ *.o | *.obj)
+ ofile=$2
+ ;;
+ *)
+ set x "$@" -o "$2"
+ shift
+ ;;
+ esac
+ ;;
+ *.c)
+ cfile=$1
+ set x "$@" "$1"
+ shift
+ ;;
+ *)
+ set x "$@" "$1"
+ shift
+ ;;
+ esac
+ fi
+ shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+ # If no '-o' option was seen then we might have been invoked from a
+ # pattern rule where we don't need one. That is ok -- this is a
+ # normal compilation that the losing compiler can handle. If no
+ # '.c' file was seen then we are probably linking. That is also
+ # ok.
+ exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use '[/\\:.-]' here to ensure that we don't use the same name
+# that we are using for the .o file. Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d
+while true; do
+ if mkdir "$lockdir" >/dev/null 2>&1; then
+ break
+ fi
+ sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+ test "$cofile" = "$ofile" || mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+ test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/etc/config.guess b/etc/config.guess
new file mode 100755
index 0000000..2193702
--- /dev/null
+++ b/etc/config.guess
@@ -0,0 +1,1473 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+# Copyright 1992-2017 Free Software Foundation, Inc.
+
+timestamp='2017-05-27'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program. This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+#
+# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
+#
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess
+#
+# Please send patches to <config-patches@gnu.org>.
+
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright 1992-2017 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit ;;
+ --version | -v )
+ echo "$version" ; exit ;;
+ --help | --h* | -h )
+ echo "$usage"; exit ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help" >&2
+ exit 1 ;;
+ * )
+ break ;;
+ esac
+done
+
+if test $# != 0; then
+ echo "$me: too many arguments$help" >&2
+ exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,) echo "int x;" > $dummy.c ;
+ for c in cc gcc c89 c99 ; do
+ if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+ CC_FOR_BUILD="$c"; break ;
+ fi ;
+ done ;
+ if test x"$CC_FOR_BUILD" = x ; then
+ CC_FOR_BUILD=no_compiler_found ;
+ fi
+ ;;
+ ,,*) CC_FOR_BUILD=$CC ;;
+ ,*,*) CC_FOR_BUILD=$HOST_CC ;;
+esac ; set_cc_for_build= ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+ PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+case "${UNAME_SYSTEM}" in
+Linux|GNU|GNU/*)
+ # If the system lacks a compiler, then just pick glibc.
+ # We could probably try harder.
+ LIBC=gnu
+
+ eval $set_cc_for_build
+ cat <<-EOF > $dummy.c
+ #include <features.h>
+ #if defined(__UCLIBC__)
+ LIBC=uclibc
+ #elif defined(__dietlibc__)
+ LIBC=dietlibc
+ #else
+ LIBC=gnu
+ #endif
+ EOF
+ eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+ ;;
+esac
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+ *:NetBSD:*:*)
+ # NetBSD (nbsd) targets should (where applicable) match one or
+ # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
+ # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently
+ # switched to ELF, *-*-netbsd* would select the old
+ # object file format. This provides both forward
+ # compatibility and a consistent mechanism for selecting the
+ # object file format.
+ #
+ # Note: NetBSD doesn't particularly care about the vendor
+ # portion of the name. We always set it to "unknown".
+ sysctl="sysctl -n hw.machine_arch"
+ UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \
+ /sbin/$sysctl 2>/dev/null || \
+ /usr/sbin/$sysctl 2>/dev/null || \
+ echo unknown)`
+ case "${UNAME_MACHINE_ARCH}" in
+ armeb) machine=armeb-unknown ;;
+ arm*) machine=arm-unknown ;;
+ sh3el) machine=shl-unknown ;;
+ sh3eb) machine=sh-unknown ;;
+ sh5el) machine=sh5le-unknown ;;
+ earmv*)
+ arch=`echo ${UNAME_MACHINE_ARCH} | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
+ endian=`echo ${UNAME_MACHINE_ARCH} | sed -ne 's,^.*\(eb\)$,\1,p'`
+ machine=${arch}${endian}-unknown
+ ;;
+ *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+ esac
+ # The Operating System including object format, if it has switched
+ # to ELF recently (or will in the future) and ABI.
+ case "${UNAME_MACHINE_ARCH}" in
+ earm*)
+ os=netbsdelf
+ ;;
+ arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+ eval $set_cc_for_build
+ if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ELF__
+ then
+ # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+ # Return netbsd for either. FIX?
+ os=netbsd
+ else
+ os=netbsdelf
+ fi
+ ;;
+ *)
+ os=netbsd
+ ;;
+ esac
+ # Determine ABI tags.
+ case "${UNAME_MACHINE_ARCH}" in
+ earm*)
+ expr='s/^earmv[0-9]/-eabi/;s/eb$//'
+ abi=`echo ${UNAME_MACHINE_ARCH} | sed -e "$expr"`
+ ;;
+ esac
+ # The OS release
+ # Debian GNU/NetBSD machines have a different userland, and
+ # thus, need a distinct triplet. However, they do not need
+ # kernel version information, so it can be replaced with a
+ # suitable tag, in the style of linux-gnu.
+ case "${UNAME_VERSION}" in
+ Debian*)
+ release='-gnu'
+ ;;
+ *)
+ release=`echo ${UNAME_RELEASE} | sed -e 's/[-_].*//' | cut -d. -f1,2`
+ ;;
+ esac
+ # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+ # contains redundant information, the shorter form:
+ # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+ echo "${machine}-${os}${release}${abi}"
+ exit ;;
+ *:Bitrig:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
+ echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE}
+ exit ;;
+ *:OpenBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+ echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
+ exit ;;
+ *:LibertyBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'`
+ echo ${UNAME_MACHINE_ARCH}-unknown-libertybsd${UNAME_RELEASE}
+ exit ;;
+ *:ekkoBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+ exit ;;
+ *:SolidBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE}
+ exit ;;
+ macppc:MirBSD:*:*)
+ echo powerpc-unknown-mirbsd${UNAME_RELEASE}
+ exit ;;
+ *:MirBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+ exit ;;
+ *:Sortix:*:*)
+ echo ${UNAME_MACHINE}-unknown-sortix
+ exit ;;
+ alpha:OSF1:*:*)
+ case $UNAME_RELEASE in
+ *4.0)
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+ ;;
+ *5.*)
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+ ;;
+ esac
+ # According to Compaq, /usr/sbin/psrinfo has been available on
+ # OSF/1 and Tru64 systems produced since 1995. I hope that
+ # covers most systems running today. This code pipes the CPU
+ # types through head -n 1, so we only detect the type of CPU 0.
+ ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+ case "$ALPHA_CPU_TYPE" in
+ "EV4 (21064)")
+ UNAME_MACHINE=alpha ;;
+ "EV4.5 (21064)")
+ UNAME_MACHINE=alpha ;;
+ "LCA4 (21066/21068)")
+ UNAME_MACHINE=alpha ;;
+ "EV5 (21164)")
+ UNAME_MACHINE=alphaev5 ;;
+ "EV5.6 (21164A)")
+ UNAME_MACHINE=alphaev56 ;;
+ "EV5.6 (21164PC)")
+ UNAME_MACHINE=alphapca56 ;;
+ "EV5.7 (21164PC)")
+ UNAME_MACHINE=alphapca57 ;;
+ "EV6 (21264)")
+ UNAME_MACHINE=alphaev6 ;;
+ "EV6.7 (21264A)")
+ UNAME_MACHINE=alphaev67 ;;
+ "EV6.8CB (21264C)")
+ UNAME_MACHINE=alphaev68 ;;
+ "EV6.8AL (21264B)")
+ UNAME_MACHINE=alphaev68 ;;
+ "EV6.8CX (21264D)")
+ UNAME_MACHINE=alphaev68 ;;
+ "EV6.9A (21264/EV69A)")
+ UNAME_MACHINE=alphaev69 ;;
+ "EV7 (21364)")
+ UNAME_MACHINE=alphaev7 ;;
+ "EV7.9 (21364A)")
+ UNAME_MACHINE=alphaev79 ;;
+ esac
+ # A Pn.n version is a patched version.
+ # A Vn.n version is a released version.
+ # A Tn.n version is a released field test version.
+ # A Xn.n version is an unreleased experimental baselevel.
+ # 1.2 uses "1.2" for uname -r.
+ echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+ # Reset EXIT trap before exiting to avoid spurious non-zero exit code.
+ exitcode=$?
+ trap '' 0
+ exit $exitcode ;;
+ Alpha\ *:Windows_NT*:*)
+ # How do we know it's Interix rather than the generic POSIX subsystem?
+ # Should we change UNAME_MACHINE based on the output of uname instead
+ # of the specific Alpha model?
+ echo alpha-pc-interix
+ exit ;;
+ 21064:Windows_NT:50:3)
+ echo alpha-dec-winnt3.5
+ exit ;;
+ Amiga*:UNIX_System_V:4.0:*)
+ echo m68k-unknown-sysv4
+ exit ;;
+ *:[Aa]miga[Oo][Ss]:*:*)
+ echo ${UNAME_MACHINE}-unknown-amigaos
+ exit ;;
+ *:[Mm]orph[Oo][Ss]:*:*)
+ echo ${UNAME_MACHINE}-unknown-morphos
+ exit ;;
+ *:OS/390:*:*)
+ echo i370-ibm-openedition
+ exit ;;
+ *:z/VM:*:*)
+ echo s390-ibm-zvmoe
+ exit ;;
+ *:OS400:*:*)
+ echo powerpc-ibm-os400
+ exit ;;
+ arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+ echo arm-acorn-riscix${UNAME_RELEASE}
+ exit ;;
+ arm*:riscos:*:*|arm*:RISCOS:*:*)
+ echo arm-unknown-riscos
+ exit ;;
+ SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+ echo hppa1.1-hitachi-hiuxmpp
+ exit ;;
+ Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+ # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+ if test "`(/bin/universe) 2>/dev/null`" = att ; then
+ echo pyramid-pyramid-sysv3
+ else
+ echo pyramid-pyramid-bsd
+ fi
+ exit ;;
+ NILE*:*:*:dcosx)
+ echo pyramid-pyramid-svr4
+ exit ;;
+ DRS?6000:unix:4.0:6*)
+ echo sparc-icl-nx6
+ exit ;;
+ DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+ case `/usr/bin/uname -p` in
+ sparc) echo sparc-icl-nx7; exit ;;
+ esac ;;
+ s390x:SunOS:*:*)
+ echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4H:SunOS:5.*:*)
+ echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+ echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
+ echo i386-pc-auroraux${UNAME_RELEASE}
+ exit ;;
+ i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+ eval $set_cc_for_build
+ SUN_ARCH=i386
+ # If there is a compiler, see if it is configured for 64-bit objects.
+ # Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
+ # This test works for both compilers.
+ if [ "$CC_FOR_BUILD" != no_compiler_found ]; then
+ if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+ grep IS_64BIT_ARCH >/dev/null
+ then
+ SUN_ARCH=x86_64
+ fi
+ fi
+ echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4*:SunOS:6*:*)
+ # According to config.sub, this is the proper way to canonicalize
+ # SunOS6. Hard to guess exactly what SunOS6 will be like, but
+ # it's likely to be more like Solaris than SunOS4.
+ echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4*:SunOS:*:*)
+ case "`/usr/bin/arch -k`" in
+ Series*|S4*)
+ UNAME_RELEASE=`uname -v`
+ ;;
+ esac
+ # Japanese Language versions have a version number like `4.1.3-JL'.
+ echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+ exit ;;
+ sun3*:SunOS:*:*)
+ echo m68k-sun-sunos${UNAME_RELEASE}
+ exit ;;
+ sun*:*:4.2BSD:*)
+ UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+ test "x${UNAME_RELEASE}" = x && UNAME_RELEASE=3
+ case "`/bin/arch`" in
+ sun3)
+ echo m68k-sun-sunos${UNAME_RELEASE}
+ ;;
+ sun4)
+ echo sparc-sun-sunos${UNAME_RELEASE}
+ ;;
+ esac
+ exit ;;
+ aushp:SunOS:*:*)
+ echo sparc-auspex-sunos${UNAME_RELEASE}
+ exit ;;
+ # The situation for MiNT is a little confusing. The machine name
+ # can be virtually everything (everything which is not
+ # "atarist" or "atariste" at least should have a processor
+ # > m68000). The system name ranges from "MiNT" over "FreeMiNT"
+ # to the lowercase version "mint" (or "freemint"). Finally
+ # the system name "TOS" denotes a system which is actually not
+ # MiNT. But MiNT is downward compatible to TOS, so this should
+ # be no problem.
+ atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit ;;
+ atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit ;;
+ *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit ;;
+ milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+ echo m68k-milan-mint${UNAME_RELEASE}
+ exit ;;
+ hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+ echo m68k-hades-mint${UNAME_RELEASE}
+ exit ;;
+ *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+ echo m68k-unknown-mint${UNAME_RELEASE}
+ exit ;;
+ m68k:machten:*:*)
+ echo m68k-apple-machten${UNAME_RELEASE}
+ exit ;;
+ powerpc:machten:*:*)
+ echo powerpc-apple-machten${UNAME_RELEASE}
+ exit ;;
+ RISC*:Mach:*:*)
+ echo mips-dec-mach_bsd4.3
+ exit ;;
+ RISC*:ULTRIX:*:*)
+ echo mips-dec-ultrix${UNAME_RELEASE}
+ exit ;;
+ VAX*:ULTRIX*:*:*)
+ echo vax-dec-ultrix${UNAME_RELEASE}
+ exit ;;
+ 2020:CLIX:*:* | 2430:CLIX:*:*)
+ echo clipper-intergraph-clix${UNAME_RELEASE}
+ exit ;;
+ mips:*:*:UMIPS | mips:*:*:RISCos)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h> /* for printf() prototype */
+ int main (int argc, char *argv[]) {
+#else
+ int main (argc, argv) int argc; char *argv[]; {
+#endif
+ #if defined (host_mips) && defined (MIPSEB)
+ #if defined (SYSTYPE_SYSV)
+ printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_SVR4)
+ printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+ printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+ #endif
+ #endif
+ exit (-1);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c &&
+ dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+ SYSTEM_NAME=`$dummy $dummyarg` &&
+ { echo "$SYSTEM_NAME"; exit; }
+ echo mips-mips-riscos${UNAME_RELEASE}
+ exit ;;
+ Motorola:PowerMAX_OS:*:*)
+ echo powerpc-motorola-powermax
+ exit ;;
+ Motorola:*:4.3:PL8-*)
+ echo powerpc-harris-powermax
+ exit ;;
+ Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+ echo powerpc-harris-powermax
+ exit ;;
+ Night_Hawk:Power_UNIX:*:*)
+ echo powerpc-harris-powerunix
+ exit ;;
+ m88k:CX/UX:7*:*)
+ echo m88k-harris-cxux7
+ exit ;;
+ m88k:*:4*:R4*)
+ echo m88k-motorola-sysv4
+ exit ;;
+ m88k:*:3*:R3*)
+ echo m88k-motorola-sysv3
+ exit ;;
+ AViiON:dgux:*:*)
+ # DG/UX returns AViiON for all architectures
+ UNAME_PROCESSOR=`/usr/bin/uname -p`
+ if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+ then
+ if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+ [ ${TARGET_BINARY_INTERFACE}x = x ]
+ then
+ echo m88k-dg-dgux${UNAME_RELEASE}
+ else
+ echo m88k-dg-dguxbcs${UNAME_RELEASE}
+ fi
+ else
+ echo i586-dg-dgux${UNAME_RELEASE}
+ fi
+ exit ;;
+ M88*:DolphinOS:*:*) # DolphinOS (SVR3)
+ echo m88k-dolphin-sysv3
+ exit ;;
+ M88*:*:R3*:*)
+ # Delta 88k system running SVR3
+ echo m88k-motorola-sysv3
+ exit ;;
+ XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+ echo m88k-tektronix-sysv3
+ exit ;;
+ Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+ echo m68k-tektronix-bsd
+ exit ;;
+ *:IRIX*:*:*)
+ echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+ exit ;;
+ ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+ echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id
+ exit ;; # Note that: echo "'`uname -s`'" gives 'AIX '
+ i*86:AIX:*:*)
+ echo i386-ibm-aix
+ exit ;;
+ ia64:AIX:*:*)
+ if [ -x /usr/bin/oslevel ] ; then
+ IBM_REV=`/usr/bin/oslevel`
+ else
+ IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+ fi
+ echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+ exit ;;
+ *:AIX:2:3)
+ if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <sys/systemcfg.h>
+
+ main()
+ {
+ if (!__power_pc())
+ exit(1);
+ puts("powerpc-ibm-aix3.2.5");
+ exit(0);
+ }
+EOF
+ if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy`
+ then
+ echo "$SYSTEM_NAME"
+ else
+ echo rs6000-ibm-aix3.2.5
+ fi
+ elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+ echo rs6000-ibm-aix3.2.4
+ else
+ echo rs6000-ibm-aix3.2
+ fi
+ exit ;;
+ *:AIX:*:[4567])
+ IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+ if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+ IBM_ARCH=rs6000
+ else
+ IBM_ARCH=powerpc
+ fi
+ if [ -x /usr/bin/lslpp ] ; then
+ IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc |
+ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/`
+ else
+ IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+ fi
+ echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+ exit ;;
+ *:AIX:*:*)
+ echo rs6000-ibm-aix
+ exit ;;
+ ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+ echo romp-ibm-bsd4.4
+ exit ;;
+ ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and
+ echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to
+ exit ;; # report: romp-ibm BSD 4.3
+ *:BOSX:*:*)
+ echo rs6000-bull-bosx
+ exit ;;
+ DPX/2?00:B.O.S.:*:*)
+ echo m68k-bull-sysv3
+ exit ;;
+ 9000/[34]??:4.3bsd:1.*:*)
+ echo m68k-hp-bsd
+ exit ;;
+ hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+ echo m68k-hp-bsd4.4
+ exit ;;
+ 9000/[34678]??:HP-UX:*:*)
+ HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+ case "${UNAME_MACHINE}" in
+ 9000/31? ) HP_ARCH=m68000 ;;
+ 9000/[34]?? ) HP_ARCH=m68k ;;
+ 9000/[678][0-9][0-9])
+ if [ -x /usr/bin/getconf ]; then
+ sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+ sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+ case "${sc_cpu_version}" in
+ 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
+ 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
+ 532) # CPU_PA_RISC2_0
+ case "${sc_kernel_bits}" in
+ 32) HP_ARCH=hppa2.0n ;;
+ 64) HP_ARCH=hppa2.0w ;;
+ '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20
+ esac ;;
+ esac
+ fi
+ if [ "${HP_ARCH}" = "" ]; then
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+
+ #define _HPUX_SOURCE
+ #include <stdlib.h>
+ #include <unistd.h>
+
+ int main ()
+ {
+ #if defined(_SC_KERNEL_BITS)
+ long bits = sysconf(_SC_KERNEL_BITS);
+ #endif
+ long cpu = sysconf (_SC_CPU_VERSION);
+
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+ case CPU_PA_RISC2_0:
+ #if defined(_SC_KERNEL_BITS)
+ switch (bits)
+ {
+ case 64: puts ("hppa2.0w"); break;
+ case 32: puts ("hppa2.0n"); break;
+ default: puts ("hppa2.0"); break;
+ } break;
+ #else /* !defined(_SC_KERNEL_BITS) */
+ puts ("hppa2.0"); break;
+ #endif
+ default: puts ("hppa1.0"); break;
+ }
+ exit (0);
+ }
+EOF
+ (CCOPTS="" $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+ test -z "$HP_ARCH" && HP_ARCH=hppa
+ fi ;;
+ esac
+ if [ ${HP_ARCH} = hppa2.0w ]
+ then
+ eval $set_cc_for_build
+
+ # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+ # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler
+ # generating 64-bit code. GNU and HP use different nomenclature:
+ #
+ # $ CC_FOR_BUILD=cc ./config.guess
+ # => hppa2.0w-hp-hpux11.23
+ # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+ # => hppa64-hp-hpux11.23
+
+ if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
+ grep -q __LP64__
+ then
+ HP_ARCH=hppa2.0w
+ else
+ HP_ARCH=hppa64
+ fi
+ fi
+ echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+ exit ;;
+ ia64:HP-UX:*:*)
+ HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+ echo ia64-hp-hpux${HPUX_REV}
+ exit ;;
+ 3050*:HI-UX:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <unistd.h>
+ int
+ main ()
+ {
+ long cpu = sysconf (_SC_CPU_VERSION);
+ /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+ true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct
+ results, however. */
+ if (CPU_IS_PA_RISC (cpu))
+ {
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+ default: puts ("hppa-hitachi-hiuxwe2"); break;
+ }
+ }
+ else if (CPU_IS_HP_MC68K (cpu))
+ puts ("m68k-hitachi-hiuxwe2");
+ else puts ("unknown-hitachi-hiuxwe2");
+ exit (0);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` &&
+ { echo "$SYSTEM_NAME"; exit; }
+ echo unknown-hitachi-hiuxwe2
+ exit ;;
+ 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+ echo hppa1.1-hp-bsd
+ exit ;;
+ 9000/8??:4.3bsd:*:*)
+ echo hppa1.0-hp-bsd
+ exit ;;
+ *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+ echo hppa1.0-hp-mpeix
+ exit ;;
+ hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+ echo hppa1.1-hp-osf
+ exit ;;
+ hp8??:OSF1:*:*)
+ echo hppa1.0-hp-osf
+ exit ;;
+ i*86:OSF1:*:*)
+ if [ -x /usr/sbin/sysversion ] ; then
+ echo ${UNAME_MACHINE}-unknown-osf1mk
+ else
+ echo ${UNAME_MACHINE}-unknown-osf1
+ fi
+ exit ;;
+ parisc*:Lites*:*:*)
+ echo hppa1.1-hp-lites
+ exit ;;
+ C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+ echo c1-convex-bsd
+ exit ;;
+ C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+ if getsysinfo -f scalar_acc
+ then echo c32-convex-bsd
+ else echo c2-convex-bsd
+ fi
+ exit ;;
+ C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+ echo c34-convex-bsd
+ exit ;;
+ C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+ echo c38-convex-bsd
+ exit ;;
+ C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+ echo c4-convex-bsd
+ exit ;;
+ CRAY*Y-MP:*:*:*)
+ echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*[A-Z]90:*:*:*)
+ echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+ -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*TS:*:*:*)
+ echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*T3E:*:*:*)
+ echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*SV1:*:*:*)
+ echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ *:UNICOS/mp:*:*)
+ echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+ FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+ FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+ FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+ echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+ exit ;;
+ 5000:UNIX_System_V:4.*:*)
+ FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+ FUJITSU_REL=`echo ${UNAME_RELEASE} | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'`
+ echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+ exit ;;
+ i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+ echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+ exit ;;
+ sparc*:BSD/OS:*:*)
+ echo sparc-unknown-bsdi${UNAME_RELEASE}
+ exit ;;
+ *:BSD/OS:*:*)
+ echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+ exit ;;
+ *:FreeBSD:*:*)
+ UNAME_PROCESSOR=`/usr/bin/uname -p`
+ case ${UNAME_PROCESSOR} in
+ amd64)
+ UNAME_PROCESSOR=x86_64 ;;
+ i386)
+ UNAME_PROCESSOR=i586 ;;
+ esac
+ echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+ exit ;;
+ i*:CYGWIN*:*)
+ echo ${UNAME_MACHINE}-pc-cygwin
+ exit ;;
+ *:MINGW64*:*)
+ echo ${UNAME_MACHINE}-pc-mingw64
+ exit ;;
+ *:MINGW*:*)
+ echo ${UNAME_MACHINE}-pc-mingw32
+ exit ;;
+ *:MSYS*:*)
+ echo ${UNAME_MACHINE}-pc-msys
+ exit ;;
+ i*:windows32*:*)
+ # uname -m includes "-pc" on this system.
+ echo ${UNAME_MACHINE}-mingw32
+ exit ;;
+ i*:PW*:*)
+ echo ${UNAME_MACHINE}-pc-pw32
+ exit ;;
+ *:Interix*:*)
+ case ${UNAME_MACHINE} in
+ x86)
+ echo i586-pc-interix${UNAME_RELEASE}
+ exit ;;
+ authenticamd | genuineintel | EM64T)
+ echo x86_64-unknown-interix${UNAME_RELEASE}
+ exit ;;
+ IA64)
+ echo ia64-unknown-interix${UNAME_RELEASE}
+ exit ;;
+ esac ;;
+ [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+ echo i${UNAME_MACHINE}-pc-mks
+ exit ;;
+ 8664:Windows_NT:*)
+ echo x86_64-pc-mks
+ exit ;;
+ i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+ # How do we know it's Interix rather than the generic POSIX subsystem?
+ # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+ # UNAME_MACHINE based on the output of uname instead of i386?
+ echo i586-pc-interix
+ exit ;;
+ i*:UWIN*:*)
+ echo ${UNAME_MACHINE}-pc-uwin
+ exit ;;
+ amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+ echo x86_64-unknown-cygwin
+ exit ;;
+ p*:CYGWIN*:*)
+ echo powerpcle-unknown-cygwin
+ exit ;;
+ prep*:SunOS:5.*:*)
+ echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ *:GNU:*:*)
+ # the GNU system
+ echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+ exit ;;
+ *:GNU/*:*:*)
+ # other systems with GNU libc and userland
+ echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC}
+ exit ;;
+ i*86:Minix:*:*)
+ echo ${UNAME_MACHINE}-pc-minix
+ exit ;;
+ aarch64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ aarch64_be:Linux:*:*)
+ UNAME_MACHINE=aarch64_be
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ alpha:Linux:*:*)
+ case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+ EV5) UNAME_MACHINE=alphaev5 ;;
+ EV56) UNAME_MACHINE=alphaev56 ;;
+ PCA56) UNAME_MACHINE=alphapca56 ;;
+ PCA57) UNAME_MACHINE=alphapca56 ;;
+ EV6) UNAME_MACHINE=alphaev6 ;;
+ EV67) UNAME_MACHINE=alphaev67 ;;
+ EV68*) UNAME_MACHINE=alphaev68 ;;
+ esac
+ objdump --private-headers /bin/sh | grep -q ld.so.1
+ if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ arc:Linux:*:* | arceb:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ arm*:Linux:*:*)
+ eval $set_cc_for_build
+ if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ARM_EABI__
+ then
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ else
+ if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ARM_PCS_VFP
+ then
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi
+ else
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf
+ fi
+ fi
+ exit ;;
+ avr32*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ cris:Linux:*:*)
+ echo ${UNAME_MACHINE}-axis-linux-${LIBC}
+ exit ;;
+ crisv32:Linux:*:*)
+ echo ${UNAME_MACHINE}-axis-linux-${LIBC}
+ exit ;;
+ e2k:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ frv:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ hexagon:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ i*86:Linux:*:*)
+ echo ${UNAME_MACHINE}-pc-linux-${LIBC}
+ exit ;;
+ ia64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ k1om:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ m32r*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ m68*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ mips:Linux:*:* | mips64:Linux:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #undef CPU
+ #undef ${UNAME_MACHINE}
+ #undef ${UNAME_MACHINE}el
+ #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+ CPU=${UNAME_MACHINE}el
+ #else
+ #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+ CPU=${UNAME_MACHINE}
+ #else
+ CPU=
+ #endif
+ #endif
+EOF
+ eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'`
+ test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; }
+ ;;
+ mips64el:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ openrisc*:Linux:*:*)
+ echo or1k-unknown-linux-${LIBC}
+ exit ;;
+ or32:Linux:*:* | or1k*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ padre:Linux:*:*)
+ echo sparc-unknown-linux-${LIBC}
+ exit ;;
+ parisc64:Linux:*:* | hppa64:Linux:*:*)
+ echo hppa64-unknown-linux-${LIBC}
+ exit ;;
+ parisc:Linux:*:* | hppa:Linux:*:*)
+ # Look for CPU level
+ case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+ PA7*) echo hppa1.1-unknown-linux-${LIBC} ;;
+ PA8*) echo hppa2.0-unknown-linux-${LIBC} ;;
+ *) echo hppa-unknown-linux-${LIBC} ;;
+ esac
+ exit ;;
+ ppc64:Linux:*:*)
+ echo powerpc64-unknown-linux-${LIBC}
+ exit ;;
+ ppc:Linux:*:*)
+ echo powerpc-unknown-linux-${LIBC}
+ exit ;;
+ ppc64le:Linux:*:*)
+ echo powerpc64le-unknown-linux-${LIBC}
+ exit ;;
+ ppcle:Linux:*:*)
+ echo powerpcle-unknown-linux-${LIBC}
+ exit ;;
+ riscv32:Linux:*:* | riscv64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ s390:Linux:*:* | s390x:Linux:*:*)
+ echo ${UNAME_MACHINE}-ibm-linux-${LIBC}
+ exit ;;
+ sh64*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ sh*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ sparc:Linux:*:* | sparc64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ tile*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ vax:Linux:*:*)
+ echo ${UNAME_MACHINE}-dec-linux-${LIBC}
+ exit ;;
+ x86_64:Linux:*:*)
+ echo ${UNAME_MACHINE}-pc-linux-${LIBC}
+ exit ;;
+ xtensa*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
+ i*86:DYNIX/ptx:4*:*)
+ # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+ # earlier versions are messed up and put the nodename in both
+ # sysname and nodename.
+ echo i386-sequent-sysv4
+ exit ;;
+ i*86:UNIX_SV:4.2MP:2.*)
+ # Unixware is an offshoot of SVR4, but it has its own version
+ # number series starting with 2...
+ # I am not positive that other SVR4 systems won't match this,
+ # I just have to hope. -- rms.
+ # Use sysv4.2uw... so that sysv4* matches it.
+ echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+ exit ;;
+ i*86:OS/2:*:*)
+ # If we were able to find `uname', then EMX Unix compatibility
+ # is probably installed.
+ echo ${UNAME_MACHINE}-pc-os2-emx
+ exit ;;
+ i*86:XTS-300:*:STOP)
+ echo ${UNAME_MACHINE}-unknown-stop
+ exit ;;
+ i*86:atheos:*:*)
+ echo ${UNAME_MACHINE}-unknown-atheos
+ exit ;;
+ i*86:syllable:*:*)
+ echo ${UNAME_MACHINE}-pc-syllable
+ exit ;;
+ i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
+ echo i386-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ i*86:*DOS:*:*)
+ echo ${UNAME_MACHINE}-pc-msdosdjgpp
+ exit ;;
+ i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+ UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+ if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+ echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+ else
+ echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+ fi
+ exit ;;
+ i*86:*:5:[678]*)
+ # UnixWare 7.x, OpenUNIX and OpenServer 6.
+ case `/bin/uname -X | grep "^Machine"` in
+ *486*) UNAME_MACHINE=i486 ;;
+ *Pentium) UNAME_MACHINE=i586 ;;
+ *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+ esac
+ echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+ exit ;;
+ i*86:*:3.2:*)
+ if test -f /usr/options/cb.name; then
+ UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+ echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+ elif /bin/uname -X 2>/dev/null >/dev/null ; then
+ UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+ (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+ (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+ && UNAME_MACHINE=i586
+ (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+ && UNAME_MACHINE=i686
+ (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+ && UNAME_MACHINE=i686
+ echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+ else
+ echo ${UNAME_MACHINE}-pc-sysv32
+ fi
+ exit ;;
+ pc:*:*:*)
+ # Left here for compatibility:
+ # uname -m prints for DJGPP always 'pc', but it prints nothing about
+ # the processor, so we play safe by assuming i586.
+ # Note: whatever this is, it MUST be the same as what config.sub
+ # prints for the "djgpp" host, or else GDB configure will decide that
+ # this is a cross-build.
+ echo i586-pc-msdosdjgpp
+ exit ;;
+ Intel:Mach:3*:*)
+ echo i386-pc-mach3
+ exit ;;
+ paragon:*:*:*)
+ echo i860-intel-osf1
+ exit ;;
+ i860:*:4.*:*) # i860-SVR4
+ if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+ echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+ else # Add other i860-SVR4 vendors below as they are discovered.
+ echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4
+ fi
+ exit ;;
+ mini*:CTIX:SYS*5:*)
+ # "miniframe"
+ echo m68010-convergent-sysv
+ exit ;;
+ mc68k:UNIX:SYSTEM5:3.51m)
+ echo m68k-convergent-sysv
+ exit ;;
+ M680?0:D-NIX:5.3:*)
+ echo m68k-diab-dnix
+ exit ;;
+ M68*:*:R3V[5678]*:*)
+ test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+ 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+ OS_REL=''
+ test -r /etc/.relid \
+ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+ 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4; exit; } ;;
+ NCR*:*:4.2:* | MPRAS*:*:4.2:*)
+ OS_REL='.3'
+ test -r /etc/.relid \
+ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+ && { echo i586-ncr-sysv4.3${OS_REL}; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
+ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+ m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+ echo m68k-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ mc68030:UNIX_System_V:4.*:*)
+ echo m68k-atari-sysv4
+ exit ;;
+ TSUNAMI:LynxOS:2.*:*)
+ echo sparc-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ rs6000:LynxOS:2.*:*)
+ echo rs6000-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
+ echo powerpc-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ SM[BE]S:UNIX_SV:*:*)
+ echo mips-dde-sysv${UNAME_RELEASE}
+ exit ;;
+ RM*:ReliantUNIX-*:*:*)
+ echo mips-sni-sysv4
+ exit ;;
+ RM*:SINIX-*:*:*)
+ echo mips-sni-sysv4
+ exit ;;
+ *:SINIX-*:*:*)
+ if uname -p 2>/dev/null >/dev/null ; then
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ echo ${UNAME_MACHINE}-sni-sysv4
+ else
+ echo ns32k-sni-sysv
+ fi
+ exit ;;
+ PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+ # says <Richard.M.Bartel@ccMail.Census.GOV>
+ echo i586-unisys-sysv4
+ exit ;;
+ *:UNIX_System_V:4*:FTX*)
+ # From Gerald Hewes <hewes@openmarket.com>.
+ # How about differentiating between stratus architectures? -djm
+ echo hppa1.1-stratus-sysv4
+ exit ;;
+ *:*:*:FTX*)
+ # From seanf@swdc.stratus.com.
+ echo i860-stratus-sysv4
+ exit ;;
+ i*86:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ echo ${UNAME_MACHINE}-stratus-vos
+ exit ;;
+ *:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ echo hppa1.1-stratus-vos
+ exit ;;
+ mc68*:A/UX:*:*)
+ echo m68k-apple-aux${UNAME_RELEASE}
+ exit ;;
+ news*:NEWS-OS:6*:*)
+ echo mips-sony-newsos6
+ exit ;;
+ R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+ if [ -d /usr/nec ]; then
+ echo mips-nec-sysv${UNAME_RELEASE}
+ else
+ echo mips-unknown-sysv${UNAME_RELEASE}
+ fi
+ exit ;;
+ BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only.
+ echo powerpc-be-beos
+ exit ;;
+ BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only.
+ echo powerpc-apple-beos
+ exit ;;
+ BePC:BeOS:*:*) # BeOS running on Intel PC compatible.
+ echo i586-pc-beos
+ exit ;;
+ BePC:Haiku:*:*) # Haiku running on Intel PC compatible.
+ echo i586-pc-haiku
+ exit ;;
+ x86_64:Haiku:*:*)
+ echo x86_64-unknown-haiku
+ exit ;;
+ SX-4:SUPER-UX:*:*)
+ echo sx4-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-5:SUPER-UX:*:*)
+ echo sx5-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-6:SUPER-UX:*:*)
+ echo sx6-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-7:SUPER-UX:*:*)
+ echo sx7-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-8:SUPER-UX:*:*)
+ echo sx8-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-8R:SUPER-UX:*:*)
+ echo sx8r-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-ACE:SUPER-UX:*:*)
+ echo sxace-nec-superux${UNAME_RELEASE}
+ exit ;;
+ Power*:Rhapsody:*:*)
+ echo powerpc-apple-rhapsody${UNAME_RELEASE}
+ exit ;;
+ *:Rhapsody:*:*)
+ echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+ exit ;;
+ *:Darwin:*:*)
+ UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+ eval $set_cc_for_build
+ if test "$UNAME_PROCESSOR" = unknown ; then
+ UNAME_PROCESSOR=powerpc
+ fi
+ if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then
+ if [ "$CC_FOR_BUILD" != no_compiler_found ]; then
+ if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+ grep IS_64BIT_ARCH >/dev/null
+ then
+ case $UNAME_PROCESSOR in
+ i386) UNAME_PROCESSOR=x86_64 ;;
+ powerpc) UNAME_PROCESSOR=powerpc64 ;;
+ esac
+ fi
+ # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
+ if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+ grep IS_PPC >/dev/null
+ then
+ UNAME_PROCESSOR=powerpc
+ fi
+ fi
+ elif test "$UNAME_PROCESSOR" = i386 ; then
+ # Avoid executing cc on OS X 10.9, as it ships with a stub
+ # that puts up a graphical alert prompting to install
+ # developer tools. Any system running Mac OS X 10.7 or
+ # later (Darwin 11 and later) is required to have a 64-bit
+ # processor. This is not true of the ARM version of Darwin
+ # that Apple uses in portable devices.
+ UNAME_PROCESSOR=x86_64
+ fi
+ echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+ exit ;;
+ *:procnto*:*:* | *:QNX:[0123456789]*:*)
+ UNAME_PROCESSOR=`uname -p`
+ if test "$UNAME_PROCESSOR" = x86; then
+ UNAME_PROCESSOR=i386
+ UNAME_MACHINE=pc
+ fi
+ echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+ exit ;;
+ *:QNX:*:4*)
+ echo i386-pc-qnx
+ exit ;;
+ NEO-*:NONSTOP_KERNEL:*:*)
+ echo neo-tandem-nsk${UNAME_RELEASE}
+ exit ;;
+ NSE-*:NONSTOP_KERNEL:*:*)
+ echo nse-tandem-nsk${UNAME_RELEASE}
+ exit ;;
+ NSR-*:NONSTOP_KERNEL:*:*)
+ echo nsr-tandem-nsk${UNAME_RELEASE}
+ exit ;;
+ NSX-*:NONSTOP_KERNEL:*:*)
+ echo nsx-tandem-nsk${UNAME_RELEASE}
+ exit ;;
+ *:NonStop-UX:*:*)
+ echo mips-compaq-nonstopux
+ exit ;;
+ BS2000:POSIX*:*:*)
+ echo bs2000-siemens-sysv
+ exit ;;
+ DS/*:UNIX_System_V:*:*)
+ echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+ exit ;;
+ *:Plan9:*:*)
+ # "uname -m" is not consistent, so use $cputype instead. 386
+ # is converted to i386 for consistency with other x86
+ # operating systems.
+ if test "$cputype" = 386; then
+ UNAME_MACHINE=i386
+ else
+ UNAME_MACHINE="$cputype"
+ fi
+ echo ${UNAME_MACHINE}-unknown-plan9
+ exit ;;
+ *:TOPS-10:*:*)
+ echo pdp10-unknown-tops10
+ exit ;;
+ *:TENEX:*:*)
+ echo pdp10-unknown-tenex
+ exit ;;
+ KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+ echo pdp10-dec-tops20
+ exit ;;
+ XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+ echo pdp10-xkl-tops20
+ exit ;;
+ *:TOPS-20:*:*)
+ echo pdp10-unknown-tops20
+ exit ;;
+ *:ITS:*:*)
+ echo pdp10-unknown-its
+ exit ;;
+ SEI:*:*:SEIUX)
+ echo mips-sei-seiux${UNAME_RELEASE}
+ exit ;;
+ *:DragonFly:*:*)
+ echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+ exit ;;
+ *:*VMS:*:*)
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ case "${UNAME_MACHINE}" in
+ A*) echo alpha-dec-vms ; exit ;;
+ I*) echo ia64-dec-vms ; exit ;;
+ V*) echo vax-dec-vms ; exit ;;
+ esac ;;
+ *:XENIX:*:SysV)
+ echo i386-pc-xenix
+ exit ;;
+ i*86:skyos:*:*)
+ echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE} | sed -e 's/ .*$//'`
+ exit ;;
+ i*86:rdos:*:*)
+ echo ${UNAME_MACHINE}-pc-rdos
+ exit ;;
+ i*86:AROS:*:*)
+ echo ${UNAME_MACHINE}-pc-aros
+ exit ;;
+ x86_64:VMkernel:*:*)
+ echo ${UNAME_MACHINE}-unknown-esx
+ exit ;;
+ amd64:Isilon\ OneFS:*:*)
+ echo x86_64-unknown-onefs
+ exit ;;
+esac
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script (version $timestamp), has failed to recognize the
+operating system you are using. If your script is old, overwrite
+config.guess and config.sub with the latest versions from:
+
+ http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess
+and
+ http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub
+
+If $0 has already been updated, send the following data and any
+information you think might be pertinent to config-patches@gnu.org to
+provide the necessary information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo = `(hostinfo) 2>/dev/null`
+/bin/universe = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/etc/config.sub b/etc/config.sub
new file mode 100755
index 0000000..40ea5df
--- /dev/null
+++ b/etc/config.sub
@@ -0,0 +1,1836 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+# Copyright 1992-2017 Free Software Foundation, Inc.
+
+timestamp='2017-04-02'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program. This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+
+
+# Please send patches to <config-patches@gnu.org>.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support. The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright 1992-2017 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit ;;
+ --version | -v )
+ echo "$version" ; exit ;;
+ --help | --h* | -h )
+ echo "$usage"; exit ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help"
+ exit 1 ;;
+
+ *local*)
+ # First pass through any local machine types.
+ echo $1
+ exit ;;
+
+ * )
+ break ;;
+ esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+ exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+ exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+ nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \
+ linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \
+ knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \
+ kopensolaris*-gnu* | cloudabi*-eabi* | \
+ storm-chaos* | os2-emx* | rtmk-nova*)
+ os=-$maybe_os
+ basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+ ;;
+ android-linux)
+ os=-linux-android
+ basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown
+ ;;
+ *)
+ basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+ if [ $basic_machine != $1 ]
+ then os=`echo $1 | sed 's/.*-/-/'`
+ else os=; fi
+ ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work. We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+ -sun*os*)
+ # Prevent following clause from handling this invalid input.
+ ;;
+ -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+ -apple | -axis | -knuth | -cray | -microblaze*)
+ os=
+ basic_machine=$1
+ ;;
+ -bluegene*)
+ os=-cnk
+ ;;
+ -sim | -cisco | -oki | -wec | -winbond)
+ os=
+ basic_machine=$1
+ ;;
+ -scout)
+ ;;
+ -wrs)
+ os=-vxworks
+ basic_machine=$1
+ ;;
+ -chorusos*)
+ os=-chorusos
+ basic_machine=$1
+ ;;
+ -chorusrdb)
+ os=-chorusrdb
+ basic_machine=$1
+ ;;
+ -hiux*)
+ os=-hiuxwe2
+ ;;
+ -sco6)
+ os=-sco5v6
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco5)
+ os=-sco3.2v5
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco4)
+ os=-sco3.2v4
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco3.2.[4-9]*)
+ os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco3.2v[4-9]*)
+ # Don't forget version if it is 3.2v4 or newer.
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco5v6*)
+ # Don't forget version if it is 3.2v4 or newer.
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco*)
+ os=-sco3.2v2
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -udk*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -isc)
+ os=-isc2.2
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -clix*)
+ basic_machine=clipper-intergraph
+ ;;
+ -isc*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -lynx*178)
+ os=-lynxos178
+ ;;
+ -lynx*5)
+ os=-lynxos5
+ ;;
+ -lynx*)
+ os=-lynxos
+ ;;
+ -ptx*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+ ;;
+ -windowsnt*)
+ os=`echo $os | sed -e 's/windowsnt/winnt/'`
+ ;;
+ -psos*)
+ os=-psos
+ ;;
+ -mint | -mint[0-9]*)
+ basic_machine=m68k-atari
+ os=-mint
+ ;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+ # Recognize the basic CPU types without company name.
+ # Some are omitted here because they have special meanings below.
+ 1750a | 580 \
+ | a29k \
+ | aarch64 | aarch64_be \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+ | am33_2.0 \
+ | arc | arceb \
+ | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \
+ | avr | avr32 \
+ | ba \
+ | be32 | be64 \
+ | bfin \
+ | c4x | c8051 | clipper \
+ | d10v | d30v | dlx | dsp16xx \
+ | e2k | epiphany \
+ | fido | fr30 | frv | ft32 \
+ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | hexagon \
+ | i370 | i860 | i960 | ia16 | ia64 \
+ | ip2k | iq2000 \
+ | k1om \
+ | le32 | le64 \
+ | lm32 \
+ | m32c | m32r | m32rle | m68000 | m68k | m88k \
+ | maxq | mb | microblaze | microblazeel | mcore | mep | metag \
+ | mips | mipsbe | mipseb | mipsel | mipsle \
+ | mips16 \
+ | mips64 | mips64el \
+ | mips64octeon | mips64octeonel \
+ | mips64orion | mips64orionel \
+ | mips64r5900 | mips64r5900el \
+ | mips64vr | mips64vrel \
+ | mips64vr4100 | mips64vr4100el \
+ | mips64vr4300 | mips64vr4300el \
+ | mips64vr5000 | mips64vr5000el \
+ | mips64vr5900 | mips64vr5900el \
+ | mipsisa32 | mipsisa32el \
+ | mipsisa32r2 | mipsisa32r2el \
+ | mipsisa32r6 | mipsisa32r6el \
+ | mipsisa64 | mipsisa64el \
+ | mipsisa64r2 | mipsisa64r2el \
+ | mipsisa64r6 | mipsisa64r6el \
+ | mipsisa64sb1 | mipsisa64sb1el \
+ | mipsisa64sr71k | mipsisa64sr71kel \
+ | mipsr5900 | mipsr5900el \
+ | mipstx39 | mipstx39el \
+ | mn10200 | mn10300 \
+ | moxie \
+ | mt \
+ | msp430 \
+ | nds32 | nds32le | nds32be \
+ | nios | nios2 | nios2eb | nios2el \
+ | ns16k | ns32k \
+ | open8 | or1k | or1knd | or32 \
+ | pdp10 | pdp11 | pj | pjl \
+ | powerpc | powerpc64 | powerpc64le | powerpcle \
+ | pru \
+ | pyramid \
+ | riscv32 | riscv64 \
+ | rl78 | rx \
+ | score \
+ | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
+ | sh64 | sh64le \
+ | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
+ | sparcv8 | sparcv9 | sparcv9b | sparcv9v \
+ | spu \
+ | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \
+ | ubicom32 \
+ | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \
+ | visium \
+ | wasm32 \
+ | we32k \
+ | x86 | xc16x | xstormy16 | xtensa \
+ | z8k | z80)
+ basic_machine=$basic_machine-unknown
+ ;;
+ c54x)
+ basic_machine=tic54x-unknown
+ ;;
+ c55x)
+ basic_machine=tic55x-unknown
+ ;;
+ c6x)
+ basic_machine=tic6x-unknown
+ ;;
+ leon|leon[3-9])
+ basic_machine=sparc-$basic_machine
+ ;;
+ m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip)
+ basic_machine=$basic_machine-unknown
+ os=-none
+ ;;
+ m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+ ;;
+ ms1)
+ basic_machine=mt-unknown
+ ;;
+
+ strongarm | thumb | xscale)
+ basic_machine=arm-unknown
+ ;;
+ xgate)
+ basic_machine=$basic_machine-unknown
+ os=-none
+ ;;
+ xscaleeb)
+ basic_machine=armeb-unknown
+ ;;
+
+ xscaleel)
+ basic_machine=armel-unknown
+ ;;
+
+ # We use `pc' rather than `unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+ basic_machine=$basic_machine-pc
+ ;;
+ # Object if more than one company name word.
+ *-*-*)
+ echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+ exit 1
+ ;;
+ # Recognize the basic CPU types with company name.
+ 580-* \
+ | a29k-* \
+ | aarch64-* | aarch64_be-* \
+ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \
+ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \
+ | avr-* | avr32-* \
+ | ba-* \
+ | be32-* | be64-* \
+ | bfin-* | bs2000-* \
+ | c[123]* | c30-* | [cjt]90-* | c4x-* \
+ | c8051-* | clipper-* | craynv-* | cydra-* \
+ | d10v-* | d30v-* | dlx-* \
+ | e2k-* | elxsi-* \
+ | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
+ | h8300-* | h8500-* \
+ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+ | hexagon-* \
+ | i*86-* | i860-* | i960-* | ia16-* | ia64-* \
+ | ip2k-* | iq2000-* \
+ | k1om-* \
+ | le32-* | le64-* \
+ | lm32-* \
+ | m32c-* | m32r-* | m32rle-* \
+ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+ | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \
+ | microblaze-* | microblazeel-* \
+ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+ | mips16-* \
+ | mips64-* | mips64el-* \
+ | mips64octeon-* | mips64octeonel-* \
+ | mips64orion-* | mips64orionel-* \
+ | mips64r5900-* | mips64r5900el-* \
+ | mips64vr-* | mips64vrel-* \
+ | mips64vr4100-* | mips64vr4100el-* \
+ | mips64vr4300-* | mips64vr4300el-* \
+ | mips64vr5000-* | mips64vr5000el-* \
+ | mips64vr5900-* | mips64vr5900el-* \
+ | mipsisa32-* | mipsisa32el-* \
+ | mipsisa32r2-* | mipsisa32r2el-* \
+ | mipsisa32r6-* | mipsisa32r6el-* \
+ | mipsisa64-* | mipsisa64el-* \
+ | mipsisa64r2-* | mipsisa64r2el-* \
+ | mipsisa64r6-* | mipsisa64r6el-* \
+ | mipsisa64sb1-* | mipsisa64sb1el-* \
+ | mipsisa64sr71k-* | mipsisa64sr71kel-* \
+ | mipsr5900-* | mipsr5900el-* \
+ | mipstx39-* | mipstx39el-* \
+ | mmix-* \
+ | mt-* \
+ | msp430-* \
+ | nds32-* | nds32le-* | nds32be-* \
+ | nios-* | nios2-* | nios2eb-* | nios2el-* \
+ | none-* | np1-* | ns16k-* | ns32k-* \
+ | open8-* \
+ | or1k*-* \
+ | orion-* \
+ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \
+ | pru-* \
+ | pyramid-* \
+ | riscv32-* | riscv64-* \
+ | rl78-* | romp-* | rs6000-* | rx-* \
+ | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
+ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+ | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
+ | sparclite-* \
+ | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \
+ | tahoe-* \
+ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+ | tile*-* \
+ | tron-* \
+ | ubicom32-* \
+ | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \
+ | vax-* \
+ | visium-* \
+ | wasm32-* \
+ | we32k-* \
+ | x86-* | x86_64-* | xc16x-* | xps100-* \
+ | xstormy16-* | xtensa*-* \
+ | ymp-* \
+ | z8k-* | z80-*)
+ ;;
+ # Recognize the basic CPU types without company name, with glob match.
+ xtensa*)
+ basic_machine=$basic_machine-unknown
+ ;;
+ # Recognize the various machine names and aliases which stand
+ # for a CPU type and a company and sometimes even an OS.
+ 386bsd)
+ basic_machine=i386-unknown
+ os=-bsd
+ ;;
+ 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+ basic_machine=m68000-att
+ ;;
+ 3b*)
+ basic_machine=we32k-att
+ ;;
+ a29khif)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ abacus)
+ basic_machine=abacus-unknown
+ ;;
+ adobe68k)
+ basic_machine=m68010-adobe
+ os=-scout
+ ;;
+ alliant | fx80)
+ basic_machine=fx80-alliant
+ ;;
+ altos | altos3068)
+ basic_machine=m68k-altos
+ ;;
+ am29k)
+ basic_machine=a29k-none
+ os=-bsd
+ ;;
+ amd64)
+ basic_machine=x86_64-pc
+ ;;
+ amd64-*)
+ basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ amdahl)
+ basic_machine=580-amdahl
+ os=-sysv
+ ;;
+ amiga | amiga-*)
+ basic_machine=m68k-unknown
+ ;;
+ amigaos | amigados)
+ basic_machine=m68k-unknown
+ os=-amigaos
+ ;;
+ amigaunix | amix)
+ basic_machine=m68k-unknown
+ os=-sysv4
+ ;;
+ apollo68)
+ basic_machine=m68k-apollo
+ os=-sysv
+ ;;
+ apollo68bsd)
+ basic_machine=m68k-apollo
+ os=-bsd
+ ;;
+ aros)
+ basic_machine=i386-pc
+ os=-aros
+ ;;
+ asmjs)
+ basic_machine=asmjs-unknown
+ ;;
+ aux)
+ basic_machine=m68k-apple
+ os=-aux
+ ;;
+ balance)
+ basic_machine=ns32k-sequent
+ os=-dynix
+ ;;
+ blackfin)
+ basic_machine=bfin-unknown
+ os=-linux
+ ;;
+ blackfin-*)
+ basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=-linux
+ ;;
+ bluegene*)
+ basic_machine=powerpc-ibm
+ os=-cnk
+ ;;
+ c54x-*)
+ basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ c55x-*)
+ basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ c6x-*)
+ basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ c90)
+ basic_machine=c90-cray
+ os=-unicos
+ ;;
+ cegcc)
+ basic_machine=arm-unknown
+ os=-cegcc
+ ;;
+ convex-c1)
+ basic_machine=c1-convex
+ os=-bsd
+ ;;
+ convex-c2)
+ basic_machine=c2-convex
+ os=-bsd
+ ;;
+ convex-c32)
+ basic_machine=c32-convex
+ os=-bsd
+ ;;
+ convex-c34)
+ basic_machine=c34-convex
+ os=-bsd
+ ;;
+ convex-c38)
+ basic_machine=c38-convex
+ os=-bsd
+ ;;
+ cray | j90)
+ basic_machine=j90-cray
+ os=-unicos
+ ;;
+ craynv)
+ basic_machine=craynv-cray
+ os=-unicosmp
+ ;;
+ cr16 | cr16-*)
+ basic_machine=cr16-unknown
+ os=-elf
+ ;;
+ crds | unos)
+ basic_machine=m68k-crds
+ ;;
+ crisv32 | crisv32-* | etraxfs*)
+ basic_machine=crisv32-axis
+ ;;
+ cris | cris-* | etrax*)
+ basic_machine=cris-axis
+ ;;
+ crx)
+ basic_machine=crx-unknown
+ os=-elf
+ ;;
+ da30 | da30-*)
+ basic_machine=m68k-da30
+ ;;
+ decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+ basic_machine=mips-dec
+ ;;
+ decsystem10* | dec10*)
+ basic_machine=pdp10-dec
+ os=-tops10
+ ;;
+ decsystem20* | dec20*)
+ basic_machine=pdp10-dec
+ os=-tops20
+ ;;
+ delta | 3300 | motorola-3300 | motorola-delta \
+ | 3300-motorola | delta-motorola)
+ basic_machine=m68k-motorola
+ ;;
+ delta88)
+ basic_machine=m88k-motorola
+ os=-sysv3
+ ;;
+ dicos)
+ basic_machine=i686-pc
+ os=-dicos
+ ;;
+ djgpp)
+ basic_machine=i586-pc
+ os=-msdosdjgpp
+ ;;
+ dpx20 | dpx20-*)
+ basic_machine=rs6000-bull
+ os=-bosx
+ ;;
+ dpx2* | dpx2*-bull)
+ basic_machine=m68k-bull
+ os=-sysv3
+ ;;
+ e500v[12])
+ basic_machine=powerpc-unknown
+ os=$os"spe"
+ ;;
+ e500v[12]-*)
+ basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=$os"spe"
+ ;;
+ ebmon29k)
+ basic_machine=a29k-amd
+ os=-ebmon
+ ;;
+ elxsi)
+ basic_machine=elxsi-elxsi
+ os=-bsd
+ ;;
+ encore | umax | mmax)
+ basic_machine=ns32k-encore
+ ;;
+ es1800 | OSE68k | ose68k | ose | OSE)
+ basic_machine=m68k-ericsson
+ os=-ose
+ ;;
+ fx2800)
+ basic_machine=i860-alliant
+ ;;
+ genix)
+ basic_machine=ns32k-ns
+ ;;
+ gmicro)
+ basic_machine=tron-gmicro
+ os=-sysv
+ ;;
+ go32)
+ basic_machine=i386-pc
+ os=-go32
+ ;;
+ h3050r* | hiux*)
+ basic_machine=hppa1.1-hitachi
+ os=-hiuxwe2
+ ;;
+ h8300hms)
+ basic_machine=h8300-hitachi
+ os=-hms
+ ;;
+ h8300xray)
+ basic_machine=h8300-hitachi
+ os=-xray
+ ;;
+ h8500hms)
+ basic_machine=h8500-hitachi
+ os=-hms
+ ;;
+ harris)
+ basic_machine=m88k-harris
+ os=-sysv3
+ ;;
+ hp300-*)
+ basic_machine=m68k-hp
+ ;;
+ hp300bsd)
+ basic_machine=m68k-hp
+ os=-bsd
+ ;;
+ hp300hpux)
+ basic_machine=m68k-hp
+ os=-hpux
+ ;;
+ hp3k9[0-9][0-9] | hp9[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hp9k2[0-9][0-9] | hp9k31[0-9])
+ basic_machine=m68000-hp
+ ;;
+ hp9k3[2-9][0-9])
+ basic_machine=m68k-hp
+ ;;
+ hp9k6[0-9][0-9] | hp6[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hp9k7[0-79][0-9] | hp7[0-79][0-9])
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k78[0-9] | hp78[0-9])
+ # FIXME: really hppa2.0-hp
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+ # FIXME: really hppa2.0-hp
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[0-9][13679] | hp8[0-9][13679])
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[0-9][0-9] | hp8[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hppa-next)
+ os=-nextstep3
+ ;;
+ hppaosf)
+ basic_machine=hppa1.1-hp
+ os=-osf
+ ;;
+ hppro)
+ basic_machine=hppa1.1-hp
+ os=-proelf
+ ;;
+ i370-ibm* | ibm*)
+ basic_machine=i370-ibm
+ ;;
+ i*86v32)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv32
+ ;;
+ i*86v4*)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv4
+ ;;
+ i*86v)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv
+ ;;
+ i*86sol2)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-solaris2
+ ;;
+ i386mach)
+ basic_machine=i386-mach
+ os=-mach
+ ;;
+ i386-vsta | vsta)
+ basic_machine=i386-unknown
+ os=-vsta
+ ;;
+ iris | iris4d)
+ basic_machine=mips-sgi
+ case $os in
+ -irix*)
+ ;;
+ *)
+ os=-irix4
+ ;;
+ esac
+ ;;
+ isi68 | isi)
+ basic_machine=m68k-isi
+ os=-sysv
+ ;;
+ leon-*|leon[3-9]-*)
+ basic_machine=sparc-`echo $basic_machine | sed 's/-.*//'`
+ ;;
+ m68knommu)
+ basic_machine=m68k-unknown
+ os=-linux
+ ;;
+ m68knommu-*)
+ basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=-linux
+ ;;
+ m88k-omron*)
+ basic_machine=m88k-omron
+ ;;
+ magnum | m3230)
+ basic_machine=mips-mips
+ os=-sysv
+ ;;
+ merlin)
+ basic_machine=ns32k-utek
+ os=-sysv
+ ;;
+ microblaze*)
+ basic_machine=microblaze-xilinx
+ ;;
+ mingw64)
+ basic_machine=x86_64-pc
+ os=-mingw64
+ ;;
+ mingw32)
+ basic_machine=i686-pc
+ os=-mingw32
+ ;;
+ mingw32ce)
+ basic_machine=arm-unknown
+ os=-mingw32ce
+ ;;
+ miniframe)
+ basic_machine=m68000-convergent
+ ;;
+ *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+ basic_machine=m68k-atari
+ os=-mint
+ ;;
+ mips3*-*)
+ basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+ ;;
+ mips3*)
+ basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+ ;;
+ monitor)
+ basic_machine=m68k-rom68k
+ os=-coff
+ ;;
+ morphos)
+ basic_machine=powerpc-unknown
+ os=-morphos
+ ;;
+ moxiebox)
+ basic_machine=moxie-unknown
+ os=-moxiebox
+ ;;
+ msdos)
+ basic_machine=i386-pc
+ os=-msdos
+ ;;
+ ms1-*)
+ basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
+ ;;
+ msys)
+ basic_machine=i686-pc
+ os=-msys
+ ;;
+ mvs)
+ basic_machine=i370-ibm
+ os=-mvs
+ ;;
+ nacl)
+ basic_machine=le32-unknown
+ os=-nacl
+ ;;
+ ncr3000)
+ basic_machine=i486-ncr
+ os=-sysv4
+ ;;
+ netbsd386)
+ basic_machine=i386-unknown
+ os=-netbsd
+ ;;
+ netwinder)
+ basic_machine=armv4l-rebel
+ os=-linux
+ ;;
+ news | news700 | news800 | news900)
+ basic_machine=m68k-sony
+ os=-newsos
+ ;;
+ news1000)
+ basic_machine=m68030-sony
+ os=-newsos
+ ;;
+ news-3600 | risc-news)
+ basic_machine=mips-sony
+ os=-newsos
+ ;;
+ necv70)
+ basic_machine=v70-nec
+ os=-sysv
+ ;;
+ next | m*-next )
+ basic_machine=m68k-next
+ case $os in
+ -nextstep* )
+ ;;
+ -ns2*)
+ os=-nextstep2
+ ;;
+ *)
+ os=-nextstep3
+ ;;
+ esac
+ ;;
+ nh3000)
+ basic_machine=m68k-harris
+ os=-cxux
+ ;;
+ nh[45]000)
+ basic_machine=m88k-harris
+ os=-cxux
+ ;;
+ nindy960)
+ basic_machine=i960-intel
+ os=-nindy
+ ;;
+ mon960)
+ basic_machine=i960-intel
+ os=-mon960
+ ;;
+ nonstopux)
+ basic_machine=mips-compaq
+ os=-nonstopux
+ ;;
+ np1)
+ basic_machine=np1-gould
+ ;;
+ neo-tandem)
+ basic_machine=neo-tandem
+ ;;
+ nse-tandem)
+ basic_machine=nse-tandem
+ ;;
+ nsr-tandem)
+ basic_machine=nsr-tandem
+ ;;
+ nsx-tandem)
+ basic_machine=nsx-tandem
+ ;;
+ op50n-* | op60c-*)
+ basic_machine=hppa1.1-oki
+ os=-proelf
+ ;;
+ openrisc | openrisc-*)
+ basic_machine=or32-unknown
+ ;;
+ os400)
+ basic_machine=powerpc-ibm
+ os=-os400
+ ;;
+ OSE68000 | ose68000)
+ basic_machine=m68000-ericsson
+ os=-ose
+ ;;
+ os68k)
+ basic_machine=m68k-none
+ os=-os68k
+ ;;
+ pa-hitachi)
+ basic_machine=hppa1.1-hitachi
+ os=-hiuxwe2
+ ;;
+ paragon)
+ basic_machine=i860-intel
+ os=-osf
+ ;;
+ parisc)
+ basic_machine=hppa-unknown
+ os=-linux
+ ;;
+ parisc-*)
+ basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=-linux
+ ;;
+ pbd)
+ basic_machine=sparc-tti
+ ;;
+ pbb)
+ basic_machine=m68k-tti
+ ;;
+ pc532 | pc532-*)
+ basic_machine=ns32k-pc532
+ ;;
+ pc98)
+ basic_machine=i386-pc
+ ;;
+ pc98-*)
+ basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentium | p5 | k5 | k6 | nexgen | viac3)
+ basic_machine=i586-pc
+ ;;
+ pentiumpro | p6 | 6x86 | athlon | athlon_*)
+ basic_machine=i686-pc
+ ;;
+ pentiumii | pentium2 | pentiumiii | pentium3)
+ basic_machine=i686-pc
+ ;;
+ pentium4)
+ basic_machine=i786-pc
+ ;;
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentiumpro-* | p6-* | 6x86-* | athlon-*)
+ basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+ basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentium4-*)
+ basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pn)
+ basic_machine=pn-gould
+ ;;
+ power) basic_machine=power-ibm
+ ;;
+ ppc | ppcbe) basic_machine=powerpc-unknown
+ ;;
+ ppc-* | ppcbe-*)
+ basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppcle | powerpclittle)
+ basic_machine=powerpcle-unknown
+ ;;
+ ppcle-* | powerpclittle-*)
+ basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppc64) basic_machine=powerpc64-unknown
+ ;;
+ ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppc64le | powerpc64little)
+ basic_machine=powerpc64le-unknown
+ ;;
+ ppc64le-* | powerpc64little-*)
+ basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ps2)
+ basic_machine=i386-ibm
+ ;;
+ pw32)
+ basic_machine=i586-unknown
+ os=-pw32
+ ;;
+ rdos | rdos64)
+ basic_machine=x86_64-pc
+ os=-rdos
+ ;;
+ rdos32)
+ basic_machine=i386-pc
+ os=-rdos
+ ;;
+ rom68k)
+ basic_machine=m68k-rom68k
+ os=-coff
+ ;;
+ rm[46]00)
+ basic_machine=mips-siemens
+ ;;
+ rtpc | rtpc-*)
+ basic_machine=romp-ibm
+ ;;
+ s390 | s390-*)
+ basic_machine=s390-ibm
+ ;;
+ s390x | s390x-*)
+ basic_machine=s390x-ibm
+ ;;
+ sa29200)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ sb1)
+ basic_machine=mipsisa64sb1-unknown
+ ;;
+ sb1el)
+ basic_machine=mipsisa64sb1el-unknown
+ ;;
+ sde)
+ basic_machine=mipsisa32-sde
+ os=-elf
+ ;;
+ sei)
+ basic_machine=mips-sei
+ os=-seiux
+ ;;
+ sequent)
+ basic_machine=i386-sequent
+ ;;
+ sh)
+ basic_machine=sh-hitachi
+ os=-hms
+ ;;
+ sh5el)
+ basic_machine=sh5le-unknown
+ ;;
+ sh64)
+ basic_machine=sh64-unknown
+ ;;
+ sparclite-wrs | simso-wrs)
+ basic_machine=sparclite-wrs
+ os=-vxworks
+ ;;
+ sps7)
+ basic_machine=m68k-bull
+ os=-sysv2
+ ;;
+ spur)
+ basic_machine=spur-unknown
+ ;;
+ st2000)
+ basic_machine=m68k-tandem
+ ;;
+ stratus)
+ basic_machine=i860-stratus
+ os=-sysv4
+ ;;
+ strongarm-* | thumb-*)
+ basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ sun2)
+ basic_machine=m68000-sun
+ ;;
+ sun2os3)
+ basic_machine=m68000-sun
+ os=-sunos3
+ ;;
+ sun2os4)
+ basic_machine=m68000-sun
+ os=-sunos4
+ ;;
+ sun3os3)
+ basic_machine=m68k-sun
+ os=-sunos3
+ ;;
+ sun3os4)
+ basic_machine=m68k-sun
+ os=-sunos4
+ ;;
+ sun4os3)
+ basic_machine=sparc-sun
+ os=-sunos3
+ ;;
+ sun4os4)
+ basic_machine=sparc-sun
+ os=-sunos4
+ ;;
+ sun4sol2)
+ basic_machine=sparc-sun
+ os=-solaris2
+ ;;
+ sun3 | sun3-*)
+ basic_machine=m68k-sun
+ ;;
+ sun4)
+ basic_machine=sparc-sun
+ ;;
+ sun386 | sun386i | roadrunner)
+ basic_machine=i386-sun
+ ;;
+ sv1)
+ basic_machine=sv1-cray
+ os=-unicos
+ ;;
+ symmetry)
+ basic_machine=i386-sequent
+ os=-dynix
+ ;;
+ t3e)
+ basic_machine=alphaev5-cray
+ os=-unicos
+ ;;
+ t90)
+ basic_machine=t90-cray
+ os=-unicos
+ ;;
+ tile*)
+ basic_machine=$basic_machine-unknown
+ os=-linux-gnu
+ ;;
+ tx39)
+ basic_machine=mipstx39-unknown
+ ;;
+ tx39el)
+ basic_machine=mipstx39el-unknown
+ ;;
+ toad1)
+ basic_machine=pdp10-xkl
+ os=-tops20
+ ;;
+ tower | tower-32)
+ basic_machine=m68k-ncr
+ ;;
+ tpf)
+ basic_machine=s390x-ibm
+ os=-tpf
+ ;;
+ udi29k)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ ultra3)
+ basic_machine=a29k-nyu
+ os=-sym1
+ ;;
+ v810 | necv810)
+ basic_machine=v810-nec
+ os=-none
+ ;;
+ vaxv)
+ basic_machine=vax-dec
+ os=-sysv
+ ;;
+ vms)
+ basic_machine=vax-dec
+ os=-vms
+ ;;
+ vpp*|vx|vx-*)
+ basic_machine=f301-fujitsu
+ ;;
+ vxworks960)
+ basic_machine=i960-wrs
+ os=-vxworks
+ ;;
+ vxworks68)
+ basic_machine=m68k-wrs
+ os=-vxworks
+ ;;
+ vxworks29k)
+ basic_machine=a29k-wrs
+ os=-vxworks
+ ;;
+ wasm32)
+ basic_machine=wasm32-unknown
+ ;;
+ w65*)
+ basic_machine=w65-wdc
+ os=-none
+ ;;
+ w89k-*)
+ basic_machine=hppa1.1-winbond
+ os=-proelf
+ ;;
+ xbox)
+ basic_machine=i686-pc
+ os=-mingw32
+ ;;
+ xps | xps100)
+ basic_machine=xps100-honeywell
+ ;;
+ xscale-* | xscalee[bl]-*)
+ basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'`
+ ;;
+ ymp)
+ basic_machine=ymp-cray
+ os=-unicos
+ ;;
+ z8k-*-coff)
+ basic_machine=z8k-unknown
+ os=-sim
+ ;;
+ z80-*-coff)
+ basic_machine=z80-unknown
+ os=-sim
+ ;;
+ none)
+ basic_machine=none-none
+ os=-none
+ ;;
+
+# Here we handle the default manufacturer of certain CPU types. It is in
+# some cases the only manufacturer, in others, it is the most popular.
+ w89k)
+ basic_machine=hppa1.1-winbond
+ ;;
+ op50n)
+ basic_machine=hppa1.1-oki
+ ;;
+ op60c)
+ basic_machine=hppa1.1-oki
+ ;;
+ romp)
+ basic_machine=romp-ibm
+ ;;
+ mmix)
+ basic_machine=mmix-knuth
+ ;;
+ rs6000)
+ basic_machine=rs6000-ibm
+ ;;
+ vax)
+ basic_machine=vax-dec
+ ;;
+ pdp10)
+ # there are many clones, so DEC is not a safe bet
+ basic_machine=pdp10-unknown
+ ;;
+ pdp11)
+ basic_machine=pdp11-dec
+ ;;
+ we32k)
+ basic_machine=we32k-att
+ ;;
+ sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele)
+ basic_machine=sh-unknown
+ ;;
+ sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
+ basic_machine=sparc-sun
+ ;;
+ cydra)
+ basic_machine=cydra-cydrome
+ ;;
+ orion)
+ basic_machine=orion-highlevel
+ ;;
+ orion105)
+ basic_machine=clipper-highlevel
+ ;;
+ mac | mpw | mac-mpw)
+ basic_machine=m68k-apple
+ ;;
+ pmac | pmac-mpw)
+ basic_machine=powerpc-apple
+ ;;
+ *-unknown)
+ # Make sure to match an already-canonicalized machine name.
+ ;;
+ *)
+ echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+ *-digital*)
+ basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+ ;;
+ *-commodore*)
+ basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+ ;;
+ *)
+ ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+ # First match some system type aliases
+ # that might get confused with valid system types.
+ # -solaris* is a basic system type, with this one exception.
+ -auroraux)
+ os=-auroraux
+ ;;
+ -solaris1 | -solaris1.*)
+ os=`echo $os | sed -e 's|solaris1|sunos4|'`
+ ;;
+ -solaris)
+ os=-solaris2
+ ;;
+ -svr4*)
+ os=-sysv4
+ ;;
+ -unixware*)
+ os=-sysv4.2uw
+ ;;
+ -gnu/linux*)
+ os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+ ;;
+ # First accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST END IN A *, to match a version number.
+ # -sysv* is not here because it comes later, after sysvr4.
+ -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+ | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\
+ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
+ | -sym* | -kopensolaris* | -plan9* \
+ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+ | -aos* | -aros* | -cloudabi* | -sortix* \
+ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+ | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
+ | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \
+ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+ | -chorusos* | -chorusrdb* | -cegcc* | -glidix* \
+ | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+ | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \
+ | -linux-newlib* | -linux-musl* | -linux-uclibc* \
+ | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \
+ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
+ | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \
+ | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox*)
+ # Remember, each alternative MUST END IN *, to match a version number.
+ ;;
+ -qnx*)
+ case $basic_machine in
+ x86-* | i*86-*)
+ ;;
+ *)
+ os=-nto$os
+ ;;
+ esac
+ ;;
+ -nto-qnx*)
+ ;;
+ -nto*)
+ os=`echo $os | sed -e 's|nto|nto-qnx|'`
+ ;;
+ -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+ | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \
+ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+ ;;
+ -mac*)
+ os=`echo $os | sed -e 's|mac|macos|'`
+ ;;
+ -linux-dietlibc)
+ os=-linux-dietlibc
+ ;;
+ -linux*)
+ os=`echo $os | sed -e 's|linux|linux-gnu|'`
+ ;;
+ -sunos5*)
+ os=`echo $os | sed -e 's|sunos5|solaris2|'`
+ ;;
+ -sunos6*)
+ os=`echo $os | sed -e 's|sunos6|solaris3|'`
+ ;;
+ -opened*)
+ os=-openedition
+ ;;
+ -os400*)
+ os=-os400
+ ;;
+ -wince*)
+ os=-wince
+ ;;
+ -osfrose*)
+ os=-osfrose
+ ;;
+ -osf*)
+ os=-osf
+ ;;
+ -utek*)
+ os=-bsd
+ ;;
+ -dynix*)
+ os=-bsd
+ ;;
+ -acis*)
+ os=-aos
+ ;;
+ -atheos*)
+ os=-atheos
+ ;;
+ -syllable*)
+ os=-syllable
+ ;;
+ -386bsd)
+ os=-bsd
+ ;;
+ -ctix* | -uts*)
+ os=-sysv
+ ;;
+ -nova*)
+ os=-rtmk-nova
+ ;;
+ -ns2 )
+ os=-nextstep2
+ ;;
+ -nsk*)
+ os=-nsk
+ ;;
+ # Preserve the version number of sinix5.
+ -sinix5.*)
+ os=`echo $os | sed -e 's|sinix|sysv|'`
+ ;;
+ -sinix*)
+ os=-sysv4
+ ;;
+ -tpf*)
+ os=-tpf
+ ;;
+ -triton*)
+ os=-sysv3
+ ;;
+ -oss*)
+ os=-sysv3
+ ;;
+ -svr4)
+ os=-sysv4
+ ;;
+ -svr3)
+ os=-sysv3
+ ;;
+ -sysvr4)
+ os=-sysv4
+ ;;
+ # This must come after -sysvr4.
+ -sysv*)
+ ;;
+ -ose*)
+ os=-ose
+ ;;
+ -es1800*)
+ os=-ose
+ ;;
+ -xenix)
+ os=-xenix
+ ;;
+ -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+ os=-mint
+ ;;
+ -aros*)
+ os=-aros
+ ;;
+ -zvmoe)
+ os=-zvmoe
+ ;;
+ -dicos*)
+ os=-dicos
+ ;;
+ -nacl*)
+ ;;
+ -ios)
+ ;;
+ -none)
+ ;;
+ *)
+ # Get rid of the `-' at the beginning of $os.
+ os=`echo $os | sed 's/[^-]*-//'`
+ echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system. Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+ score-*)
+ os=-elf
+ ;;
+ spu-*)
+ os=-elf
+ ;;
+ *-acorn)
+ os=-riscix1.2
+ ;;
+ arm*-rebel)
+ os=-linux
+ ;;
+ arm*-semi)
+ os=-aout
+ ;;
+ c4x-* | tic4x-*)
+ os=-coff
+ ;;
+ c8051-*)
+ os=-elf
+ ;;
+ hexagon-*)
+ os=-elf
+ ;;
+ tic54x-*)
+ os=-coff
+ ;;
+ tic55x-*)
+ os=-coff
+ ;;
+ tic6x-*)
+ os=-coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+ os=-tops20
+ ;;
+ pdp11-*)
+ os=-none
+ ;;
+ *-dec | vax-*)
+ os=-ultrix4.2
+ ;;
+ m68*-apollo)
+ os=-domain
+ ;;
+ i386-sun)
+ os=-sunos4.0.2
+ ;;
+ m68000-sun)
+ os=-sunos3
+ ;;
+ m68*-cisco)
+ os=-aout
+ ;;
+ mep-*)
+ os=-elf
+ ;;
+ mips*-cisco)
+ os=-elf
+ ;;
+ mips*-*)
+ os=-elf
+ ;;
+ or32-*)
+ os=-coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=-sysv3
+ ;;
+ sparc-* | *-sun)
+ os=-sunos4.1.1
+ ;;
+ pru-*)
+ os=-elf
+ ;;
+ *-be)
+ os=-beos
+ ;;
+ *-haiku)
+ os=-haiku
+ ;;
+ *-ibm)
+ os=-aix
+ ;;
+ *-knuth)
+ os=-mmixware
+ ;;
+ *-wec)
+ os=-proelf
+ ;;
+ *-winbond)
+ os=-proelf
+ ;;
+ *-oki)
+ os=-proelf
+ ;;
+ *-hp)
+ os=-hpux
+ ;;
+ *-hitachi)
+ os=-hiux
+ ;;
+ i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+ os=-sysv
+ ;;
+ *-cbm)
+ os=-amigaos
+ ;;
+ *-dg)
+ os=-dgux
+ ;;
+ *-dolphin)
+ os=-sysv3
+ ;;
+ m68k-ccur)
+ os=-rtu
+ ;;
+ m88k-omron*)
+ os=-luna
+ ;;
+ *-next )
+ os=-nextstep
+ ;;
+ *-sequent)
+ os=-ptx
+ ;;
+ *-crds)
+ os=-unos
+ ;;
+ *-ns)
+ os=-genix
+ ;;
+ i370-*)
+ os=-mvs
+ ;;
+ *-next)
+ os=-nextstep3
+ ;;
+ *-gould)
+ os=-sysv
+ ;;
+ *-highlevel)
+ os=-bsd
+ ;;
+ *-encore)
+ os=-bsd
+ ;;
+ *-sgi)
+ os=-irix
+ ;;
+ *-siemens)
+ os=-sysv4
+ ;;
+ *-masscomp)
+ os=-rtu
+ ;;
+ f30[01]-fujitsu | f700-fujitsu)
+ os=-uxpv
+ ;;
+ *-rom68k)
+ os=-coff
+ ;;
+ *-*bug)
+ os=-coff
+ ;;
+ *-apple)
+ os=-macos
+ ;;
+ *-atari*)
+ os=-mint
+ ;;
+ *)
+ os=-none
+ ;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer. We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+ *-unknown)
+ case $os in
+ -riscix*)
+ vendor=acorn
+ ;;
+ -sunos*)
+ vendor=sun
+ ;;
+ -cnk*|-aix*)
+ vendor=ibm
+ ;;
+ -beos*)
+ vendor=be
+ ;;
+ -hpux*)
+ vendor=hp
+ ;;
+ -mpeix*)
+ vendor=hp
+ ;;
+ -hiux*)
+ vendor=hitachi
+ ;;
+ -unos*)
+ vendor=crds
+ ;;
+ -dgux*)
+ vendor=dg
+ ;;
+ -luna*)
+ vendor=omron
+ ;;
+ -genix*)
+ vendor=ns
+ ;;
+ -mvs* | -opened*)
+ vendor=ibm
+ ;;
+ -os400*)
+ vendor=ibm
+ ;;
+ -ptx*)
+ vendor=sequent
+ ;;
+ -tpf*)
+ vendor=ibm
+ ;;
+ -vxsim* | -vxworks* | -windiss*)
+ vendor=wrs
+ ;;
+ -aux*)
+ vendor=apple
+ ;;
+ -hms*)
+ vendor=hitachi
+ ;;
+ -mpw* | -macos*)
+ vendor=apple
+ ;;
+ -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+ vendor=atari
+ ;;
+ -vos*)
+ vendor=stratus
+ ;;
+ esac
+ basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+ ;;
+esac
+
+echo $basic_machine$os
+exit
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/etc/depcomp b/etc/depcomp
new file mode 100755
index 0000000..b39f98f
--- /dev/null
+++ b/etc/depcomp
@@ -0,0 +1,791 @@
+#! /bin/sh
+# depcomp - compile a program generating dependencies as side-effects
+
+scriptversion=2016-01-11.22; # UTC
+
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+case $1 in
+ '')
+ echo "$0: No command. Try '$0 --help' for more information." 1>&2
+ exit 1;
+ ;;
+ -h | --h*)
+ cat <<\EOF
+Usage: depcomp [--help] [--version] PROGRAM [ARGS]
+
+Run PROGRAMS ARGS to compile a file, generating dependencies
+as side-effects.
+
+Environment variables:
+ depmode Dependency tracking mode.
+ source Source file read by 'PROGRAMS ARGS'.
+ object Object file output by 'PROGRAMS ARGS'.
+ DEPDIR directory where to store dependencies.
+ depfile Dependency file to output.
+ tmpdepfile Temporary file to use when outputting dependencies.
+ libtool Whether libtool is used (yes/no).
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+ exit $?
+ ;;
+ -v | --v*)
+ echo "depcomp $scriptversion"
+ exit $?
+ ;;
+esac
+
+# Get the directory component of the given path, and save it in the
+# global variables '$dir'. Note that this directory component will
+# be either empty or ending with a '/' character. This is deliberate.
+set_dir_from ()
+{
+ case $1 in
+ */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;;
+ *) dir=;;
+ esac
+}
+
+# Get the suffix-stripped basename of the given path, and save it the
+# global variable '$base'.
+set_base_from ()
+{
+ base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'`
+}
+
+# If no dependency file was actually created by the compiler invocation,
+# we still have to create a dummy depfile, to avoid errors with the
+# Makefile "include basename.Plo" scheme.
+make_dummy_depfile ()
+{
+ echo "#dummy" > "$depfile"
+}
+
+# Factor out some common post-processing of the generated depfile.
+# Requires the auxiliary global variable '$tmpdepfile' to be set.
+aix_post_process_depfile ()
+{
+ # If the compiler actually managed to produce a dependency file,
+ # post-process it.
+ if test -f "$tmpdepfile"; then
+ # Each line is of the form 'foo.o: dependency.h'.
+ # Do two passes, one to just change these to
+ # $object: dependency.h
+ # and one to simply output
+ # dependency.h:
+ # which is needed to avoid the deleted-header problem.
+ { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile"
+ sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile"
+ } > "$depfile"
+ rm -f "$tmpdepfile"
+ else
+ make_dummy_depfile
+ fi
+}
+
+# A tabulation character.
+tab=' '
+# A newline character.
+nl='
+'
+# Character ranges might be problematic outside the C locale.
+# These definitions help.
+upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ
+lower=abcdefghijklmnopqrstuvwxyz
+digits=0123456789
+alpha=${upper}${lower}
+
+if test -z "$depmode" || test -z "$source" || test -z "$object"; then
+ echo "depcomp: Variables source, object and depmode must be set" 1>&2
+ exit 1
+fi
+
+# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
+depfile=${depfile-`echo "$object" |
+ sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
+tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
+
+rm -f "$tmpdepfile"
+
+# Avoid interferences from the environment.
+gccflag= dashmflag=
+
+# Some modes work just like other modes, but use different flags. We
+# parameterize here, but still list the modes in the big case below,
+# to make depend.m4 easier to write. Note that we *cannot* use a case
+# here, because this file can only contain one case statement.
+if test "$depmode" = hp; then
+ # HP compiler uses -M and no extra arg.
+ gccflag=-M
+ depmode=gcc
+fi
+
+if test "$depmode" = dashXmstdout; then
+ # This is just like dashmstdout with a different argument.
+ dashmflag=-xM
+ depmode=dashmstdout
+fi
+
+cygpath_u="cygpath -u -f -"
+if test "$depmode" = msvcmsys; then
+ # This is just like msvisualcpp but w/o cygpath translation.
+ # Just convert the backslash-escaped backslashes to single forward
+ # slashes to satisfy depend.m4
+ cygpath_u='sed s,\\\\,/,g'
+ depmode=msvisualcpp
+fi
+
+if test "$depmode" = msvc7msys; then
+ # This is just like msvc7 but w/o cygpath translation.
+ # Just convert the backslash-escaped backslashes to single forward
+ # slashes to satisfy depend.m4
+ cygpath_u='sed s,\\\\,/,g'
+ depmode=msvc7
+fi
+
+if test "$depmode" = xlc; then
+ # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information.
+ gccflag=-qmakedep=gcc,-MF
+ depmode=gcc
+fi
+
+case "$depmode" in
+gcc3)
+## gcc 3 implements dependency tracking that does exactly what
+## we want. Yay! Note: for some reason libtool 1.4 doesn't like
+## it if -MD -MP comes after the -MF stuff. Hmm.
+## Unfortunately, FreeBSD c89 acceptance of flags depends upon
+## the command line argument order; so add the flags where they
+## appear in depend2.am. Note that the slowdown incurred here
+## affects only configure: in makefiles, %FASTDEP% shortcuts this.
+ for arg
+ do
+ case $arg in
+ -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
+ *) set fnord "$@" "$arg" ;;
+ esac
+ shift # fnord
+ shift # $arg
+ done
+ "$@"
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ mv "$tmpdepfile" "$depfile"
+ ;;
+
+gcc)
+## Note that this doesn't just cater to obsosete pre-3.x GCC compilers.
+## but also to in-use compilers like IMB xlc/xlC and the HP C compiler.
+## (see the conditional assignment to $gccflag above).
+## There are various ways to get dependency output from gcc. Here's
+## why we pick this rather obscure method:
+## - Don't want to use -MD because we'd like the dependencies to end
+## up in a subdir. Having to rename by hand is ugly.
+## (We might end up doing this anyway to support other compilers.)
+## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
+## -MM, not -M (despite what the docs say). Also, it might not be
+## supported by the other compilers which use the 'gcc' depmode.
+## - Using -M directly means running the compiler twice (even worse
+## than renaming).
+ if test -z "$gccflag"; then
+ gccflag=-MD,
+ fi
+ "$@" -Wp,"$gccflag$tmpdepfile"
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ # The second -e expression handles DOS-style file names with drive
+ # letters.
+ sed -e 's/^[^:]*: / /' \
+ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
+## This next piece of magic avoids the "deleted header file" problem.
+## The problem is that when a header file which appears in a .P file
+## is deleted, the dependency causes make to die (because there is
+## typically no way to rebuild the header). We avoid this by adding
+## dummy dependencies for each header file. Too bad gcc doesn't do
+## this for us directly.
+## Some versions of gcc put a space before the ':'. On the theory
+## that the space means something, we add a space to the output as
+## well. hp depmode also adds that space, but also prefixes the VPATH
+## to the object. Take care to not repeat it in the output.
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly. Breaking it into two sed invocations is a workaround.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+hp)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+sgi)
+ if test "$libtool" = yes; then
+ "$@" "-Wp,-MDupdate,$tmpdepfile"
+ else
+ "$@" -MDupdate "$tmpdepfile"
+ fi
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+
+ if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files
+ echo "$object : \\" > "$depfile"
+ # Clip off the initial element (the dependent). Don't try to be
+ # clever and replace this with sed code, as IRIX sed won't handle
+ # lines with more than a fixed number of characters (4096 in
+ # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines;
+ # the IRIX cc adds comments like '#:fec' to the end of the
+ # dependency line.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \
+ | tr "$nl" ' ' >> "$depfile"
+ echo >> "$depfile"
+ # The second pass generates a dummy entry for each header file.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
+ >> "$depfile"
+ else
+ make_dummy_depfile
+ fi
+ rm -f "$tmpdepfile"
+ ;;
+
+xlc)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+aix)
+ # The C for AIX Compiler uses -M and outputs the dependencies
+ # in a .u file. In older versions, this file always lives in the
+ # current directory. Also, the AIX compiler puts '$object:' at the
+ # start of each line; $object doesn't have directory information.
+ # Version 6 uses the directory in both cases.
+ set_dir_from "$object"
+ set_base_from "$object"
+ if test "$libtool" = yes; then
+ tmpdepfile1=$dir$base.u
+ tmpdepfile2=$base.u
+ tmpdepfile3=$dir.libs/$base.u
+ "$@" -Wc,-M
+ else
+ tmpdepfile1=$dir$base.u
+ tmpdepfile2=$dir$base.u
+ tmpdepfile3=$dir$base.u
+ "$@" -M
+ fi
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ exit $stat
+ fi
+
+ for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ do
+ test -f "$tmpdepfile" && break
+ done
+ aix_post_process_depfile
+ ;;
+
+tcc)
+ # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26
+ # FIXME: That version still under development at the moment of writing.
+ # Make that this statement remains true also for stable, released
+ # versions.
+ # It will wrap lines (doesn't matter whether long or short) with a
+ # trailing '\', as in:
+ #
+ # foo.o : \
+ # foo.c \
+ # foo.h \
+ #
+ # It will put a trailing '\' even on the last line, and will use leading
+ # spaces rather than leading tabs (at least since its commit 0394caf7
+ # "Emit spaces for -MD").
+ "$@" -MD -MF "$tmpdepfile"
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'.
+ # We have to change lines of the first kind to '$object: \'.
+ sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile"
+ # And for each line of the second kind, we have to emit a 'dep.h:'
+ # dummy dependency, to avoid the deleted-header problem.
+ sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+## The order of this option in the case statement is important, since the
+## shell code in configure will try each of these formats in the order
+## listed in this file. A plain '-MD' option would be understood by many
+## compilers, so we must ensure this comes after the gcc and icc options.
+pgcc)
+ # Portland's C compiler understands '-MD'.
+ # Will always output deps to 'file.d' where file is the root name of the
+ # source file under compilation, even if file resides in a subdirectory.
+ # The object file name does not affect the name of the '.d' file.
+ # pgcc 10.2 will output
+ # foo.o: sub/foo.c sub/foo.h
+ # and will wrap long lines using '\' :
+ # foo.o: sub/foo.c ... \
+ # sub/foo.h ... \
+ # ...
+ set_dir_from "$object"
+ # Use the source, not the object, to determine the base name, since
+ # that's sadly what pgcc will do too.
+ set_base_from "$source"
+ tmpdepfile=$base.d
+
+ # For projects that build the same source file twice into different object
+ # files, the pgcc approach of using the *source* file root name can cause
+ # problems in parallel builds. Use a locking strategy to avoid stomping on
+ # the same $tmpdepfile.
+ lockdir=$base.d-lock
+ trap "
+ echo '$0: caught signal, cleaning up...' >&2
+ rmdir '$lockdir'
+ exit 1
+ " 1 2 13 15
+ numtries=100
+ i=$numtries
+ while test $i -gt 0; do
+ # mkdir is a portable test-and-set.
+ if mkdir "$lockdir" 2>/dev/null; then
+ # This process acquired the lock.
+ "$@" -MD
+ stat=$?
+ # Release the lock.
+ rmdir "$lockdir"
+ break
+ else
+ # If the lock is being held by a different process, wait
+ # until the winning process is done or we timeout.
+ while test -d "$lockdir" && test $i -gt 0; do
+ sleep 1
+ i=`expr $i - 1`
+ done
+ fi
+ i=`expr $i - 1`
+ done
+ trap - 1 2 13 15
+ if test $i -le 0; then
+ echo "$0: failed to acquire lock after $numtries attempts" >&2
+ echo "$0: check lockdir '$lockdir'" >&2
+ exit 1
+ fi
+
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ # Each line is of the form `foo.o: dependent.h',
+ # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
+ # Do two passes, one to just change these to
+ # `$object: dependent.h' and one to simply `dependent.h:'.
+ sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
+ # Some versions of the HPUX 10.20 sed can't process this invocation
+ # correctly. Breaking it into two sed invocations is a workaround.
+ sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+hp2)
+ # The "hp" stanza above does not work with aCC (C++) and HP's ia64
+ # compilers, which have integrated preprocessors. The correct option
+ # to use with these is +Maked; it writes dependencies to a file named
+ # 'foo.d', which lands next to the object file, wherever that
+ # happens to be.
+ # Much of this is similar to the tru64 case; see comments there.
+ set_dir_from "$object"
+ set_base_from "$object"
+ if test "$libtool" = yes; then
+ tmpdepfile1=$dir$base.d
+ tmpdepfile2=$dir.libs/$base.d
+ "$@" -Wc,+Maked
+ else
+ tmpdepfile1=$dir$base.d
+ tmpdepfile2=$dir$base.d
+ "$@" +Maked
+ fi
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile1" "$tmpdepfile2"
+ exit $stat
+ fi
+
+ for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
+ do
+ test -f "$tmpdepfile" && break
+ done
+ if test -f "$tmpdepfile"; then
+ sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile"
+ # Add 'dependent.h:' lines.
+ sed -ne '2,${
+ s/^ *//
+ s/ \\*$//
+ s/$/:/
+ p
+ }' "$tmpdepfile" >> "$depfile"
+ else
+ make_dummy_depfile
+ fi
+ rm -f "$tmpdepfile" "$tmpdepfile2"
+ ;;
+
+tru64)
+ # The Tru64 compiler uses -MD to generate dependencies as a side
+ # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'.
+ # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
+ # dependencies in 'foo.d' instead, so we check for that too.
+ # Subdirectories are respected.
+ set_dir_from "$object"
+ set_base_from "$object"
+
+ if test "$libtool" = yes; then
+ # Libtool generates 2 separate objects for the 2 libraries. These
+ # two compilations output dependencies in $dir.libs/$base.o.d and
+ # in $dir$base.o.d. We have to check for both files, because
+ # one of the two compilations can be disabled. We should prefer
+ # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
+ # automatically cleaned when .libs/ is deleted, while ignoring
+ # the former would cause a distcleancheck panic.
+ tmpdepfile1=$dir$base.o.d # libtool 1.5
+ tmpdepfile2=$dir.libs/$base.o.d # Likewise.
+ tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504
+ "$@" -Wc,-MD
+ else
+ tmpdepfile1=$dir$base.d
+ tmpdepfile2=$dir$base.d
+ tmpdepfile3=$dir$base.d
+ "$@" -MD
+ fi
+
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ exit $stat
+ fi
+
+ for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ do
+ test -f "$tmpdepfile" && break
+ done
+ # Same post-processing that is required for AIX mode.
+ aix_post_process_depfile
+ ;;
+
+msvc7)
+ if test "$libtool" = yes; then
+ showIncludes=-Wc,-showIncludes
+ else
+ showIncludes=-showIncludes
+ fi
+ "$@" $showIncludes > "$tmpdepfile"
+ stat=$?
+ grep -v '^Note: including file: ' "$tmpdepfile"
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ # The first sed program below extracts the file names and escapes
+ # backslashes for cygpath. The second sed program outputs the file
+ # name when reading, but also accumulates all include files in the
+ # hold buffer in order to output them again at the end. This only
+ # works with sed implementations that can handle large buffers.
+ sed < "$tmpdepfile" -n '
+/^Note: including file: *\(.*\)/ {
+ s//\1/
+ s/\\/\\\\/g
+ p
+}' | $cygpath_u | sort -u | sed -n '
+s/ /\\ /g
+s/\(.*\)/'"$tab"'\1 \\/p
+s/.\(.*\) \\/\1:/
+H
+$ {
+ s/.*/'"$tab"'/
+ G
+ p
+}' >> "$depfile"
+ echo >> "$depfile" # make sure the fragment doesn't end with a backslash
+ rm -f "$tmpdepfile"
+ ;;
+
+msvc7msys)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+#nosideeffect)
+ # This comment above is used by automake to tell side-effect
+ # dependency tracking mechanisms from slower ones.
+
+dashmstdout)
+ # Important note: in order to support this mode, a compiler *must*
+ # always write the preprocessed file to stdout, regardless of -o.
+ "$@" || exit $?
+
+ # Remove the call to Libtool.
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+
+ # Remove '-o $object'.
+ IFS=" "
+ for arg
+ do
+ case $arg in
+ -o)
+ shift
+ ;;
+ $object)
+ shift
+ ;;
+ *)
+ set fnord "$@" "$arg"
+ shift # fnord
+ shift # $arg
+ ;;
+ esac
+ done
+
+ test -z "$dashmflag" && dashmflag=-M
+ # Require at least two characters before searching for ':'
+ # in the target name. This is to cope with DOS-style filenames:
+ # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise.
+ "$@" $dashmflag |
+ sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile"
+ rm -f "$depfile"
+ cat < "$tmpdepfile" > "$depfile"
+ # Some versions of the HPUX 10.20 sed can't process this sed invocation
+ # correctly. Breaking it into two sed invocations is a workaround.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+dashXmstdout)
+ # This case only exists to satisfy depend.m4. It is never actually
+ # run, as this mode is specially recognized in the preamble.
+ exit 1
+ ;;
+
+makedepend)
+ "$@" || exit $?
+ # Remove any Libtool call
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+ # X makedepend
+ shift
+ cleared=no eat=no
+ for arg
+ do
+ case $cleared in
+ no)
+ set ""; shift
+ cleared=yes ;;
+ esac
+ if test $eat = yes; then
+ eat=no
+ continue
+ fi
+ case "$arg" in
+ -D*|-I*)
+ set fnord "$@" "$arg"; shift ;;
+ # Strip any option that makedepend may not understand. Remove
+ # the object too, otherwise makedepend will parse it as a source file.
+ -arch)
+ eat=yes ;;
+ -*|$object)
+ ;;
+ *)
+ set fnord "$@" "$arg"; shift ;;
+ esac
+ done
+ obj_suffix=`echo "$object" | sed 's/^.*\././'`
+ touch "$tmpdepfile"
+ ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
+ rm -f "$depfile"
+ # makedepend may prepend the VPATH from the source file name to the object.
+ # No need to regex-escape $object, excess matching of '.' is harmless.
+ sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile"
+ # Some versions of the HPUX 10.20 sed can't process the last invocation
+ # correctly. Breaking it into two sed invocations is a workaround.
+ sed '1,2d' "$tmpdepfile" \
+ | tr ' ' "$nl" \
+ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile" "$tmpdepfile".bak
+ ;;
+
+cpp)
+ # Important note: in order to support this mode, a compiler *must*
+ # always write the preprocessed file to stdout.
+ "$@" || exit $?
+
+ # Remove the call to Libtool.
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+
+ # Remove '-o $object'.
+ IFS=" "
+ for arg
+ do
+ case $arg in
+ -o)
+ shift
+ ;;
+ $object)
+ shift
+ ;;
+ *)
+ set fnord "$@" "$arg"
+ shift # fnord
+ shift # $arg
+ ;;
+ esac
+ done
+
+ "$@" -E \
+ | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+ -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+ | sed '$ s: \\$::' > "$tmpdepfile"
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ cat < "$tmpdepfile" >> "$depfile"
+ sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+msvisualcpp)
+ # Important note: in order to support this mode, a compiler *must*
+ # always write the preprocessed file to stdout.
+ "$@" || exit $?
+
+ # Remove the call to Libtool.
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+
+ IFS=" "
+ for arg
+ do
+ case "$arg" in
+ -o)
+ shift
+ ;;
+ $object)
+ shift
+ ;;
+ "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
+ set fnord "$@"
+ shift
+ shift
+ ;;
+ *)
+ set fnord "$@" "$arg"
+ shift
+ shift
+ ;;
+ esac
+ done
+ "$@" -E 2>/dev/null |
+ sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile"
+ echo "$tab" >> "$depfile"
+ sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+msvcmsys)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+none)
+ exec "$@"
+ ;;
+
+*)
+ echo "Unknown depmode $depmode" 1>&2
+ exit 1
+ ;;
+esac
+
+exit 0
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/etc/install-sh b/etc/install-sh
new file mode 100755
index 0000000..0360b79
--- /dev/null
+++ b/etc/install-sh
@@ -0,0 +1,501 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2016-01-11.22; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+tab=' '
+nl='
+'
+IFS=" $tab$nl"
+
+# Set DOITPROG to "echo" to test this script.
+
+doit=${DOITPROG-}
+doit_exec=${doit:-exec}
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+is_target_a_directory=possibly
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+ or: $0 [OPTION]... SRCFILES... DIRECTORY
+ or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+ or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+ --help display this help and exit.
+ --version display version info and exit.
+
+ -c (ignored)
+ -C install only if different (preserve the last data modification time)
+ -d create directories instead of installing files.
+ -g GROUP $chgrpprog installed files to GROUP.
+ -m MODE $chmodprog installed files to MODE.
+ -o USER $chownprog installed files to USER.
+ -s $stripprog installed files.
+ -t DIRECTORY install into DIRECTORY.
+ -T report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+ CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+ RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+ case $1 in
+ -c) ;;
+
+ -C) copy_on_change=true;;
+
+ -d) dir_arg=true;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift;;
+
+ --help) echo "$usage"; exit $?;;
+
+ -m) mode=$2
+ case $mode in
+ *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
+ echo "$0: invalid mode: $mode" >&2
+ exit 1;;
+ esac
+ shift;;
+
+ -o) chowncmd="$chownprog $2"
+ shift;;
+
+ -s) stripcmd=$stripprog;;
+
+ -t)
+ is_target_a_directory=always
+ dst_arg=$2
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ shift;;
+
+ -T) is_target_a_directory=never;;
+
+ --version) echo "$0 $scriptversion"; exit $?;;
+
+ --) shift
+ break;;
+
+ -*) echo "$0: invalid option: $1" >&2
+ exit 1;;
+
+ *) break;;
+ esac
+ shift
+done
+
+# We allow the use of options -d and -T together, by making -d
+# take the precedence; this is for compatibility with GNU install.
+
+if test -n "$dir_arg"; then
+ if test -n "$dst_arg"; then
+ echo "$0: target directory not allowed when installing a directory." >&2
+ exit 1
+ fi
+fi
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+ # When -d is used, all remaining arguments are directories to create.
+ # When -t is used, the destination is already specified.
+ # Otherwise, the last argument is the destination. Remove it from $@.
+ for arg
+ do
+ if test -n "$dst_arg"; then
+ # $@ is not empty: it contains at least $arg.
+ set fnord "$@" "$dst_arg"
+ shift # fnord
+ fi
+ shift # arg
+ dst_arg=$arg
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ done
+fi
+
+if test $# -eq 0; then
+ if test -z "$dir_arg"; then
+ echo "$0: no input file specified." >&2
+ exit 1
+ fi
+ # It's OK to call 'install-sh -d' without argument.
+ # This can happen when creating conditional directories.
+ exit 0
+fi
+
+if test -z "$dir_arg"; then
+ if test $# -gt 1 || test "$is_target_a_directory" = always; then
+ if test ! -d "$dst_arg"; then
+ echo "$0: $dst_arg: Is not a directory." >&2
+ exit 1
+ fi
+ fi
+fi
+
+if test -z "$dir_arg"; then
+ do_exit='(exit $ret); exit $ret'
+ trap "ret=129; $do_exit" 1
+ trap "ret=130; $do_exit" 2
+ trap "ret=141; $do_exit" 13
+ trap "ret=143; $do_exit" 15
+
+ # Set umask so as not to create temps with too-generous modes.
+ # However, 'strip' requires both read and write access to temps.
+ case $mode in
+ # Optimize common cases.
+ *644) cp_umask=133;;
+ *755) cp_umask=22;;
+
+ *[0-7])
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw='% 200'
+ fi
+ cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+ *)
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw=,u+rw
+ fi
+ cp_umask=$mode$u_plus_rw;;
+ esac
+fi
+
+for src
+do
+ # Protect names problematic for 'test' and other utilities.
+ case $src in
+ -* | [=\(\)!]) src=./$src;;
+ esac
+
+ if test -n "$dir_arg"; then
+ dst=$src
+ dstdir=$dst
+ test -d "$dstdir"
+ dstdir_status=$?
+ else
+
+ # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+ # might cause directories to be created, which would be especially bad
+ # if $src (and thus $dsttmp) contains '*'.
+ if test ! -f "$src" && test ! -d "$src"; then
+ echo "$0: $src does not exist." >&2
+ exit 1
+ fi
+
+ if test -z "$dst_arg"; then
+ echo "$0: no destination specified." >&2
+ exit 1
+ fi
+ dst=$dst_arg
+
+ # If destination is a directory, append the input filename; won't work
+ # if double slashes aren't ignored.
+ if test -d "$dst"; then
+ if test "$is_target_a_directory" = never; then
+ echo "$0: $dst_arg: Is a directory" >&2
+ exit 1
+ fi
+ dstdir=$dst
+ dst=$dstdir/`basename "$src"`
+ dstdir_status=0
+ else
+ dstdir=`dirname "$dst"`
+ test -d "$dstdir"
+ dstdir_status=$?
+ fi
+ fi
+
+ obsolete_mkdir_used=false
+
+ if test $dstdir_status != 0; then
+ case $posix_mkdir in
+ '')
+ # Create intermediate dirs using mode 755 as modified by the umask.
+ # This is like FreeBSD 'install' as of 1997-10-28.
+ umask=`umask`
+ case $stripcmd.$umask in
+ # Optimize common cases.
+ *[2367][2367]) mkdir_umask=$umask;;
+ .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+ *[0-7])
+ mkdir_umask=`expr $umask + 22 \
+ - $umask % 100 % 40 + $umask % 20 \
+ - $umask % 10 % 4 + $umask % 2
+ `;;
+ *) mkdir_umask=$umask,go-w;;
+ esac
+
+ # With -d, create the new directory with the user-specified mode.
+ # Otherwise, rely on $mkdir_umask.
+ if test -n "$dir_arg"; then
+ mkdir_mode=-m$mode
+ else
+ mkdir_mode=
+ fi
+
+ posix_mkdir=false
+ case $umask in
+ *[123567][0-7][0-7])
+ # POSIX mkdir -p sets u+wx bits regardless of umask, which
+ # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+ ;;
+ *)
+ tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+ if (umask $mkdir_umask &&
+ exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+ then
+ if test -z "$dir_arg" || {
+ # Check for POSIX incompatibilities with -m.
+ # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+ # other-writable bit of parent directory when it shouldn't.
+ # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+ ls_ld_tmpdir=`ls -ld "$tmpdir"`
+ case $ls_ld_tmpdir in
+ d????-?r-*) different_mode=700;;
+ d????-?--*) different_mode=755;;
+ *) false;;
+ esac &&
+ $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+ ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+ test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+ }
+ }
+ then posix_mkdir=:
+ fi
+ rmdir "$tmpdir/d" "$tmpdir"
+ else
+ # Remove any dirs left behind by ancient mkdir implementations.
+ rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+ fi
+ trap '' 0;;
+ esac;;
+ esac
+
+ if
+ $posix_mkdir && (
+ umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+ )
+ then :
+ else
+
+ # The umask is ridiculous, or mkdir does not conform to POSIX,
+ # or it failed possibly due to a race condition. Create the
+ # directory the slow way, step by step, checking for races as we go.
+
+ case $dstdir in
+ /*) prefix='/';;
+ [-=\(\)!]*) prefix='./';;
+ *) prefix='';;
+ esac
+
+ oIFS=$IFS
+ IFS=/
+ set -f
+ set fnord $dstdir
+ shift
+ set +f
+ IFS=$oIFS
+
+ prefixes=
+
+ for d
+ do
+ test X"$d" = X && continue
+
+ prefix=$prefix$d
+ if test -d "$prefix"; then
+ prefixes=
+ else
+ if $posix_mkdir; then
+ (umask=$mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+ # Don't fail if two instances are running concurrently.
+ test -d "$prefix" || exit 1
+ else
+ case $prefix in
+ *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) qprefix=$prefix;;
+ esac
+ prefixes="$prefixes '$qprefix'"
+ fi
+ fi
+ prefix=$prefix/
+ done
+
+ if test -n "$prefixes"; then
+ # Don't fail if two instances are running concurrently.
+ (umask $mkdir_umask &&
+ eval "\$doit_exec \$mkdirprog $prefixes") ||
+ test -d "$dstdir" || exit 1
+ obsolete_mkdir_used=true
+ fi
+ fi
+ fi
+
+ if test -n "$dir_arg"; then
+ { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+ { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+ else
+
+ # Make a couple of temp file names in the proper directory.
+ dsttmp=$dstdir/_inst.$$_
+ rmtmp=$dstdir/_rm.$$_
+
+ # Trap to clean up those temp files at exit.
+ trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+ # Copy the file name to the temp name.
+ (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+ # and set any options; do chmod last to preserve setuid bits.
+ #
+ # If any of these fail, we abort the whole thing. If we want to
+ # ignore errors from any of these, just make sure not to ignore
+ # errors from the above "$doit $cpprog $src $dsttmp" command.
+ #
+ { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+ { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+ { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+ # If -C, don't bother to copy if it wouldn't change the file.
+ if $copy_on_change &&
+ old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
+ new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
+ set -f &&
+ set X $old && old=:$2:$4:$5:$6 &&
+ set X $new && new=:$2:$4:$5:$6 &&
+ set +f &&
+ test "$old" = "$new" &&
+ $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+ then
+ rm -f "$dsttmp"
+ else
+ # Rename the file to the real destination.
+ $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+ # The rename failed, perhaps because mv can't rename something else
+ # to itself, or perhaps because mv is so ancient that it does not
+ # support -f.
+ {
+ # Now remove or move aside any old file at destination location.
+ # We try this two ways since rm can't unlink itself on some
+ # systems and the destination file might be busy for other
+ # reasons. In this case, the final cleanup might fail but the new
+ # file should still install successfully.
+ {
+ test ! -f "$dst" ||
+ $doit $rmcmd -f "$dst" 2>/dev/null ||
+ { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+ { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+ } ||
+ { echo "$0: cannot unlink or rename $dst" >&2
+ (exit 1); exit 1
+ }
+ } &&
+
+ # Now rename the file to the real destination.
+ $doit $mvcmd "$dsttmp" "$dst"
+ }
+ fi || exit 1
+
+ trap '' 0
+ fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/etc/missing b/etc/missing
new file mode 100755
index 0000000..c6e3795
--- /dev/null
+++ b/etc/missing
@@ -0,0 +1,215 @@
+#! /bin/sh
+# Common wrapper for a few potentially missing GNU programs.
+
+scriptversion=2016-01-11.22; # UTC
+
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
+# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+if test $# -eq 0; then
+ echo 1>&2 "Try '$0 --help' for more information"
+ exit 1
+fi
+
+case $1 in
+
+ --is-lightweight)
+ # Used by our autoconf macros to check whether the available missing
+ # script is modern enough.
+ exit 0
+ ;;
+
+ --run)
+ # Back-compat with the calling convention used by older automake.
+ shift
+ ;;
+
+ -h|--h|--he|--hel|--help)
+ echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due
+to PROGRAM being missing or too old.
+
+Options:
+ -h, --help display this help and exit
+ -v, --version output version information and exit
+
+Supported PROGRAM values:
+ aclocal autoconf autoheader autom4te automake makeinfo
+ bison yacc flex lex help2man
+
+Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and
+'g' are ignored when checking the name.
+
+Send bug reports to <bug-automake@gnu.org>."
+ exit $?
+ ;;
+
+ -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+ echo "missing $scriptversion (GNU Automake)"
+ exit $?
+ ;;
+
+ -*)
+ echo 1>&2 "$0: unknown '$1' option"
+ echo 1>&2 "Try '$0 --help' for more information"
+ exit 1
+ ;;
+
+esac
+
+# Run the given program, remember its exit status.
+"$@"; st=$?
+
+# If it succeeded, we are done.
+test $st -eq 0 && exit 0
+
+# Also exit now if we it failed (or wasn't found), and '--version' was
+# passed; such an option is passed most likely to detect whether the
+# program is present and works.
+case $2 in --version|--help) exit $st;; esac
+
+# Exit code 63 means version mismatch. This often happens when the user
+# tries to use an ancient version of a tool on a file that requires a
+# minimum version.
+if test $st -eq 63; then
+ msg="probably too old"
+elif test $st -eq 127; then
+ # Program was missing.
+ msg="missing on your system"
+else
+ # Program was found and executed, but failed. Give up.
+ exit $st
+fi
+
+perl_URL=http://www.perl.org/
+flex_URL=http://flex.sourceforge.net/
+gnu_software_URL=http://www.gnu.org/software
+
+program_details ()
+{
+ case $1 in
+ aclocal|automake)
+ echo "The '$1' program is part of the GNU Automake package:"
+ echo "<$gnu_software_URL/automake>"
+ echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:"
+ echo "<$gnu_software_URL/autoconf>"
+ echo "<$gnu_software_URL/m4/>"
+ echo "<$perl_URL>"
+ ;;
+ autoconf|autom4te|autoheader)
+ echo "The '$1' program is part of the GNU Autoconf package:"
+ echo "<$gnu_software_URL/autoconf/>"
+ echo "It also requires GNU m4 and Perl in order to run:"
+ echo "<$gnu_software_URL/m4/>"
+ echo "<$perl_URL>"
+ ;;
+ esac
+}
+
+give_advice ()
+{
+ # Normalize program name to check for.
+ normalized_program=`echo "$1" | sed '
+ s/^gnu-//; t
+ s/^gnu//; t
+ s/^g//; t'`
+
+ printf '%s\n' "'$1' is $msg."
+
+ configure_deps="'configure.ac' or m4 files included by 'configure.ac'"
+ case $normalized_program in
+ autoconf*)
+ echo "You should only need it if you modified 'configure.ac',"
+ echo "or m4 files included by it."
+ program_details 'autoconf'
+ ;;
+ autoheader*)
+ echo "You should only need it if you modified 'acconfig.h' or"
+ echo "$configure_deps."
+ program_details 'autoheader'
+ ;;
+ automake*)
+ echo "You should only need it if you modified 'Makefile.am' or"
+ echo "$configure_deps."
+ program_details 'automake'
+ ;;
+ aclocal*)
+ echo "You should only need it if you modified 'acinclude.m4' or"
+ echo "$configure_deps."
+ program_details 'aclocal'
+ ;;
+ autom4te*)
+ echo "You might have modified some maintainer files that require"
+ echo "the 'autom4te' program to be rebuilt."
+ program_details 'autom4te'
+ ;;
+ bison*|yacc*)
+ echo "You should only need it if you modified a '.y' file."
+ echo "You may want to install the GNU Bison package:"
+ echo "<$gnu_software_URL/bison/>"
+ ;;
+ lex*|flex*)
+ echo "You should only need it if you modified a '.l' file."
+ echo "You may want to install the Fast Lexical Analyzer package:"
+ echo "<$flex_URL>"
+ ;;
+ help2man*)
+ echo "You should only need it if you modified a dependency" \
+ "of a man page."
+ echo "You may want to install the GNU Help2man package:"
+ echo "<$gnu_software_URL/help2man/>"
+ ;;
+ makeinfo*)
+ echo "You should only need it if you modified a '.texi' file, or"
+ echo "any other file indirectly affecting the aspect of the manual."
+ echo "You might want to install the Texinfo package:"
+ echo "<$gnu_software_URL/texinfo/>"
+ echo "The spurious makeinfo call might also be the consequence of"
+ echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might"
+ echo "want to install GNU make:"
+ echo "<$gnu_software_URL/make/>"
+ ;;
+ *)
+ echo "You might have modified some files without having the proper"
+ echo "tools for further handling them. Check the 'README' file, it"
+ echo "often tells you about the needed prerequisites for installing"
+ echo "this package. You may also peek at any GNU archive site, in"
+ echo "case some other package contains this missing '$1' program."
+ ;;
+ esac
+}
+
+give_advice "$1" | sed -e '1s/^/WARNING: /' \
+ -e '2,$s/^/ /' >&2
+
+# Propagate the correct exit status (expected to be 127 for a program
+# not found, 63 for a program that failed due to version mismatch).
+exit $st
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/etc/ylwrap b/etc/ylwrap
new file mode 100755
index 0000000..d788f2d
--- /dev/null
+++ b/etc/ylwrap
@@ -0,0 +1,247 @@
+#! /bin/sh
+# ylwrap - wrapper for lex/yacc invocations.
+
+scriptversion=2016-01-11.22; # UTC
+
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
+#
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+get_dirname ()
+{
+ case $1 in
+ */*|*\\*) printf '%s\n' "$1" | sed -e 's|\([\\/]\)[^\\/]*$|\1|';;
+ # Otherwise, we want the empty string (not ".").
+ esac
+}
+
+# guard FILE
+# ----------
+# The CPP macro used to guard inclusion of FILE.
+guard ()
+{
+ printf '%s\n' "$1" \
+ | sed \
+ -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' \
+ -e 's/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ]/_/g' \
+ -e 's/__*/_/g'
+}
+
+# quote_for_sed [STRING]
+# ----------------------
+# Return STRING (or stdin) quoted to be used as a sed pattern.
+quote_for_sed ()
+{
+ case $# in
+ 0) cat;;
+ 1) printf '%s\n' "$1";;
+ esac \
+ | sed -e 's|[][\\.*]|\\&|g'
+}
+
+case "$1" in
+ '')
+ echo "$0: No files given. Try '$0 --help' for more information." 1>&2
+ exit 1
+ ;;
+ --basedir)
+ basedir=$2
+ shift 2
+ ;;
+ -h|--h*)
+ cat <<\EOF
+Usage: ylwrap [--help|--version] INPUT [OUTPUT DESIRED]... -- PROGRAM [ARGS]...
+
+Wrapper for lex/yacc invocations, renaming files as desired.
+
+ INPUT is the input file
+ OUTPUT is one file PROG generates
+ DESIRED is the file we actually want instead of OUTPUT
+ PROGRAM is program to run
+ ARGS are passed to PROG
+
+Any number of OUTPUT,DESIRED pairs may be used.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+ exit $?
+ ;;
+ -v|--v*)
+ echo "ylwrap $scriptversion"
+ exit $?
+ ;;
+esac
+
+
+# The input.
+input=$1
+shift
+# We'll later need for a correct munging of "#line" directives.
+input_sub_rx=`get_dirname "$input" | quote_for_sed`
+case $input in
+ [\\/]* | ?:[\\/]*)
+ # Absolute path; do nothing.
+ ;;
+ *)
+ # Relative path. Make it absolute.
+ input=`pwd`/$input
+ ;;
+esac
+input_rx=`get_dirname "$input" | quote_for_sed`
+
+# Since DOS filename conventions don't allow two dots,
+# the DOS version of Bison writes out y_tab.c instead of y.tab.c
+# and y_tab.h instead of y.tab.h. Test to see if this is the case.
+y_tab_nodot=false
+if test -f y_tab.c || test -f y_tab.h; then
+ y_tab_nodot=true
+fi
+
+# The parser itself, the first file, is the destination of the .y.c
+# rule in the Makefile.
+parser=$1
+
+# A sed program to s/FROM/TO/g for all the FROM/TO so that, for
+# instance, we rename #include "y.tab.h" into #include "parse.h"
+# during the conversion from y.tab.c to parse.c.
+sed_fix_filenames=
+
+# Also rename header guards, as Bison 2.7 for instance uses its header
+# guard in its implementation file.
+sed_fix_header_guards=
+
+while test $# -ne 0; do
+ if test x"$1" = x"--"; then
+ shift
+ break
+ fi
+ from=$1
+ # Handle y_tab.c and y_tab.h output by DOS
+ if $y_tab_nodot; then
+ case $from in
+ "y.tab.c") from=y_tab.c;;
+ "y.tab.h") from=y_tab.h;;
+ esac
+ fi
+ shift
+ to=$1
+ shift
+ sed_fix_filenames="${sed_fix_filenames}s|"`quote_for_sed "$from"`"|$to|g;"
+ sed_fix_header_guards="${sed_fix_header_guards}s|"`guard "$from"`"|"`guard "$to"`"|g;"
+done
+
+# The program to run.
+prog=$1
+shift
+# Make any relative path in $prog absolute.
+case $prog in
+ [\\/]* | ?:[\\/]*) ;;
+ *[\\/]*) prog=`pwd`/$prog ;;
+esac
+
+dirname=ylwrap$$
+do_exit="cd '`pwd`' && rm -rf $dirname > /dev/null 2>&1;"' (exit $ret); exit $ret'
+trap "ret=129; $do_exit" 1
+trap "ret=130; $do_exit" 2
+trap "ret=141; $do_exit" 13
+trap "ret=143; $do_exit" 15
+mkdir $dirname || exit 1
+
+cd $dirname
+
+case $# in
+ 0) "$prog" "$input" ;;
+ *) "$prog" "$@" "$input" ;;
+esac
+ret=$?
+
+if test $ret -eq 0; then
+ for from in *
+ do
+ to=`printf '%s\n' "$from" | sed "$sed_fix_filenames"`
+ if test -f "$from"; then
+ # If $2 is an absolute path name, then just use that,
+ # otherwise prepend '../'.
+ case $to in
+ [\\/]* | ?:[\\/]*) target=$to;;
+ *) target=../$to;;
+ esac
+
+ # Do not overwrite unchanged header files to avoid useless
+ # recompilations. Always update the parser itself: it is the
+ # destination of the .y.c rule in the Makefile. Divert the
+ # output of all other files to a temporary file so we can
+ # compare them to existing versions.
+ if test $from != $parser; then
+ realtarget=$target
+ target=tmp-`printf '%s\n' "$target" | sed 's|.*[\\/]||g'`
+ fi
+
+ # Munge "#line" or "#" directives. Don't let the resulting
+ # debug information point at an absolute srcdir. Use the real
+ # output file name, not yy.lex.c for instance. Adjust the
+ # include guards too.
+ sed -e "/^#/!b" \
+ -e "s|$input_rx|$input_sub_rx|" \
+ -e "$sed_fix_filenames" \
+ -e "$sed_fix_header_guards" \
+ "$from" >"$target" || ret=$?
+
+ # Check whether files must be updated.
+ if test "$from" != "$parser"; then
+ if test -f "$realtarget" && cmp -s "$realtarget" "$target"; then
+ echo "$to is unchanged"
+ rm -f "$target"
+ else
+ echo "updating $to"
+ mv -f "$target" "$realtarget"
+ fi
+ fi
+ else
+ # A missing file is only an error for the parser. This is a
+ # blatant hack to let us support using "yacc -d". If -d is not
+ # specified, don't fail when the header file is "missing".
+ if test "$from" = "$parser"; then
+ ret=1
+ fi
+ fi
+ done
+fi
+
+# Remove the directory.
+cd ..
+rm -rf $dirname
+
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/example_tmux.conf b/example_tmux.conf
new file mode 100644
index 0000000..1bd9afd
--- /dev/null
+++ b/example_tmux.conf
@@ -0,0 +1,71 @@
+#
+# Example .tmux.conf
+#
+# By Nicholas Marriott. Public domain.
+#
+
+# Some tweaks to the status line
+set -g status-right "%H:%M"
+set -g window-status-current-style "underscore"
+
+# If running inside tmux ($TMUX is set), then change the status line to red
+%if #{TMUX}
+set -g status-bg red
+%endif
+
+# Enable RGB colour if running in xterm(1)
+set-option -sa terminal-overrides ",xterm*:Tc"
+
+# Change the default $TERM to tmux-256color
+set -g default-terminal "tmux-256color"
+
+# No bells at all
+set -g bell-action none
+
+# Keep windows around after they exit
+set -g remain-on-exit on
+
+# Change the prefix key to C-a
+set -g prefix C-a
+unbind C-b
+bind C-a send-prefix
+
+# Turn the mouse on, but without copy mode dragging
+set -g mouse on
+unbind -n MouseDrag1Pane
+unbind -Tcopy-mode MouseDrag1Pane
+
+# Some extra key bindings to select higher numbered windows
+bind F1 selectw -t:10
+bind F2 selectw -t:11
+bind F3 selectw -t:12
+bind F4 selectw -t:13
+bind F5 selectw -t:14
+bind F6 selectw -t:15
+bind F7 selectw -t:16
+bind F8 selectw -t:17
+bind F9 selectw -t:18
+bind F10 selectw -t:19
+bind F11 selectw -t:20
+bind F12 selectw -t:21
+
+# A key to toggle between smallest and largest sizes if a window is visible in
+# multiple places
+bind F set -w window-size
+
+# Keys to toggle monitoring activity in a window and the synchronize-panes option
+bind m set monitor-activity
+bind y set synchronize-panes\; display 'synchronize-panes #{?synchronize-panes,on,off}'
+
+# Create a single default session - because a session is created here, tmux
+# should be started with "tmux attach" rather than "tmux new"
+new -d -s0 -nirssi 'exec irssi'
+set -t0:0 monitor-activity on
+set -t0:0 aggressive-resize on
+neww -d -ntodo 'exec emacs ~/TODO'
+setw -t0:1 aggressive-resize on
+neww -d -nmutt 'exec mutt'
+setw -t0:2 aggressive-resize on
+neww -d
+neww -d
+neww -d
diff --git a/file.c b/file.c
new file mode 100644
index 0000000..b2f155f
--- /dev/null
+++ b/file.c
@@ -0,0 +1,819 @@
+/* $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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * IPC file handling. Both client and server use the same data structures
+ * (client_file and client_files) to store list of active files. Most functions
+ * are for use either in client or server but not both.
+ */
+
+static int file_next_stream = 3;
+
+RB_GENERATE(client_files, client_file, entry, file_cmp);
+
+/* Get path for file, either as given or from working directory. */
+static char *
+file_get_path(struct client *c, const char *file)
+{
+ char *path;
+
+ if (*file == '/')
+ path = xstrdup(file);
+ else
+ xasprintf(&path, "%s/%s", server_client_get_cwd(c, NULL), file);
+ return (path);
+}
+
+/* Tree comparison function. */
+int
+file_cmp(struct client_file *cf1, struct client_file *cf2)
+{
+ if (cf1->stream < cf2->stream)
+ return (-1);
+ if (cf1->stream > cf2->stream)
+ return (1);
+ return (0);
+}
+
+/*
+ * Create a file object in the client process - the peer is the server to send
+ * messages to. Check callback is fired when the file is finished with so the
+ * process can decide if it needs to exit (if it is waiting for files to
+ * flush).
+ */
+struct client_file *
+file_create_with_peer(struct tmuxpeer *peer, struct client_files *files,
+ int stream, client_file_cb cb, void *cbdata)
+{
+ struct client_file *cf;
+
+ cf = xcalloc(1, sizeof *cf);
+ cf->c = NULL;
+ cf->references = 1;
+ cf->stream = stream;
+
+ cf->buffer = evbuffer_new();
+ if (cf->buffer == NULL)
+ fatalx("out of memory");
+
+ cf->cb = cb;
+ cf->data = cbdata;
+
+ cf->peer = peer;
+ cf->tree = files;
+ RB_INSERT(client_files, files, cf);
+
+ return (cf);
+}
+
+/* Create a file object in the server, communicating with the given client. */
+struct client_file *
+file_create_with_client(struct client *c, int stream, client_file_cb cb,
+ void *cbdata)
+{
+ struct client_file *cf;
+
+ if (c != NULL && (c->flags & CLIENT_ATTACHED))
+ c = NULL;
+
+ cf = xcalloc(1, sizeof *cf);
+ cf->c = c;
+ cf->references = 1;
+ cf->stream = stream;
+
+ cf->buffer = evbuffer_new();
+ if (cf->buffer == NULL)
+ fatalx("out of memory");
+
+ cf->cb = cb;
+ cf->data = cbdata;
+
+ if (cf->c != NULL) {
+ cf->peer = cf->c->peer;
+ cf->tree = &cf->c->files;
+ RB_INSERT(client_files, &cf->c->files, cf);
+ cf->c->references++;
+ }
+
+ return (cf);
+}
+
+/* Free a file. */
+void
+file_free(struct client_file *cf)
+{
+ if (--cf->references != 0)
+ return;
+
+ evbuffer_free(cf->buffer);
+ free(cf->path);
+
+ if (cf->tree != NULL)
+ RB_REMOVE(client_files, cf->tree, cf);
+ if (cf->c != NULL)
+ server_client_unref(cf->c);
+
+ free(cf);
+}
+
+/* Event to fire the done callback. */
+static void
+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)))
+ cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data);
+ file_free(cf);
+}
+
+/* Add an event to fire the done callback (used by the server). */
+void
+file_fire_done(struct client_file *cf)
+{
+ event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL);
+}
+
+/* Fire the read callback. */
+void
+file_fire_read(struct client_file *cf)
+{
+ if (cf->cb != NULL)
+ cf->cb(cf->c, cf->path, cf->error, 0, cf->buffer, cf->data);
+}
+
+/* Can this file be printed to? */
+int
+file_can_print(struct client *c)
+{
+ if (c == NULL)
+ return (0);
+ if (c->session != NULL && (~c->flags & CLIENT_CONTROL))
+ return (0);
+ return (1);
+}
+
+/* Print a message to a file. */
+void
+file_print(struct client *c, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ file_vprint(c, fmt, ap);
+ va_end(ap);
+}
+
+/* Print a message to a file. */
+void
+file_vprint(struct client *c, const char *fmt, va_list ap)
+{
+ struct client_file find, *cf;
+ struct msg_write_open msg;
+
+ if (!file_can_print(c))
+ return;
+
+ find.stream = 1;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
+ cf = file_create_with_client(c, 1, NULL, NULL);
+ cf->path = xstrdup("-");
+
+ evbuffer_add_vprintf(cf->buffer, fmt, ap);
+
+ msg.stream = 1;
+ msg.fd = STDOUT_FILENO;
+ msg.flags = 0;
+ proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
+ } else {
+ evbuffer_add_vprintf(cf->buffer, fmt, ap);
+ file_push(cf);
+ }
+}
+
+/* Print a buffer to a file. */
+void
+file_print_buffer(struct client *c, void *data, size_t size)
+{
+ struct client_file find, *cf;
+ struct msg_write_open msg;
+
+ if (!file_can_print(c))
+ return;
+
+ find.stream = 1;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
+ cf = file_create_with_client(c, 1, NULL, NULL);
+ cf->path = xstrdup("-");
+
+ evbuffer_add(cf->buffer, data, size);
+
+ msg.stream = 1;
+ msg.fd = STDOUT_FILENO;
+ msg.flags = 0;
+ proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
+ } else {
+ evbuffer_add(cf->buffer, data, size);
+ file_push(cf);
+ }
+}
+
+/* Report an error to a file. */
+void
+file_error(struct client *c, const char *fmt, ...)
+{
+ struct client_file find, *cf;
+ struct msg_write_open msg;
+ va_list ap;
+
+ if (!file_can_print(c))
+ return;
+
+ va_start(ap, fmt);
+
+ find.stream = 2;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
+ cf = file_create_with_client(c, 2, NULL, NULL);
+ cf->path = xstrdup("-");
+
+ evbuffer_add_vprintf(cf->buffer, fmt, ap);
+
+ msg.stream = 2;
+ msg.fd = STDERR_FILENO;
+ msg.flags = 0;
+ proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
+ } else {
+ evbuffer_add_vprintf(cf->buffer, fmt, ap);
+ file_push(cf);
+ }
+
+ va_end(ap);
+}
+
+/* Write data to a file. */
+void
+file_write(struct client *c, const char *path, int flags, const void *bdata,
+ size_t bsize, client_file_cb cb, void *cbdata)
+{
+ struct client_file *cf;
+ struct msg_write_open *msg;
+ size_t msglen;
+ int fd = -1;
+ u_int stream = file_next_stream++;
+ FILE *f;
+ const char *mode;
+
+ if (strcmp(path, "-") == 0) {
+ cf = file_create_with_client(c, stream, cb, cbdata);
+ cf->path = xstrdup("-");
+
+ fd = STDOUT_FILENO;
+ if (c == NULL ||
+ (c->flags & CLIENT_ATTACHED) ||
+ (c->flags & CLIENT_CONTROL)) {
+ cf->error = EBADF;
+ goto done;
+ }
+ goto skip;
+ }
+
+ cf = file_create_with_client(c, stream, cb, cbdata);
+ cf->path = file_get_path(c, path);
+
+ if (c == NULL || c->flags & CLIENT_ATTACHED) {
+ if (flags & O_APPEND)
+ mode = "ab";
+ else
+ mode = "wb";
+ f = fopen(cf->path, mode);
+ if (f == NULL) {
+ cf->error = errno;
+ goto done;
+ }
+ if (fwrite(bdata, 1, bsize, f) != bsize) {
+ fclose(f);
+ cf->error = EIO;
+ goto done;
+ }
+ fclose(f);
+ goto done;
+ }
+
+skip:
+ evbuffer_add(cf->buffer, bdata, bsize);
+
+ msglen = strlen(cf->path) + 1 + sizeof *msg;
+ if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+ cf->error = E2BIG;
+ goto done;
+ }
+ msg = xmalloc(msglen);
+ msg->stream = cf->stream;
+ msg->fd = fd;
+ msg->flags = flags;
+ memcpy(msg + 1, cf->path, msglen - sizeof *msg);
+ if (proc_send(cf->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) {
+ free(msg);
+ cf->error = EINVAL;
+ goto done;
+ }
+ free(msg);
+ return;
+
+done:
+ file_fire_done(cf);
+}
+
+/* Read a file. */
+void
+file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata)
+{
+ struct client_file *cf;
+ struct msg_read_open *msg;
+ size_t msglen;
+ int fd = -1;
+ u_int stream = file_next_stream++;
+ FILE *f;
+ size_t size;
+ char buffer[BUFSIZ];
+
+ if (strcmp(path, "-") == 0) {
+ cf = file_create_with_client(c, stream, cb, cbdata);
+ cf->path = xstrdup("-");
+
+ fd = STDIN_FILENO;
+ if (c == NULL ||
+ (c->flags & CLIENT_ATTACHED) ||
+ (c->flags & CLIENT_CONTROL)) {
+ cf->error = EBADF;
+ goto done;
+ }
+ goto skip;
+ }
+
+ cf = file_create_with_client(c, stream, cb, cbdata);
+ cf->path = file_get_path(c, path);
+
+ if (c == NULL || c->flags & CLIENT_ATTACHED) {
+ f = fopen(cf->path, "rb");
+ if (f == NULL) {
+ cf->error = errno;
+ goto done;
+ }
+ for (;;) {
+ size = fread(buffer, 1, sizeof buffer, f);
+ if (evbuffer_add(cf->buffer, buffer, size) != 0) {
+ cf->error = ENOMEM;
+ goto done;
+ }
+ if (size != sizeof buffer)
+ break;
+ }
+ if (ferror(f)) {
+ cf->error = EIO;
+ goto done;
+ }
+ fclose(f);
+ goto done;
+ }
+
+skip:
+ msglen = strlen(cf->path) + 1 + sizeof *msg;
+ if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+ cf->error = E2BIG;
+ goto done;
+ }
+ msg = xmalloc(msglen);
+ msg->stream = cf->stream;
+ msg->fd = fd;
+ memcpy(msg + 1, cf->path, msglen - sizeof *msg);
+ if (proc_send(cf->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) {
+ free(msg);
+ cf->error = EINVAL;
+ goto done;
+ }
+ free(msg);
+ return;
+
+done:
+ file_fire_done(cf);
+}
+
+/* Push event, fired if there is more writing to be done. */
+static void
+file_push_cb(__unused int fd, __unused short events, void *arg)
+{
+ struct client_file *cf = arg;
+
+ if (cf->c == NULL || ~cf->c->flags & CLIENT_DEAD)
+ file_push(cf);
+ file_free(cf);
+}
+
+/* Push uwritten data to the client for a file, if it will accept it. */
+void
+file_push(struct client_file *cf)
+{
+ struct msg_write_data *msg;
+ size_t msglen, sent, left;
+ struct msg_write_close close;
+
+ msg = xmalloc(sizeof *msg);
+ left = EVBUFFER_LENGTH(cf->buffer);
+ while (left != 0) {
+ sent = left;
+ if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg)
+ sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg;
+
+ msglen = (sizeof *msg) + sent;
+ msg = xrealloc(msg, msglen);
+ msg->stream = cf->stream;
+ memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent);
+ if (proc_send(cf->peer, MSG_WRITE, -1, msg, msglen) != 0)
+ break;
+ evbuffer_drain(cf->buffer, sent);
+
+ left = EVBUFFER_LENGTH(cf->buffer);
+ log_debug("file %d sent %zu, left %zu", cf->stream, sent, left);
+ }
+ if (left != 0) {
+ cf->references++;
+ event_once(-1, EV_TIMEOUT, file_push_cb, cf, NULL);
+ } else if (cf->stream > 2) {
+ close.stream = cf->stream;
+ proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
+ file_fire_done(cf);
+ }
+ free(msg);
+}
+
+/* Check if any files have data left to write. */
+int
+file_write_left(struct client_files *files)
+{
+ struct client_file *cf;
+ size_t left;
+ int waiting = 0;
+
+ RB_FOREACH(cf, client_files, files) {
+ if (cf->event == NULL)
+ continue;
+ left = EVBUFFER_LENGTH(cf->event->output);
+ if (left != 0) {
+ waiting++;
+ log_debug("file %u %zu bytes left", cf->stream, left);
+ }
+ }
+ return (waiting != 0);
+}
+
+/* Client file write error callback. */
+static void
+file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
+ void *arg)
+{
+ struct client_file *cf = arg;
+
+ log_debug("write error file %d", cf->stream);
+
+ bufferevent_free(cf->event);
+ cf->event = NULL;
+
+ close(cf->fd);
+ cf->fd = -1;
+
+ if (cf->cb != NULL)
+ cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
+}
+
+/* Client file write callback. */
+static void
+file_write_callback(__unused struct bufferevent *bev, void *arg)
+{
+ struct client_file *cf = arg;
+
+ log_debug("write check file %d", cf->stream);
+
+ if (cf->cb != NULL)
+ cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
+
+ if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
+ bufferevent_free(cf->event);
+ close(cf->fd);
+ RB_REMOVE(client_files, cf->tree, cf);
+ file_free(cf);
+ }
+}
+
+/* Handle a file write open message (client). */
+void
+file_write_open(struct client_files *files, struct tmuxpeer *peer,
+ struct imsg *imsg, int allow_streams, int close_received,
+ client_file_cb cb, void *cbdata)
+{
+ struct msg_write_open *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ const char *path;
+ struct msg_write_ready reply;
+ struct client_file find, *cf;
+ const int flags = O_NONBLOCK|O_WRONLY|O_CREAT;
+ int error = 0;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_WRITE_OPEN size");
+ if (msglen == sizeof *msg)
+ path = "-";
+ else
+ path = (const char *)(msg + 1);
+ log_debug("open write file %d %s", msg->stream, path);
+
+ find.stream = msg->stream;
+ if (RB_FIND(client_files, files, &find) != NULL) {
+ error = EBADF;
+ goto reply;
+ }
+ cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata);
+ if (cf->closed) {
+ error = EBADF;
+ goto reply;
+ }
+
+ cf->fd = -1;
+ if (msg->fd == -1)
+ cf->fd = open(path, msg->flags|flags, 0644);
+ else if (allow_streams) {
+ if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO)
+ errno = EBADF;
+ else {
+ cf->fd = dup(msg->fd);
+ if (close_received)
+ close(msg->fd); /* can only be used once */
+ }
+ } else
+ errno = EBADF;
+ if (cf->fd == -1) {
+ error = errno;
+ goto reply;
+ }
+
+ cf->event = bufferevent_new(cf->fd, NULL, file_write_callback,
+ file_write_error_callback, cf);
+ bufferevent_enable(cf->event, EV_WRITE);
+ goto reply;
+
+reply:
+ reply.stream = msg->stream;
+ reply.error = error;
+ proc_send(peer, MSG_WRITE_READY, -1, &reply, sizeof reply);
+}
+
+/* Handle a file write data message (client). */
+void
+file_write_data(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_write_data *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+ size_t size = msglen - sizeof *msg;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_WRITE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ fatalx("unknown stream number");
+ log_debug("write %zu to file %d", size, cf->stream);
+
+ if (cf->event != NULL)
+ bufferevent_write(cf->event, msg + 1, size);
+}
+
+/* Handle a file write close message (client). */
+void
+file_write_close(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_write_close *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+
+ if (msglen != sizeof *msg)
+ fatalx("bad MSG_WRITE_CLOSE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ fatalx("unknown stream number");
+ log_debug("close file %d", cf->stream);
+
+ if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
+ if (cf->event != NULL)
+ bufferevent_free(cf->event);
+ if (cf->fd != -1)
+ close(cf->fd);
+ RB_REMOVE(client_files, files, cf);
+ file_free(cf);
+ }
+}
+
+/* Client file read error callback. */
+static void
+file_read_error_callback(__unused struct bufferevent *bev, __unused short what,
+ void *arg)
+{
+ struct client_file *cf = arg;
+ struct msg_read_done msg;
+
+ log_debug("read error file %d", cf->stream);
+
+ msg.stream = cf->stream;
+ msg.error = 0;
+ proc_send(cf->peer, MSG_READ_DONE, -1, &msg, sizeof msg);
+
+ bufferevent_free(cf->event);
+ close(cf->fd);
+ RB_REMOVE(client_files, cf->tree, cf);
+ file_free(cf);
+}
+
+/* Client file read callback. */
+static void
+file_read_callback(__unused struct bufferevent *bev, void *arg)
+{
+ struct client_file *cf = arg;
+ void *bdata;
+ size_t bsize;
+ struct msg_read_data *msg;
+ size_t msglen;
+
+ msg = xmalloc(sizeof *msg);
+ for (;;) {
+ bdata = EVBUFFER_DATA(cf->event->input);
+ bsize = EVBUFFER_LENGTH(cf->event->input);
+
+ if (bsize == 0)
+ break;
+ if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg)
+ bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg;
+ log_debug("read %zu from file %d", bsize, cf->stream);
+
+ msglen = (sizeof *msg) + bsize;
+ msg = xrealloc(msg, msglen);
+ msg->stream = cf->stream;
+ memcpy(msg + 1, bdata, bsize);
+ proc_send(cf->peer, MSG_READ, -1, msg, msglen);
+
+ evbuffer_drain(cf->event->input, bsize);
+ }
+ free(msg);
+}
+
+/* Handle a file read open message (client). */
+void
+file_read_open(struct client_files *files, struct tmuxpeer *peer,
+ struct imsg *imsg, int allow_streams, int close_received, client_file_cb cb,
+ void *cbdata)
+{
+ struct msg_read_open *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ const char *path;
+ struct msg_read_done reply;
+ struct client_file find, *cf;
+ const int flags = O_NONBLOCK|O_RDONLY;
+ int error;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_READ_OPEN size");
+ if (msglen == sizeof *msg)
+ path = "-";
+ else
+ path = (const char *)(msg + 1);
+ log_debug("open read file %d %s", msg->stream, path);
+
+ find.stream = msg->stream;
+ if (RB_FIND(client_files, files, &find) != NULL) {
+ error = EBADF;
+ goto reply;
+ }
+ cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata);
+ if (cf->closed) {
+ error = EBADF;
+ goto reply;
+ }
+
+ cf->fd = -1;
+ if (msg->fd == -1)
+ cf->fd = open(path, flags);
+ else if (allow_streams) {
+ if (msg->fd != STDIN_FILENO)
+ errno = EBADF;
+ else {
+ cf->fd = dup(msg->fd);
+ if (close_received)
+ close(msg->fd); /* can only be used once */
+ }
+ } else
+ errno = EBADF;
+ if (cf->fd == -1) {
+ error = errno;
+ goto reply;
+ }
+
+ cf->event = bufferevent_new(cf->fd, file_read_callback, NULL,
+ file_read_error_callback, cf);
+ bufferevent_enable(cf->event, EV_READ);
+ return;
+
+reply:
+ reply.stream = msg->stream;
+ reply.error = error;
+ proc_send(peer, MSG_READ_DONE, -1, &reply, sizeof reply);
+}
+
+/* Handle a write ready message (server). */
+void
+file_write_ready(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_write_ready *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+
+ if (msglen != sizeof *msg)
+ fatalx("bad MSG_WRITE_READY size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ return;
+ if (msg->error != 0) {
+ cf->error = msg->error;
+ file_fire_done(cf);
+ } else
+ file_push(cf);
+}
+
+/* Handle read data message (server). */
+void
+file_read_data(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_read_data *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+ void *bdata = msg + 1;
+ size_t bsize = msglen - sizeof *msg;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_READ_DATA size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ return;
+
+ log_debug("file %d read %zu bytes", cf->stream, bsize);
+ if (cf->error == 0) {
+ if (evbuffer_add(cf->buffer, bdata, bsize) != 0) {
+ cf->error = ENOMEM;
+ file_fire_done(cf);
+ } else
+ file_fire_read(cf);
+ }
+}
+
+/* Handle a read done message (server). */
+void
+file_read_done(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_read_done *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_DONE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ return;
+
+ log_debug("file %d read done", cf->stream);
+ cf->error = msg->error;
+ file_fire_done(cf);
+}
diff --git a/format-draw.c b/format-draw.c
new file mode 100644
index 0000000..1a7e60b
--- /dev/null
+++ b/format-draw.c
@@ -0,0 +1,1205 @@
+/* $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"
+
+/* Format range. */
+struct format_range {
+ u_int index;
+ struct screen *s;
+
+ u_int start;
+ u_int end;
+
+ enum style_range_type type;
+ u_int argument;
+
+ TAILQ_ENTRY(format_range) entry;
+};
+TAILQ_HEAD(format_ranges, format_range);
+
+/* Does this range match this style? */
+static int
+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);
+ return (1);
+}
+
+/* Free a range. */
+static void
+format_free_range(struct format_ranges *frs, struct format_range *fr)
+{
+ TAILQ_REMOVE(frs, fr, entry);
+ free(fr);
+}
+
+/* Fix range positions. */
+static void
+format_update_ranges(struct format_ranges *frs, struct screen *s, u_int offset,
+ u_int start, u_int width)
+{
+ struct format_range *fr, *fr1;
+
+ if (frs == NULL)
+ return;
+
+ TAILQ_FOREACH_SAFE(fr, frs, entry, fr1) {
+ if (fr->s != s)
+ continue;
+
+ if (fr->end <= start || fr->start >= start + width) {
+ format_free_range(frs, fr);
+ continue;
+ }
+
+ if (fr->start < start)
+ fr->start = start;
+ if (fr->end > start + width)
+ fr->end = start + width;
+ if (fr->start == fr->end) {
+ format_free_range(frs, fr);
+ continue;
+ }
+
+ fr->start -= start;
+ fr->end -= start;
+
+ fr->start += offset;
+ fr->end += offset;
+ }
+}
+
+/* Draw a part of the format. */
+static void
+format_draw_put(struct screen_write_ctx *octx, u_int ocx, u_int ocy,
+ struct screen *s, struct format_ranges *frs, u_int offset, u_int start,
+ u_int width)
+{
+ /*
+ * The offset is how far from the cursor on the target screen; start
+ * and width how much to copy from the source screen.
+ */
+ screen_write_cursormove(octx, ocx + offset, ocy, 0);
+ screen_write_fast_copy(octx, s, start, 0, width, 1);
+ format_update_ranges(frs, s, offset, start, width);
+}
+
+/* Draw list part of format. */
+static void
+format_draw_put_list(struct screen_write_ctx *octx,
+ u_int ocx, u_int ocy, u_int offset, u_int width, struct screen *list,
+ struct screen *list_left, struct screen *list_right, int focus_start,
+ int focus_end, struct format_ranges *frs)
+{
+ u_int start, focus_centre;
+
+ /* If there is enough space for the list, draw it entirely. */
+ if (width >= list->cx) {
+ format_draw_put(octx, ocx, ocy, list, frs, offset, 0, width);
+ return;
+ }
+
+ /* The list needs to be trimmed. Try to keep the focus visible. */
+ focus_centre = focus_start + (focus_end - focus_start) / 2;
+ if (focus_centre < width / 2)
+ start = 0;
+ else
+ start = focus_centre - width / 2;
+ if (start + width > list->cx)
+ start = list->cx - width;
+
+ /* Draw <> markers at either side if needed. */
+ if (start != 0 && width > list_left->cx) {
+ screen_write_cursormove(octx, ocx + offset, ocy, 0);
+ screen_write_fast_copy(octx, list_left, 0, 0, list_left->cx, 1);
+ offset += list_left->cx;
+ start += list_left->cx;
+ width -= list_left->cx;
+ }
+ if (start + width < list->cx && width > list_right->cx) {
+ screen_write_cursormove(octx, ocx + offset + width -
+ list_right->cx, ocy, 0);
+ screen_write_fast_copy(octx, list_right, 0, 0, list_right->cx,
+ 1);
+ width -= list_right->cx;
+ }
+
+ /* Draw the list screen itself. */
+ format_draw_put(octx, ocx, ocy, list, frs, offset, start, width);
+}
+
+/* Draw format with no list. */
+static void
+format_draw_none(struct screen_write_ctx *octx, u_int available, u_int ocx,
+ u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
+ struct screen *abs_centre, struct format_ranges *frs)
+{
+ u_int width_left, width_centre, width_right, width_abs_centre;
+
+ width_left = left->cx;
+ width_centre = centre->cx;
+ width_right = right->cx;
+ width_abs_centre = abs_centre->cx;
+
+ /*
+ * Try to keep as much of the left and right as possible at the expense
+ * of the centre.
+ */
+ while (width_left + width_centre + width_right > available) {
+ if (width_centre > 0)
+ width_centre--;
+ else if (width_right > 0)
+ width_right--;
+ else
+ width_left--;
+ }
+
+ /* Write left. */
+ format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
+
+ /* Write right at available - width_right. */
+ format_draw_put(octx, ocx, ocy, right, frs,
+ available - width_right,
+ right->cx - width_right,
+ width_right);
+
+ /*
+ * Write centre halfway between
+ * width_left
+ * and
+ * available - width_right.
+ */
+ format_draw_put(octx, ocx, ocy, centre, frs,
+ width_left
+ + ((available - width_right) - width_left) / 2
+ - width_centre / 2,
+ centre->cx / 2 - width_centre / 2,
+ width_centre);
+
+ /*
+ * Write abs_centre in the perfect centre of all horizontal space.
+ */
+ if (width_abs_centre > available)
+ width_abs_centre = available;
+ format_draw_put(octx, ocx, ocy, abs_centre, frs,
+ (available - width_abs_centre) / 2,
+ 0,
+ width_abs_centre);
+}
+
+/* Draw format with list on the left. */
+static void
+format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx,
+ u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
+ struct screen *abs_centre, struct screen *list, struct screen *list_left,
+ struct screen *list_right, struct screen *after, int focus_start,
+ int focus_end, struct format_ranges *frs)
+{
+ u_int width_left, width_centre, width_right;
+ u_int width_list, width_after, width_abs_centre;
+ struct screen_write_ctx ctx;
+
+ width_left = left->cx;
+ width_centre = centre->cx;
+ width_right = right->cx;
+ width_abs_centre = abs_centre->cx;
+ width_list = list->cx;
+ width_after = after->cx;
+
+ /*
+ * Trim first the centre, then the list, then the right, then after the
+ * list, then the left.
+ */
+ while (width_left +
+ width_centre +
+ width_right +
+ width_list +
+ width_after > available) {
+ if (width_centre > 0)
+ width_centre--;
+ else if (width_list > 0)
+ width_list--;
+ else if (width_right > 0)
+ width_right--;
+ else if (width_after > 0)
+ width_after--;
+ else
+ width_left--;
+ }
+
+ /* If there is no list left, pass off to the no list function. */
+ if (width_list == 0) {
+ screen_write_start(&ctx, left);
+ screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
+ screen_write_stop(&ctx);
+
+ format_draw_none(octx, available, ocx, ocy, left, centre,
+ right, abs_centre, frs);
+ return;
+ }
+
+ /* Write left at 0. */
+ format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
+
+ /* Write right at available - width_right. */
+ format_draw_put(octx, ocx, ocy, right, frs,
+ available - width_right,
+ right->cx - width_right,
+ width_right);
+
+ /* Write after at width_left + width_list. */
+ format_draw_put(octx, ocx, ocy, after, frs,
+ width_left + width_list,
+ 0,
+ width_after);
+
+ /*
+ * Write centre halfway between
+ * width_left + width_list + width_after
+ * and
+ * available - width_right.
+ */
+ format_draw_put(octx, ocx, ocy, centre, frs,
+ (width_left + width_list + width_after)
+ + ((available - width_right)
+ - (width_left + width_list + width_after)) / 2
+ - width_centre / 2,
+ centre->cx / 2 - width_centre / 2,
+ width_centre);
+
+ /*
+ * The list now goes from
+ * width_left
+ * to
+ * width_left + width_list.
+ * If there is no focus given, keep the left in focus.
+ */
+ if (focus_start == -1 || focus_end == -1)
+ focus_start = focus_end = 0;
+ format_draw_put_list(octx, ocx, ocy, width_left, width_list, list,
+ list_left, list_right, focus_start, focus_end, frs);
+
+ /*
+ * Write abs_centre in the perfect centre of all horizontal space.
+ */
+ if (width_abs_centre > available)
+ width_abs_centre = available;
+ format_draw_put(octx, ocx, ocy, abs_centre, frs,
+ (available - width_abs_centre) / 2,
+ 0,
+ width_abs_centre);
+}
+
+/* Draw format with list in the centre. */
+static void
+format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx,
+ u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
+ struct screen *abs_centre, struct screen *list, struct screen *list_left,
+ struct screen *list_right, struct screen *after, int focus_start,
+ int focus_end, struct format_ranges *frs)
+{
+ u_int width_left, width_centre, width_right, middle;
+ u_int width_list, width_after, width_abs_centre;
+ struct screen_write_ctx ctx;
+
+ width_left = left->cx;
+ width_centre = centre->cx;
+ width_right = right->cx;
+ width_abs_centre = abs_centre->cx;
+ width_list = list->cx;
+ width_after = after->cx;
+
+ /*
+ * Trim first the list, then after the list, then the centre, then the
+ * right, then the left.
+ */
+ while (width_left +
+ width_centre +
+ width_right +
+ width_list +
+ width_after > available) {
+ if (width_list > 0)
+ width_list--;
+ else if (width_after > 0)
+ width_after--;
+ else if (width_centre > 0)
+ width_centre--;
+ else if (width_right > 0)
+ width_right--;
+ else
+ width_left--;
+ }
+
+ /* If there is no list left, pass off to the no list function. */
+ if (width_list == 0) {
+ screen_write_start(&ctx, centre);
+ screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
+ screen_write_stop(&ctx);
+
+ format_draw_none(octx, available, ocx, ocy, left, centre,
+ right, abs_centre, frs);
+ return;
+ }
+
+ /* Write left at 0. */
+ format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
+
+ /* Write right at available - width_right. */
+ format_draw_put(octx, ocx, ocy, right, frs,
+ available - width_right,
+ right->cx - width_right,
+ width_right);
+
+ /*
+ * All three centre sections are offset from the middle of the
+ * available space.
+ */
+ middle = (width_left + ((available - width_right) - width_left) / 2);
+
+ /*
+ * Write centre at
+ * middle - width_list / 2 - width_centre.
+ */
+ format_draw_put(octx, ocx, ocy, centre, frs,
+ middle - width_list / 2 - width_centre,
+ 0,
+ width_centre);
+
+ /*
+ * Write after at
+ * middle - width_list / 2 + width_list
+ */
+ format_draw_put(octx, ocx, ocy, after, frs,
+ middle - width_list / 2 + width_list,
+ 0,
+ width_after);
+
+ /*
+ * The list now goes from
+ * middle - width_list / 2
+ * to
+ * middle + width_list / 2
+ * If there is no focus given, keep the centre in focus.
+ */
+ if (focus_start == -1 || focus_end == -1)
+ focus_start = focus_end = list->cx / 2;
+ format_draw_put_list(octx, ocx, ocy, middle - width_list / 2,
+ width_list, list, list_left, list_right, focus_start, focus_end,
+ frs);
+
+ /*
+ * Write abs_centre in the perfect centre of all horizontal space.
+ */
+ if (width_abs_centre > available)
+ width_abs_centre = available;
+ format_draw_put(octx, ocx, ocy, abs_centre, frs,
+ (available - width_abs_centre) / 2,
+ 0,
+ width_abs_centre);
+}
+
+/* Draw format with list on the right. */
+static void
+format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx,
+ u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
+ struct screen *abs_centre, struct screen *list,
+ struct screen *list_left, struct screen *list_right, struct screen *after,
+ int focus_start, int focus_end, struct format_ranges *frs)
+{
+ u_int width_left, width_centre, width_right;
+ u_int width_list, width_after, width_abs_centre;
+ struct screen_write_ctx ctx;
+
+ width_left = left->cx;
+ width_centre = centre->cx;
+ width_right = right->cx;
+ width_abs_centre = abs_centre->cx;
+ width_list = list->cx;
+ width_after = after->cx;
+
+ /*
+ * Trim first the centre, then the list, then the right, then
+ * after the list, then the left.
+ */
+ while (width_left +
+ width_centre +
+ width_right +
+ width_list +
+ width_after > available) {
+ if (width_centre > 0)
+ width_centre--;
+ else if (width_list > 0)
+ width_list--;
+ else if (width_right > 0)
+ width_right--;
+ else if (width_after > 0)
+ width_after--;
+ else
+ width_left--;
+ }
+
+ /* If there is no list left, pass off to the no list function. */
+ if (width_list == 0) {
+ screen_write_start(&ctx, right);
+ screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
+ screen_write_stop(&ctx);
+
+ format_draw_none(octx, available, ocx, ocy, left, centre,
+ right, abs_centre, frs);
+ return;
+ }
+
+ /* Write left at 0. */
+ format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
+
+ /* Write after at available - width_after. */
+ format_draw_put(octx, ocx, ocy, after, frs,
+ available - width_after,
+ after->cx - width_after,
+ width_after);
+
+ /*
+ * Write right at
+ * available - width_right - width_list - width_after.
+ */
+ format_draw_put(octx, ocx, ocy, right, frs,
+ available - width_right - width_list - width_after,
+ 0,
+ width_right);
+
+ /*
+ * Write centre halfway between
+ * width_left
+ * and
+ * available - width_right - width_list - width_after.
+ */
+ format_draw_put(octx, ocx, ocy, centre, frs,
+ width_left
+ + ((available - width_right - width_list - width_after)
+ - width_left) / 2
+ - width_centre / 2,
+ centre->cx / 2 - width_centre / 2,
+ width_centre);
+
+ /*
+ * The list now goes from
+ * available - width_list - width_after
+ * to
+ * available - width_after
+ * If there is no focus given, keep the right in focus.
+ */
+ if (focus_start == -1 || focus_end == -1)
+ focus_start = focus_end = 0;
+ format_draw_put_list(octx, ocx, ocy, available - width_list -
+ width_after, width_list, list, list_left, list_right, focus_start,
+ focus_end, frs);
+
+ /*
+ * Write abs_centre in the perfect centre of all horizontal space.
+ */
+ if (width_abs_centre > available)
+ width_abs_centre = available;
+ format_draw_put(octx, ocx, ocy, abs_centre, frs,
+ (available - width_abs_centre) / 2,
+ 0,
+ width_abs_centre);
+}
+
+static void
+format_draw_absolute_centre(struct screen_write_ctx *octx, u_int available,
+ u_int ocx, u_int ocy, struct screen *left, struct screen *centre,
+ struct screen *right, struct screen *abs_centre, struct screen *list,
+ struct screen *list_left, struct screen *list_right, struct screen *after,
+ int focus_start, int focus_end, struct format_ranges *frs)
+{
+ u_int width_left, width_centre, width_right, width_abs_centre;
+ u_int width_list, width_after, middle, abs_centre_offset;
+
+ width_left = left->cx;
+ width_centre = centre->cx;
+ width_right = right->cx;
+ width_abs_centre = abs_centre->cx;
+ width_list = list->cx;
+ width_after = after->cx;
+
+ /*
+ * Trim first centre, then the right, then the left.
+ */
+ while (width_left +
+ width_centre +
+ width_right > available) {
+ if (width_centre > 0)
+ width_centre--;
+ else if (width_right > 0)
+ width_right--;
+ else
+ width_left--;
+ }
+
+ /*
+ * We trim list after and abs_centre independently, as we are drawing
+ * them over the rest. Trim first the list, then after the list, then
+ * abs_centre.
+ */
+ while (width_list + width_after + width_abs_centre > available) {
+ if (width_list > 0)
+ width_list--;
+ else if (width_after > 0)
+ width_after--;
+ else
+ width_abs_centre--;
+ }
+
+ /* Write left at 0. */
+ format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
+
+ /* Write right at available - width_right. */
+ format_draw_put(octx, ocx, ocy, right, frs,
+ available - width_right,
+ right->cx - width_right,
+ width_right);
+
+ /*
+ * Keep writing centre at the relative centre. Only the list is written
+ * in the absolute centre of the horizontal space.
+ */
+ middle = (width_left + ((available - width_right) - width_left) / 2);
+
+ /*
+ * Write centre at
+ * middle - width_centre.
+ */
+ format_draw_put(octx, ocx, ocy, centre, frs,
+ middle - width_centre,
+ 0,
+ width_centre);
+
+ /*
+ * If there is no focus given, keep the centre in focus.
+ */
+ if (focus_start == -1 || focus_end == -1)
+ focus_start = focus_end = list->cx / 2;
+
+ /*
+ * We centre abs_centre and the list together, so their shared centre is
+ * in the perfect centre of horizontal space.
+ */
+ abs_centre_offset = (available - width_list - width_abs_centre) / 2;
+
+ /*
+ * Write abs_centre before the list.
+ */
+ format_draw_put(octx, ocx, ocy, abs_centre, frs, abs_centre_offset,
+ 0, width_abs_centre);
+ abs_centre_offset += width_abs_centre;
+
+ /*
+ * Draw the list in the absolute centre
+ */
+ format_draw_put_list(octx, ocx, ocy, abs_centre_offset, width_list,
+ list, list_left, list_right, focus_start, focus_end, frs);
+ abs_centre_offset += width_list;
+
+ /*
+ * Write after at the end of the centre
+ */
+ format_draw_put(octx, ocx, ocy, after, frs, abs_centre_offset, 0,
+ width_after);
+}
+
+/* Get width and count of any leading #s. */
+static const char *
+format_leading_hashes(const char *cp, u_int *n, u_int *width)
+{
+ for (*n = 0; cp[*n] == '#'; (*n)++)
+ /* nothing */;
+ if (*n == 0) {
+ *width = 0;
+ return (cp);
+ }
+ if (cp[*n] != '[') {
+ if ((*n % 2) == 0)
+ *width = (*n / 2);
+ else
+ *width = (*n / 2) + 1;
+ return (cp + *n);
+ }
+ *width = (*n / 2);
+ if ((*n % 2) == 0) {
+ /*
+ * An even number of #s means that all #s are escaped, so not a
+ * style. The caller should not skip this. Return pointing to
+ * the [.
+ */
+ return (cp + *n);
+ }
+ /* This is a style, so return pointing to the #. */
+ return (cp + *n - 1);
+}
+
+/* Draw multiple characters. */
+static void
+format_draw_many(struct screen_write_ctx *ctx, struct style *sy, char ch,
+ u_int n)
+{
+ u_int i;
+
+ utf8_set(&sy->gc.data, ch);
+ for (i = 0; i < n; i++)
+ screen_write_cell(ctx, &sy->gc);
+}
+
+/* Draw a format to a screen. */
+void
+format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
+ u_int available, const char *expanded, struct style_ranges *srs,
+ int default_colours)
+{
+ enum { LEFT,
+ CENTRE,
+ RIGHT,
+ ABSOLUTE_CENTRE,
+ LIST,
+ LIST_LEFT,
+ LIST_RIGHT,
+ AFTER,
+ TOTAL } current = LEFT, last = LEFT;
+ const char *names[] = { "LEFT",
+ "CENTRE",
+ "RIGHT",
+ "ABSOLUTE_CENTRE",
+ "LIST",
+ "LIST_LEFT",
+ "LIST_RIGHT",
+ "AFTER" };
+ size_t size = strlen(expanded);
+ struct screen *os = octx->s, s[TOTAL];
+ struct screen_write_ctx ctx[TOTAL];
+ u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL];
+ u_int map[] = { LEFT,
+ LEFT,
+ CENTRE,
+ RIGHT,
+ ABSOLUTE_CENTRE };
+ int focus_start = -1, focus_end = -1;
+ int list_state = -1, fill = -1, even;
+ enum style_align list_align = STYLE_ALIGN_DEFAULT;
+ struct grid_cell gc, current_default;
+ struct style sy, saved_sy;
+ struct utf8_data *ud = &sy.gc.data;
+ const char *cp, *end;
+ enum utf8_state more;
+ char *tmp;
+ struct format_range *fr = NULL, *fr1;
+ struct format_ranges frs;
+ struct style_range *sr;
+
+ memcpy(&current_default, base, sizeof current_default);
+ style_set(&sy, &current_default);
+ TAILQ_INIT(&frs);
+ log_debug("%s: %s", __func__, expanded);
+
+ /*
+ * We build three screens for left, right, centre alignment, one for
+ * the list, one for anything after the list and two for the list left
+ * and right markers.
+ */
+ for (i = 0; i < TOTAL; i++) {
+ screen_init(&s[i], size, 1, 0);
+ screen_write_start(&ctx[i], &s[i]);
+ screen_write_clearendofline(&ctx[i], current_default.bg);
+ width[i] = 0;
+ }
+
+ /*
+ * Walk the string and add to the corresponding screens,
+ * parsing styles as we go.
+ */
+ cp = expanded;
+ while (*cp != '\0') {
+ /* Handle sequences of #. */
+ if (cp[0] == '#' && cp[1] != '[' && cp[1] != '\0') {
+ for (n = 1; cp[n] == '#'; n++)
+ /* nothing */;
+ even = ((n % 2) == 0);
+ if (cp[n] != '[') {
+ cp += n;
+ if (even)
+ n = (n / 2);
+ else
+ n = (n / 2) + 1;
+ width[current] += n;
+ format_draw_many(&ctx[current], &sy, '#', n);
+ continue;
+ }
+ if (even)
+ cp += (n + 1);
+ else
+ cp += (n - 1);
+ if (sy.ignore)
+ continue;
+ format_draw_many(&ctx[current], &sy, '#', n / 2);
+ width[current] += (n / 2);
+ if (even) {
+ utf8_set(ud, '[');
+ screen_write_cell(&ctx[current], &sy.gc);
+ width[current]++;
+ }
+ continue;
+ }
+
+ /* Is this not a style? */
+ if (cp[0] != '#' || cp[1] != '[' || sy.ignore) {
+ /* See if this is a UTF-8 character. */
+ if ((more = utf8_open(ud, *cp)) == UTF8_MORE) {
+ while (*++cp != '\0' && more == UTF8_MORE)
+ more = utf8_append(ud, *cp);
+ if (more != UTF8_DONE)
+ cp -= ud->have;
+ }
+
+ /* Not a UTF-8 character - ASCII or not valid. */
+ if (more != UTF8_DONE) {
+ if (*cp < 0x20 || *cp > 0x7e) {
+ /* Ignore nonprintable characters. */
+ cp++;
+ continue;
+ }
+ utf8_set(ud, *cp);
+ cp++;
+ }
+
+ /* Draw the cell to the current screen. */
+ screen_write_cell(&ctx[current], &sy.gc);
+ width[current] += ud->width;
+ continue;
+ }
+
+ /* This is a style. Work out where the end is and parse it. */
+ end = format_skip(cp + 2, "]");
+ if (end == NULL) {
+ log_debug("%s: no terminating ] at '%s'", __func__,
+ cp + 2);
+ TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1)
+ format_free_range(&frs, fr);
+ goto out;
+ }
+ tmp = xstrndup(cp + 2, end - (cp + 2));
+ style_copy(&saved_sy, &sy);
+ if (style_parse(&sy, &current_default, tmp) != 0) {
+ log_debug("%s: invalid style '%s'", __func__, tmp);
+ free(tmp);
+ cp = end + 1;
+ continue;
+ }
+ log_debug("%s: style '%s' -> '%s'", __func__, tmp,
+ style_tostring(&sy));
+ free(tmp);
+ if (default_colours) {
+ sy.gc.bg = base->bg;
+ sy.gc.fg = base->fg;
+ }
+
+ /* If this style has a fill colour, store it for later. */
+ if (sy.fill != 8)
+ fill = sy.fill;
+
+ /* If this style pushed or popped the default, update it. */
+ if (sy.default_type == STYLE_DEFAULT_PUSH) {
+ memcpy(&current_default, &saved_sy.gc,
+ sizeof current_default);
+ sy.default_type = STYLE_DEFAULT_BASE;
+ } else if (sy.default_type == STYLE_DEFAULT_POP) {
+ memcpy(&current_default, base, sizeof current_default);
+ sy.default_type = STYLE_DEFAULT_BASE;
+ }
+
+ /* Check the list state. */
+ switch (sy.list) {
+ case STYLE_LIST_ON:
+ /*
+ * Entering the list, exiting a marker, or exiting the
+ * focus.
+ */
+ if (list_state != 0) {
+ if (fr != NULL) { /* abort any region */
+ free(fr);
+ fr = NULL;
+ }
+ list_state = 0;
+ list_align = sy.align;
+ }
+
+ /* End the focus if started. */
+ if (focus_start != -1 && focus_end == -1)
+ focus_end = s[LIST].cx;
+
+ current = LIST;
+ break;
+ case STYLE_LIST_FOCUS:
+ /* Entering the focus. */
+ if (list_state != 0) /* not inside the list */
+ break;
+ if (focus_start == -1) /* focus already started */
+ focus_start = s[LIST].cx;
+ break;
+ case STYLE_LIST_OFF:
+ /* Exiting or outside the list. */
+ if (list_state == 0) {
+ if (fr != NULL) { /* abort any region */
+ free(fr);
+ fr = NULL;
+ }
+ if (focus_start != -1 && focus_end == -1)
+ focus_end = s[LIST].cx;
+
+ map[list_align] = AFTER;
+ if (list_align == STYLE_ALIGN_LEFT)
+ map[STYLE_ALIGN_DEFAULT] = AFTER;
+ list_state = 1;
+ }
+ current = map[sy.align];
+ break;
+ case STYLE_LIST_LEFT_MARKER:
+ /* Entering left marker. */
+ if (list_state != 0) /* not inside the list */
+ break;
+ if (s[LIST_LEFT].cx != 0) /* already have marker */
+ break;
+ if (fr != NULL) { /* abort any region */
+ free(fr);
+ fr = NULL;
+ }
+ if (focus_start != -1 && focus_end == -1)
+ focus_start = focus_end = -1;
+ current = LIST_LEFT;
+ break;
+ case STYLE_LIST_RIGHT_MARKER:
+ /* Entering right marker. */
+ if (list_state != 0) /* not inside the list */
+ break;
+ if (s[LIST_RIGHT].cx != 0) /* already have marker */
+ break;
+ if (fr != NULL) { /* abort any region */
+ free(fr);
+ fr = NULL;
+ }
+ if (focus_start != -1 && focus_end == -1)
+ focus_start = focus_end = -1;
+ current = LIST_RIGHT;
+ break;
+ }
+ if (current != last) {
+ log_debug("%s: change %s -> %s", __func__,
+ names[last], names[current]);
+ last = current;
+ }
+
+ /*
+ * Check if the range style has changed and if so end the
+ * current range and start a new one if needed.
+ */
+ if (srs != NULL) {
+ if (fr != NULL && !format_is_type(fr, &sy)) {
+ if (s[current].cx != fr->start) {
+ fr->end = s[current].cx + 1;
+ TAILQ_INSERT_TAIL(&frs, fr, entry);
+ } else
+ free(fr);
+ fr = NULL;
+ }
+ if (fr == NULL && sy.range_type != STYLE_RANGE_NONE) {
+ fr = xcalloc(1, sizeof *fr);
+ fr->index = current;
+
+ fr->s = &s[current];
+ fr->start = s[current].cx;
+
+ fr->type = sy.range_type;
+ fr->argument = sy.range_argument;
+ }
+ }
+
+ cp = end + 1;
+ }
+ free(fr);
+
+ for (i = 0; i < TOTAL; i++) {
+ screen_write_stop(&ctx[i]);
+ log_debug("%s: width %s is %u", __func__, names[i], width[i]);
+ }
+ if (focus_start != -1 && focus_end != -1)
+ log_debug("%s: focus %d-%d", __func__, focus_start, focus_end);
+ TAILQ_FOREACH(fr, &frs, entry) {
+ log_debug("%s: range %d|%u is %s %u-%u", __func__, fr->type,
+ fr->argument, names[fr->index], fr->start, fr->end);
+ }
+
+ /* Clear the available area. */
+ if (fill != -1) {
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.bg = fill;
+ for (i = 0; i < available; i++)
+ screen_write_putc(octx, &gc, ' ');
+ }
+
+ /*
+ * Draw the screens. How they are arranged depends on where the list
+ * appears.
+ */
+ switch (list_align) {
+ case STYLE_ALIGN_DEFAULT:
+ /* No list. */
+ format_draw_none(octx, available, ocx, ocy, &s[LEFT],
+ &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &frs);
+ break;
+ case STYLE_ALIGN_LEFT:
+ /* List is part of the left. */
+ format_draw_left(octx, available, ocx, ocy, &s[LEFT],
+ &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
+ &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
+ focus_start, focus_end, &frs);
+ break;
+ case STYLE_ALIGN_CENTRE:
+ /* List is part of the centre. */
+ format_draw_centre(octx, available, ocx, ocy, &s[LEFT],
+ &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
+ &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
+ focus_start, focus_end, &frs);
+ break;
+ case STYLE_ALIGN_RIGHT:
+ /* List is part of the right. */
+ format_draw_right(octx, available, ocx, ocy, &s[LEFT],
+ &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
+ &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
+ focus_start, focus_end, &frs);
+ break;
+ case STYLE_ALIGN_ABSOLUTE_CENTRE:
+ /* List is in the centre of the entire horizontal space. */
+ format_draw_absolute_centre(octx, available, ocx, ocy, &s[LEFT],
+ &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
+ &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
+ focus_start, focus_end, &frs);
+ break;
+ }
+
+ /* Create ranges to return. */
+ TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1) {
+ sr = xcalloc(1, sizeof *sr);
+ sr->type = fr->type;
+ sr->argument = fr->argument;
+ 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);
+
+ format_free_range(&frs, fr);
+ }
+
+out:
+ /* Free the screens. */
+ for (i = 0; i < TOTAL; i++)
+ screen_free(&s[i]);
+
+ /* Restore the original cursor position. */
+ screen_write_cursormove(octx, ocx, ocy, 0);
+}
+
+/* Get width, taking #[] into account. */
+u_int
+format_width(const char *expanded)
+{
+ const char *cp, *end;
+ u_int n, leading_width, width = 0;
+ struct utf8_data ud;
+ enum utf8_state more;
+
+ cp = expanded;
+ while (*cp != '\0') {
+ if (*cp == '#') {
+ end = format_leading_hashes(cp, &n, &leading_width);
+ width += leading_width;
+ cp = end;
+ if (*cp == '#') {
+ end = format_skip(cp + 2, "]");
+ if (end == NULL)
+ return (0);
+ cp = end + 1;
+ }
+ } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
+ while (*++cp != '\0' && more == UTF8_MORE)
+ more = utf8_append(&ud, *cp);
+ if (more == UTF8_DONE)
+ width += ud.width;
+ else
+ cp -= ud.have;
+ } else if (*cp > 0x1f && *cp < 0x7f) {
+ width++;
+ cp++;
+ } else
+ cp++;
+ }
+ return (width);
+}
+
+/*
+ * Trim on the left, taking #[] into account. Note, we copy the whole set of
+ * unescaped #s, but only add their escaped size to width. This is because the
+ * format_draw function will actually do the escaping when it runs
+ */
+char *
+format_trim_left(const char *expanded, u_int limit)
+{
+ char *copy, *out;
+ const char *cp = expanded, *end;
+ u_int n, width = 0, leading_width;
+ struct utf8_data ud;
+ enum utf8_state more;
+
+ out = copy = xcalloc(1, strlen(expanded) + 1);
+ while (*cp != '\0') {
+ if (width >= limit)
+ break;
+ if (*cp == '#') {
+ end = format_leading_hashes(cp, &n, &leading_width);
+ if (leading_width > limit - width)
+ leading_width = limit - width;
+ if (leading_width != 0) {
+ if (n == 1)
+ *out++ = '#';
+ else {
+ memset(out, '#', 2 * leading_width);
+ out += 2 * leading_width;
+ }
+ width += leading_width;
+ }
+ cp = end;
+ if (*cp == '#') {
+ end = format_skip(cp + 2, "]");
+ if (end == NULL)
+ break;
+ memcpy(out, cp, end + 1 - cp);
+ out += (end + 1 - cp);
+ cp = end + 1;
+ }
+ } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
+ while (*++cp != '\0' && more == UTF8_MORE)
+ more = utf8_append(&ud, *cp);
+ if (more == UTF8_DONE) {
+ if (width + ud.width <= limit) {
+ memcpy(out, ud.data, ud.size);
+ out += ud.size;
+ }
+ width += ud.width;
+ } else {
+ cp -= ud.have;
+ cp++;
+ }
+ } else if (*cp > 0x1f && *cp < 0x7f) {
+ if (width + 1 <= limit)
+ *out++ = *cp;
+ width++;
+ cp++;
+ } else
+ cp++;
+ }
+ *out = '\0';
+ return (copy);
+}
+
+/* Trim on the right, taking #[] into account. */
+char *
+format_trim_right(const char *expanded, u_int limit)
+{
+ char *copy, *out;
+ const char *cp = expanded, *end;
+ u_int width = 0, total_width, skip, n;
+ u_int leading_width, copy_width;
+ struct utf8_data ud;
+ enum utf8_state more;
+
+ total_width = format_width(expanded);
+ if (total_width <= limit)
+ return (xstrdup(expanded));
+ skip = total_width - limit;
+
+ out = copy = xcalloc(1, strlen(expanded) + 1);
+ while (*cp != '\0') {
+ if (*cp == '#') {
+ end = format_leading_hashes(cp, &n, &leading_width);
+ copy_width = leading_width;
+ if (width <= skip) {
+ if (skip - width >= copy_width)
+ copy_width = 0;
+ else
+ copy_width -= (skip - width);
+ }
+ if (copy_width != 0) {
+ if (n == 1)
+ *out++ = '#';
+ else {
+ memset(out, '#', 2 * copy_width);
+ out += 2 * copy_width;
+ }
+ }
+ width += leading_width;
+ cp = end;
+ if (*cp == '#') {
+ end = format_skip(cp + 2, "]");
+ if (end == NULL)
+ break;
+ memcpy(out, cp, end + 1 - cp);
+ out += (end + 1 - cp);
+ cp = end + 1;
+ }
+ } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
+ while (*++cp != '\0' && more == UTF8_MORE)
+ more = utf8_append(&ud, *cp);
+ if (more == UTF8_DONE) {
+ if (width >= skip) {
+ memcpy(out, ud.data, ud.size);
+ out += ud.size;
+ }
+ width += ud.width;
+ } else {
+ cp -= ud.have;
+ cp++;
+ }
+ } else if (*cp > 0x1f && *cp < 0x7f) {
+ if (width >= skip)
+ *out++ = *cp;
+ width++;
+ cp++;
+ } else
+ cp++;
+ }
+ *out = '\0';
+ return (copy);
+}
diff --git a/format.c b/format.c
new file mode 100644
index 0000000..ccd259e
--- /dev/null
+++ b/format.c
@@ -0,0 +1,5059 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <libgen.h>
+#include <math.h>
+#include <pwd.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Build a list of key-value pairs and use them to expand #{key} entries in a
+ * string.
+ */
+
+struct format_expand_state;
+
+static char *format_job_get(struct format_expand_state *, const char *);
+static char *format_expand1(struct format_expand_state *, const char *);
+static int format_replace(struct format_expand_state *, const char *,
+ size_t, char **, size_t *, size_t *);
+static void format_defaults_session(struct format_tree *,
+ struct session *);
+static void format_defaults_client(struct format_tree *, struct client *);
+static void format_defaults_winlink(struct format_tree *,
+ struct winlink *);
+
+/* Entry in format job tree. */
+struct format_job {
+ struct client *client;
+ u_int tag;
+ const char *cmd;
+ const char *expanded;
+
+ time_t last;
+ char *out;
+ int updated;
+
+ struct job *job;
+ int status;
+
+ RB_ENTRY(format_job) entry;
+};
+
+/* Format job tree. */
+static int format_job_cmp(struct format_job *, struct format_job *);
+static RB_HEAD(format_job_tree, format_job) format_jobs = RB_INITIALIZER();
+RB_GENERATE_STATIC(format_job_tree, format_job, entry, format_job_cmp);
+
+/* Format job tree comparison function. */
+static int
+format_job_cmp(struct format_job *fj1, struct format_job *fj2)
+{
+ if (fj1->tag < fj2->tag)
+ return (-1);
+ if (fj1->tag > fj2->tag)
+ return (1);
+ return (strcmp(fj1->cmd, fj2->cmd));
+}
+
+/* Format modifiers. */
+#define FORMAT_TIMESTRING 0x1
+#define FORMAT_BASENAME 0x2
+#define FORMAT_DIRNAME 0x4
+#define FORMAT_QUOTE_SHELL 0x8
+#define FORMAT_LITERAL 0x10
+#define FORMAT_EXPAND 0x20
+#define FORMAT_EXPANDTIME 0x40
+#define FORMAT_SESSIONS 0x80
+#define FORMAT_WINDOWS 0x100
+#define FORMAT_PANES 0x200
+#define FORMAT_PRETTY 0x400
+#define FORMAT_LENGTH 0x800
+#define FORMAT_WIDTH 0x1000
+#define FORMAT_QUOTE_STYLE 0x2000
+#define FORMAT_WINDOW_NAME 0x4000
+#define FORMAT_SESSION_NAME 0x8000
+#define FORMAT_CHARACTER 0x10000
+#define FORMAT_COLOUR 0x20000
+
+/* Limit on recursion. */
+#define FORMAT_LOOP_LIMIT 100
+
+/* Format expand flags. */
+#define FORMAT_EXPAND_TIME 0x1
+#define FORMAT_EXPAND_NOJOBS 0x2
+
+/* Entry in format tree. */
+struct format_entry {
+ char *key;
+ char *value;
+ time_t time;
+ format_cb cb;
+ RB_ENTRY(format_entry) entry;
+};
+
+/* Format type. */
+enum format_type {
+ FORMAT_TYPE_UNKNOWN,
+ FORMAT_TYPE_SESSION,
+ FORMAT_TYPE_WINDOW,
+ FORMAT_TYPE_PANE
+};
+
+struct format_tree {
+ enum format_type type;
+
+ struct client *c;
+ struct session *s;
+ struct winlink *wl;
+ struct window *w;
+ struct window_pane *wp;
+ struct paste_buffer *pb;
+
+ struct cmdq_item *item;
+ struct client *client;
+ int flags;
+ u_int tag;
+
+ struct mouse_event m;
+
+ RB_HEAD(format_entry_tree, format_entry) tree;
+};
+static int format_entry_cmp(struct format_entry *, struct format_entry *);
+RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp);
+
+/* Format expand state. */
+struct format_expand_state {
+ struct format_tree *ft;
+ u_int loop;
+ time_t time;
+ struct tm tm;
+ int flags;
+};
+
+/* Format modifier. */
+struct format_modifier {
+ char modifier[3];
+ u_int size;
+
+ char **argv;
+ int argc;
+};
+
+/* Format entry tree comparison function. */
+static int
+format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2)
+{
+ return (strcmp(fe1->key, fe2->key));
+}
+
+/* Single-character uppercase aliases. */
+static const char *format_upper[] = {
+ NULL, /* A */
+ NULL, /* B */
+ NULL, /* C */
+ "pane_id", /* D */
+ NULL, /* E */
+ "window_flags", /* F */
+ NULL, /* G */
+ "host", /* H */
+ "window_index", /* I */
+ NULL, /* J */
+ NULL, /* K */
+ NULL, /* L */
+ NULL, /* M */
+ NULL, /* N */
+ NULL, /* O */
+ "pane_index", /* P */
+ NULL, /* Q */
+ NULL, /* R */
+ "session_name", /* S */
+ "pane_title", /* T */
+ NULL, /* U */
+ NULL, /* V */
+ "window_name", /* W */
+ NULL, /* X */
+ NULL, /* Y */
+ NULL /* Z */
+};
+
+/* Single-character lowercase aliases. */
+static const char *format_lower[] = {
+ NULL, /* a */
+ NULL, /* b */
+ NULL, /* c */
+ NULL, /* d */
+ NULL, /* e */
+ NULL, /* f */
+ NULL, /* g */
+ "host_short", /* h */
+ NULL, /* i */
+ NULL, /* j */
+ NULL, /* k */
+ NULL, /* l */
+ NULL, /* m */
+ NULL, /* n */
+ NULL, /* o */
+ NULL, /* p */
+ NULL, /* q */
+ NULL, /* r */
+ NULL, /* s */
+ NULL, /* t */
+ NULL, /* u */
+ NULL, /* v */
+ NULL, /* w */
+ NULL, /* x */
+ NULL, /* y */
+ NULL /* z */
+};
+
+/* Is logging enabled? */
+static inline int
+format_logging(struct format_tree *ft)
+{
+ return (log_get_level() != 0 || (ft->flags & FORMAT_VERBOSE));
+}
+
+/* Log a message if verbose. */
+static void printflike(3, 4)
+format_log1(struct format_expand_state *es, const char *from, const char *fmt,
+ ...)
+{
+ struct format_tree *ft = es->ft;
+ va_list ap;
+ char *s;
+ static const char spaces[] = " ";
+
+ if (!format_logging(ft))
+ return;
+
+ va_start(ap, fmt);
+ xvasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ log_debug("%s: %s", from, s);
+ if (ft->item != NULL && (ft->flags & FORMAT_VERBOSE))
+ cmdq_print(ft->item, "#%.*s%s", es->loop, spaces, s);
+
+ free(s);
+}
+#define format_log(es, fmt, ...) format_log1(es, __func__, fmt, ##__VA_ARGS__)
+
+/* Copy expand state. */
+static void
+format_copy_state(struct format_expand_state *to,
+ struct format_expand_state *from, int flags)
+{
+ to->ft = from->ft;
+ to->loop = from->loop;
+ to->time = from->time;
+ memcpy(&to->tm, &from->tm, sizeof to->tm);
+ to->flags = from->flags|flags;
+}
+
+/* Format job update callback. */
+static void
+format_job_update(struct job *job)
+{
+ struct format_job *fj = job_get_data(job);
+ struct evbuffer *evb = job_get_event(job)->input;
+ char *line = NULL, *next;
+ time_t t;
+
+ while ((next = evbuffer_readline(evb)) != NULL) {
+ free(line);
+ line = next;
+ }
+ if (line == NULL)
+ return;
+ fj->updated = 1;
+
+ free(fj->out);
+ fj->out = line;
+
+ log_debug("%s: %p %s: %s", __func__, fj, fj->cmd, fj->out);
+
+ t = time(NULL);
+ if (fj->status && fj->last != t) {
+ if (fj->client != NULL)
+ server_status_client(fj->client);
+ fj->last = t;
+ }
+}
+
+/* Format job complete callback. */
+static void
+format_job_complete(struct job *job)
+{
+ struct format_job *fj = job_get_data(job);
+ struct evbuffer *evb = job_get_event(job)->input;
+ char *line, *buf;
+ size_t len;
+
+ fj->job = NULL;
+
+ buf = NULL;
+ if ((line = evbuffer_readline(evb)) == NULL) {
+ len = EVBUFFER_LENGTH(evb);
+ buf = xmalloc(len + 1);
+ if (len != 0)
+ memcpy(buf, EVBUFFER_DATA(evb), len);
+ buf[len] = '\0';
+ } else
+ buf = line;
+
+ log_debug("%s: %p %s: %s", __func__, fj, fj->cmd, buf);
+
+ if (*buf != '\0' || !fj->updated) {
+ free(fj->out);
+ fj->out = buf;
+ } else
+ free(buf);
+
+ if (fj->status) {
+ if (fj->client != NULL)
+ server_status_client(fj->client);
+ fj->status = 0;
+ }
+}
+
+/* Find a job. */
+static char *
+format_job_get(struct format_expand_state *es, const char *cmd)
+{
+ struct format_tree *ft = es->ft;
+ struct format_job_tree *jobs;
+ struct format_job fj0, *fj;
+ time_t t;
+ char *expanded;
+ int force;
+ struct format_expand_state next;
+
+ if (ft->client == NULL)
+ jobs = &format_jobs;
+ else if (ft->client->jobs != NULL)
+ jobs = ft->client->jobs;
+ else {
+ jobs = ft->client->jobs = xmalloc(sizeof *ft->client->jobs);
+ RB_INIT(jobs);
+ }
+
+ fj0.tag = ft->tag;
+ fj0.cmd = cmd;
+ if ((fj = RB_FIND(format_job_tree, jobs, &fj0)) == NULL) {
+ fj = xcalloc(1, sizeof *fj);
+ fj->client = ft->client;
+ fj->tag = ft->tag;
+ fj->cmd = xstrdup(cmd);
+
+ RB_INSERT(format_job_tree, jobs, fj);
+ }
+
+ format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS);
+ next.flags &= ~FORMAT_EXPAND_TIME;
+
+ expanded = format_expand1(&next, cmd);
+ if (fj->expanded == NULL || strcmp(expanded, fj->expanded) != 0) {
+ free((void *)fj->expanded);
+ fj->expanded = xstrdup(expanded);
+ force = 1;
+ } else
+ force = (ft->flags & FORMAT_FORCE);
+
+ t = time(NULL);
+ if (force && fj->job != NULL)
+ job_free(fj->job);
+ if (force || (fj->job == NULL && fj->last != t)) {
+ fj->job = job_run(expanded, 0, NULL, NULL, NULL,
+ server_client_get_cwd(ft->client, NULL), format_job_update,
+ format_job_complete, NULL, fj, JOB_NOWAIT, -1, -1);
+ if (fj->job == NULL) {
+ free(fj->out);
+ xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd);
+ }
+ fj->last = t;
+ fj->updated = 0;
+ } else if (fj->job != NULL && (t - fj->last) > 1 && fj->out == NULL)
+ xasprintf(&fj->out, "<'%s' not ready>", fj->cmd);
+ free(expanded);
+
+ if (ft->flags & FORMAT_STATUS)
+ fj->status = 1;
+ if (fj->out == NULL)
+ return (xstrdup(""));
+ return (format_expand1(&next, fj->out));
+}
+
+/* Remove old jobs. */
+static void
+format_job_tidy(struct format_job_tree *jobs, int force)
+{
+ struct format_job *fj, *fj1;
+ time_t now;
+
+ now = time(NULL);
+ RB_FOREACH_SAFE(fj, format_job_tree, jobs, fj1) {
+ if (!force && (fj->last > now || now - fj->last < 3600))
+ continue;
+ RB_REMOVE(format_job_tree, jobs, fj);
+
+ log_debug("%s: %s", __func__, fj->cmd);
+
+ if (fj->job != NULL)
+ job_free(fj->job);
+
+ free((void *)fj->expanded);
+ free((void *)fj->cmd);
+ free(fj->out);
+
+ free(fj);
+ }
+}
+
+/* Tidy old jobs for all clients. */
+void
+format_tidy_jobs(void)
+{
+ struct client *c;
+
+ format_job_tidy(&format_jobs, 0);
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->jobs != NULL)
+ format_job_tidy(c->jobs, 0);
+ }
+}
+
+/* Remove old jobs for client. */
+void
+format_lost_client(struct client *c)
+{
+ if (c->jobs != NULL)
+ format_job_tidy(c->jobs, 1);
+ free(c->jobs);
+}
+
+/* Wrapper for asprintf. */
+static char * printflike(1, 2)
+format_printf(const char *fmt, ...)
+{
+ va_list ap;
+ char *s;
+
+ va_start(ap, fmt);
+ xvasprintf(&s, fmt, ap);
+ va_end(ap);
+ return (s);
+}
+
+/* Callback for host. */
+static void *
+format_cb_host(__unused struct format_tree *ft)
+{
+ char host[HOST_NAME_MAX + 1];
+
+ if (gethostname(host, sizeof host) != 0)
+ return (xstrdup(""));
+ return (xstrdup(host));
+}
+
+/* Callback for host_short. */
+static void *
+format_cb_host_short(__unused struct format_tree *ft)
+{
+ char host[HOST_NAME_MAX + 1], *cp;
+
+ if (gethostname(host, sizeof host) != 0)
+ return (xstrdup(""));
+ if ((cp = strchr(host, '.')) != NULL)
+ *cp = '\0';
+ return (xstrdup(host));
+}
+
+/* Callback for pid. */
+static void *
+format_cb_pid(__unused struct format_tree *ft)
+{
+ char *value;
+
+ xasprintf(&value, "%ld", (long)getpid());
+ return (value);
+}
+
+/* Callback for session_attached_list. */
+static void *
+format_cb_session_attached_list(struct format_tree *ft)
+{
+ struct session *s = ft->s;
+ struct client *loop;
+ struct evbuffer *buffer;
+ int size;
+ char *value = NULL;
+
+ if (s == NULL)
+ return (NULL);
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop->session == s) {
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%s", loop->name);
+ }
+ }
+
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for session_alerts. */
+static void *
+format_cb_session_alerts(struct format_tree *ft)
+{
+ struct session *s = ft->s;
+ struct winlink *wl;
+ char alerts[1024], tmp[16];
+
+ if (s == NULL)
+ return (NULL);
+
+ *alerts = '\0';
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ if ((wl->flags & WINLINK_ALERTFLAGS) == 0)
+ continue;
+ xsnprintf(tmp, sizeof tmp, "%u", wl->idx);
+
+ if (*alerts != '\0')
+ strlcat(alerts, ",", sizeof alerts);
+ strlcat(alerts, tmp, sizeof alerts);
+ if (wl->flags & WINLINK_ACTIVITY)
+ strlcat(alerts, "#", sizeof alerts);
+ if (wl->flags & WINLINK_BELL)
+ strlcat(alerts, "!", sizeof alerts);
+ if (wl->flags & WINLINK_SILENCE)
+ strlcat(alerts, "~", sizeof alerts);
+ }
+ return (xstrdup(alerts));
+}
+
+/* Callback for session_stack. */
+static void *
+format_cb_session_stack(struct format_tree *ft)
+{
+ struct session *s = ft->s;
+ struct winlink *wl;
+ char result[1024], tmp[16];
+
+ if (s == NULL)
+ return (NULL);
+
+ xsnprintf(result, sizeof result, "%u", s->curw->idx);
+ TAILQ_FOREACH(wl, &s->lastw, sentry) {
+ xsnprintf(tmp, sizeof tmp, "%u", wl->idx);
+
+ if (*result != '\0')
+ strlcat(result, ",", sizeof result);
+ strlcat(result, tmp, sizeof result);
+ }
+ return (xstrdup(result));
+}
+
+/* Callback for window_stack_index. */
+static void *
+format_cb_window_stack_index(struct format_tree *ft)
+{
+ struct session *s;
+ struct winlink *wl;
+ u_int idx;
+ char *value = NULL;
+
+ if (ft->wl == NULL)
+ return (NULL);
+ s = ft->wl->session;
+
+ idx = 0;
+ TAILQ_FOREACH(wl, &s->lastw, sentry) {
+ idx++;
+ if (wl == ft->wl)
+ break;
+ }
+ if (wl == NULL)
+ return (xstrdup("0"));
+ xasprintf(&value, "%u", idx);
+ return (value);
+}
+
+/* Callback for window_linked_sessions_list. */
+static void *
+format_cb_window_linked_sessions_list(struct format_tree *ft)
+{
+ struct window *w;
+ struct winlink *wl;
+ struct evbuffer *buffer;
+ int size;
+ char *value = NULL;
+
+ if (ft->wl == NULL)
+ return (NULL);
+ w = ft->wl->window;
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%s", wl->session->name);
+ }
+
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for window_active_sessions. */
+static void *
+format_cb_window_active_sessions(struct format_tree *ft)
+{
+ struct window *w;
+ struct winlink *wl;
+ u_int n = 0;
+ char *value;
+
+ if (ft->wl == NULL)
+ return (NULL);
+ w = ft->wl->window;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->session->curw == wl)
+ n++;
+ }
+
+ xasprintf(&value, "%u", n);
+ return (value);
+}
+
+/* Callback for window_active_sessions_list. */
+static void *
+format_cb_window_active_sessions_list(struct format_tree *ft)
+{
+ struct window *w;
+ struct winlink *wl;
+ struct evbuffer *buffer;
+ int size;
+ char *value = NULL;
+
+ if (ft->wl == NULL)
+ return (NULL);
+ w = ft->wl->window;
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->session->curw == wl) {
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%s", wl->session->name);
+ }
+ }
+
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for window_active_clients. */
+static void *
+format_cb_window_active_clients(struct format_tree *ft)
+{
+ struct window *w;
+ struct client *loop;
+ struct session *client_session;
+ u_int n = 0;
+ char *value;
+
+ if (ft->wl == NULL)
+ return (NULL);
+ w = ft->wl->window;
+
+ TAILQ_FOREACH(loop, &clients, entry) {
+ client_session = loop->session;
+ if (client_session == NULL)
+ continue;
+
+ if (w == client_session->curw->window)
+ n++;
+ }
+
+ xasprintf(&value, "%u", n);
+ return (value);
+}
+
+/* Callback for window_active_clients_list. */
+static void *
+format_cb_window_active_clients_list(struct format_tree *ft)
+{
+ struct window *w;
+ struct client *loop;
+ struct session *client_session;
+ struct evbuffer *buffer;
+ int size;
+ char *value = NULL;
+
+ if (ft->wl == NULL)
+ return (NULL);
+ w = ft->wl->window;
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+
+ TAILQ_FOREACH(loop, &clients, entry) {
+ client_session = loop->session;
+ if (client_session == NULL)
+ continue;
+
+ if (w == client_session->curw->window) {
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%s", loop->name);
+ }
+ }
+
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for window_layout. */
+static void *
+format_cb_window_layout(struct format_tree *ft)
+{
+ struct window *w = ft->w;
+
+ if (w == NULL)
+ return (NULL);
+
+ if (w->saved_layout_root != NULL)
+ return (layout_dump(w->saved_layout_root));
+ return (layout_dump(w->layout_root));
+}
+
+/* Callback for window_visible_layout. */
+static void *
+format_cb_window_visible_layout(struct format_tree *ft)
+{
+ struct window *w = ft->w;
+
+ if (w == NULL)
+ return (NULL);
+
+ return (layout_dump(w->layout_root));
+}
+
+/* Callback for pane_start_command. */
+static void *
+format_cb_start_command(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+
+ if (wp == NULL)
+ return (NULL);
+
+ return (cmd_stringify_argv(wp->argc, wp->argv));
+}
+
+/* Callback for pane_start_path. */
+static void *
+format_cb_start_path(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+
+ if (wp == NULL)
+ return (NULL);
+
+ if (wp->cwd == NULL)
+ return (xstrdup(""));
+ return (xstrdup(wp->cwd));
+}
+
+/* Callback for pane_current_command. */
+static void *
+format_cb_current_command(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ char *cmd, *value;
+
+ if (wp == NULL || wp->shell == NULL)
+ return (NULL);
+
+ cmd = osdep_get_name(wp->fd, wp->tty);
+ if (cmd == NULL || *cmd == '\0') {
+ free(cmd);
+ cmd = cmd_stringify_argv(wp->argc, wp->argv);
+ if (cmd == NULL || *cmd == '\0') {
+ free(cmd);
+ cmd = xstrdup(wp->shell);
+ }
+ }
+ value = parse_window_name(cmd);
+ free(cmd);
+ return (value);
+}
+
+/* Callback for pane_current_path. */
+static void *
+format_cb_current_path(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ char *cwd;
+
+ if (wp == NULL)
+ return (NULL);
+
+ cwd = osdep_get_cwd(wp->fd);
+ if (cwd == NULL)
+ return (NULL);
+ return (xstrdup(cwd));
+}
+
+/* Callback for history_bytes. */
+static void *
+format_cb_history_bytes(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct grid *gd;
+ struct grid_line *gl;
+ size_t size = 0;
+ u_int i;
+ char *value;
+
+ if (wp == NULL)
+ return (NULL);
+ gd = wp->base.grid;
+
+ for (i = 0; i < gd->hsize + gd->sy; i++) {
+ gl = grid_get_line(gd, i);
+ size += gl->cellsize * sizeof *gl->celldata;
+ size += gl->extdsize * sizeof *gl->extddata;
+ }
+ size += (gd->hsize + gd->sy) * sizeof *gl;
+
+ xasprintf(&value, "%zu", size);
+ return (value);
+}
+
+/* Callback for history_all_bytes. */
+static void *
+format_cb_history_all_bytes(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct grid *gd;
+ struct grid_line *gl;
+ u_int i, lines, cells = 0, extended_cells = 0;
+ char *value;
+
+ if (wp == NULL)
+ return (NULL);
+ gd = wp->base.grid;
+
+ lines = gd->hsize + gd->sy;
+ for (i = 0; i < lines; i++) {
+ gl = grid_get_line(gd, i);
+ cells += gl->cellsize;
+ extended_cells += gl->extdsize;
+ }
+
+ xasprintf(&value, "%u,%zu,%u,%zu,%u,%zu", lines,
+ lines * sizeof *gl, cells, cells * sizeof *gl->celldata,
+ extended_cells, extended_cells * sizeof *gl->extddata);
+ return (value);
+}
+
+/* Callback for pane_tabs. */
+static void *
+format_cb_pane_tabs(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct evbuffer *buffer;
+ u_int i;
+ int size;
+ char *value = NULL;
+
+ if (wp == NULL)
+ return (NULL);
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+ for (i = 0; i < wp->base.grid->sx; i++) {
+ if (!bit_test(wp->base.tabs, i))
+ continue;
+
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%u", i);
+ }
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for pane_fg. */
+static void *
+format_cb_pane_fg(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct grid_cell gc;
+
+ if (wp == NULL)
+ return (NULL);
+
+ tty_default_colours(&gc, wp);
+ return (xstrdup(colour_tostring(gc.fg)));
+}
+
+/* Callback for pane_bg. */
+static void *
+format_cb_pane_bg(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct grid_cell gc;
+
+ if (wp == NULL)
+ return (NULL);
+
+ tty_default_colours(&gc, wp);
+ return (xstrdup(colour_tostring(gc.bg)));
+}
+
+/* Callback for session_group_list. */
+static void *
+format_cb_session_group_list(struct format_tree *ft)
+{
+ struct session *s = ft->s;
+ struct session_group *sg;
+ struct session *loop;
+ struct evbuffer *buffer;
+ int size;
+ char *value = NULL;
+
+ if (s == NULL)
+ return (NULL);
+ sg = session_group_contains(s);
+ if (sg == NULL)
+ return (NULL);
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+
+ TAILQ_FOREACH(loop, &sg->sessions, gentry) {
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%s", loop->name);
+ }
+
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for session_group_attached_list. */
+static void *
+format_cb_session_group_attached_list(struct format_tree *ft)
+{
+ struct session *s = ft->s, *client_session, *session_loop;
+ struct session_group *sg;
+ struct client *loop;
+ struct evbuffer *buffer;
+ int size;
+ char *value = NULL;
+
+ if (s == NULL)
+ return (NULL);
+ sg = session_group_contains(s);
+ if (sg == NULL)
+ return (NULL);
+
+ buffer = evbuffer_new();
+ if (buffer == NULL)
+ fatalx("out of memory");
+
+ TAILQ_FOREACH(loop, &clients, entry) {
+ client_session = loop->session;
+ if (client_session == NULL)
+ continue;
+ TAILQ_FOREACH(session_loop, &sg->sessions, gentry) {
+ if (session_loop == client_session){
+ if (EVBUFFER_LENGTH(buffer) > 0)
+ evbuffer_add(buffer, ",", 1);
+ evbuffer_add_printf(buffer, "%s", loop->name);
+ }
+ }
+ }
+
+ if ((size = EVBUFFER_LENGTH(buffer)) != 0)
+ xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
+ evbuffer_free(buffer);
+ return (value);
+}
+
+/* Callback for pane_in_mode. */
+static void *
+format_cb_pane_in_mode(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ u_int n = 0;
+ struct window_mode_entry *wme;
+ char *value;
+
+ if (wp == NULL)
+ return (NULL);
+
+ TAILQ_FOREACH(wme, &wp->modes, entry)
+ n++;
+ xasprintf(&value, "%u", n);
+ return (value);
+}
+
+/* Callback for pane_at_top. */
+static void *
+format_cb_pane_at_top(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct window *w;
+ int status, flag;
+ char *value;
+
+ if (wp == NULL)
+ return (NULL);
+ w = wp->window;
+
+ status = options_get_number(w->options, "pane-border-status");
+ if (status == PANE_STATUS_TOP)
+ flag = (wp->yoff == 1);
+ else
+ flag = (wp->yoff == 0);
+ xasprintf(&value, "%d", flag);
+ return (value);
+}
+
+/* Callback for pane_at_bottom. */
+static void *
+format_cb_pane_at_bottom(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct window *w;
+ int status, flag;
+ char *value;
+
+ if (wp == NULL)
+ return (NULL);
+ w = wp->window;
+
+ status = options_get_number(w->options, "pane-border-status");
+ if (status == PANE_STATUS_BOTTOM)
+ flag = (wp->yoff + wp->sy == w->sy - 1);
+ else
+ flag = (wp->yoff + wp->sy == w->sy);
+ xasprintf(&value, "%d", flag);
+ return (value);
+}
+
+/* Callback for cursor_character. */
+static void *
+format_cb_cursor_character(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ struct grid_cell gc;
+ char *value = NULL;
+
+ if (wp == NULL)
+ return (NULL);
+
+ grid_view_get_cell(wp->base.grid, wp->base.cx, wp->base.cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ xasprintf(&value, "%.*s", (int)gc.data.size, gc.data.data);
+ return (value);
+}
+
+/* Callback for mouse_word. */
+static void *
+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);
+ 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);
+
+ 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 (NULL);
+ }
+ gd = wp->base.grid;
+ return (format_grid_word(gd, x, gd->hsize + y));
+}
+
+/* Callback for mouse_line. */
+static void *
+format_cb_mouse_line(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);
+
+ if (!TAILQ_EMPTY(&wp->modes)) {
+ if (TAILQ_FIRST(&wp->modes)->mode == &window_copy_mode ||
+ TAILQ_FIRST(&wp->modes)->mode == &window_view_mode)
+ return (window_copy_get_line(wp, y));
+ return (NULL);
+ }
+ gd = wp->base.grid;
+ return (format_grid_line(gd, gd->hsize + y));
+}
+
+/* Callback for alternate_on. */
+static void *
+format_cb_alternate_on(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.saved_grid != NULL)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for alternate_saved_x. */
+static void *
+format_cb_alternate_saved_x(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.saved_cx));
+ return (NULL);
+}
+
+/* Callback for alternate_saved_y. */
+static void *
+format_cb_alternate_saved_y(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.saved_cy));
+ return (NULL);
+}
+
+/* Callback for buffer_name. */
+static void *
+format_cb_buffer_name(struct format_tree *ft)
+{
+ if (ft->pb != NULL)
+ return (xstrdup(paste_buffer_name(ft->pb)));
+ return (NULL);
+}
+
+/* Callback for buffer_sample. */
+static void *
+format_cb_buffer_sample(struct format_tree *ft)
+{
+ if (ft->pb != NULL)
+ return (paste_make_sample(ft->pb));
+ return (NULL);
+}
+
+/* Callback for buffer_size. */
+static void *
+format_cb_buffer_size(struct format_tree *ft)
+{
+ size_t size;
+
+ if (ft->pb != NULL) {
+ paste_buffer_data(ft->pb, &size);
+ return (format_printf("%zu", size));
+ }
+ return (NULL);
+}
+
+/* Callback for client_cell_height. */
+static void *
+format_cb_client_cell_height(struct format_tree *ft)
+{
+ if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED))
+ return (format_printf("%u", ft->c->tty.ypixel));
+ return (NULL);
+}
+
+/* Callback for client_cell_width. */
+static void *
+format_cb_client_cell_width(struct format_tree *ft)
+{
+ if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED))
+ return (format_printf("%u", ft->c->tty.xpixel));
+ return (NULL);
+}
+
+/* Callback for client_control_mode. */
+static void *
+format_cb_client_control_mode(struct format_tree *ft)
+{
+ if (ft->c != NULL) {
+ if (ft->c->flags & CLIENT_CONTROL)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for client_discarded. */
+static void *
+format_cb_client_discarded(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (format_printf("%zu", ft->c->discarded));
+ return (NULL);
+}
+
+/* Callback for client_flags. */
+static void *
+format_cb_client_flags(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (xstrdup(server_client_get_flags(ft->c)));
+ return (NULL);
+}
+
+/* Callback for client_height. */
+static void *
+format_cb_client_height(struct format_tree *ft)
+{
+ if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED))
+ return (format_printf("%u", ft->c->tty.sy));
+ return (NULL);
+}
+
+/* Callback for client_key_table. */
+static void *
+format_cb_client_key_table(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (xstrdup(ft->c->keytable->name));
+ return (NULL);
+}
+
+/* Callback for client_last_session. */
+static void *
+format_cb_client_last_session(struct format_tree *ft)
+{
+ if (ft->c != NULL &&
+ ft->c->last_session != NULL &&
+ session_alive(ft->c->last_session))
+ return (xstrdup(ft->c->last_session->name));
+ return (NULL);
+}
+
+/* Callback for client_name. */
+static void *
+format_cb_client_name(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (xstrdup(ft->c->name));
+ return (NULL);
+}
+
+/* Callback for client_pid. */
+static void *
+format_cb_client_pid(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (format_printf("%ld", (long)ft->c->pid));
+ return (NULL);
+}
+
+/* Callback for client_prefix. */
+static void *
+format_cb_client_prefix(struct format_tree *ft)
+{
+ const char *name;
+
+ if (ft->c != NULL) {
+ name = server_client_get_key_table(ft->c);
+ if (strcmp(ft->c->keytable->name, name) == 0)
+ return (xstrdup("0"));
+ return (xstrdup("1"));
+ }
+ return (NULL);
+}
+
+/* Callback for client_readonly. */
+static void *
+format_cb_client_readonly(struct format_tree *ft)
+{
+ if (ft->c != NULL) {
+ if (ft->c->flags & CLIENT_READONLY)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for client_session. */
+static void *
+format_cb_client_session(struct format_tree *ft)
+{
+ if (ft->c != NULL && ft->c->session != NULL)
+ return (xstrdup(ft->c->session->name));
+ return (NULL);
+}
+
+/* Callback for client_termfeatures. */
+static void *
+format_cb_client_termfeatures(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (xstrdup(tty_get_features(ft->c->term_features)));
+ return (NULL);
+}
+
+/* Callback for client_termname. */
+static void *
+format_cb_client_termname(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (xstrdup(ft->c->term_name));
+ return (NULL);
+}
+
+/* Callback for client_termtype. */
+static void *
+format_cb_client_termtype(struct format_tree *ft)
+{
+ if (ft->c != NULL) {
+ if (ft->c->term_type == NULL)
+ return (xstrdup(""));
+ return (xstrdup(ft->c->term_type));
+ }
+ return (NULL);
+}
+
+/* Callback for client_tty. */
+static void *
+format_cb_client_tty(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (xstrdup(ft->c->ttyname));
+ return (NULL);
+}
+
+/* Callback for client_uid. */
+static void *
+format_cb_client_uid(struct format_tree *ft)
+{
+ uid_t uid;
+
+ if (ft->c != NULL) {
+ uid = proc_get_peer_uid(ft->c->peer);
+ if (uid != (uid_t)-1)
+ return (format_printf("%ld", (long)uid));
+ }
+ return (NULL);
+}
+
+/* Callback for client_user. */
+static void *
+format_cb_client_user(struct format_tree *ft)
+{
+ uid_t uid;
+ struct passwd *pw;
+
+ if (ft->c != NULL) {
+ uid = proc_get_peer_uid(ft->c->peer);
+ if (uid != (uid_t)-1 && (pw = getpwuid(uid)) != NULL)
+ return (xstrdup(pw->pw_name));
+ }
+ return (NULL);
+}
+
+/* Callback for client_utf8. */
+static void *
+format_cb_client_utf8(struct format_tree *ft)
+{
+ if (ft->c != NULL) {
+ if (ft->c->flags & CLIENT_UTF8)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for client_width. */
+static void *
+format_cb_client_width(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (format_printf("%u", ft->c->tty.sx));
+ return (NULL);
+}
+
+/* Callback for client_written. */
+static void *
+format_cb_client_written(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (format_printf("%zu", ft->c->written));
+ return (NULL);
+}
+
+/* Callback for config_files. */
+static void *
+format_cb_config_files(__unused struct format_tree *ft)
+{
+ char *s = NULL;
+ size_t slen = 0;
+ u_int i;
+ size_t n;
+
+ for (i = 0; i < cfg_nfiles; i++) {
+ n = strlen(cfg_files[i]) + 1;
+ s = xrealloc(s, slen + n + 1);
+ slen += xsnprintf(s + slen, n + 1, "%s,", cfg_files[i]);
+ }
+ if (s == NULL)
+ return (xstrdup(""));
+ s[slen - 1] = '\0';
+ return (s);
+}
+
+/* Callback for cursor_flag. */
+static void *
+format_cb_cursor_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_CURSOR)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for cursor_x. */
+static void *
+format_cb_cursor_x(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.cx));
+ return (NULL);
+}
+
+/* Callback for cursor_y. */
+static void *
+format_cb_cursor_y(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.cy));
+ return (NULL);
+}
+
+/* Callback for history_limit. */
+static void *
+format_cb_history_limit(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.grid->hlimit));
+ return (NULL);
+}
+
+/* Callback for history_size. */
+static void *
+format_cb_history_size(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.grid->hsize));
+ return (NULL);
+}
+
+/* Callback for insert_flag. */
+static void *
+format_cb_insert_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_INSERT)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for keypad_cursor_flag. */
+static void *
+format_cb_keypad_cursor_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_KCURSOR)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for keypad_flag. */
+static void *
+format_cb_keypad_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_KKEYPAD)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_all_flag. */
+static void *
+format_cb_mouse_all_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_MOUSE_ALL)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_any_flag. */
+static void *
+format_cb_mouse_any_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & ALL_MOUSE_MODES)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_button_flag. */
+static void *
+format_cb_mouse_button_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_MOUSE_BUTTON)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_pane. */
+static void *
+format_cb_mouse_pane(struct format_tree *ft)
+{
+ struct window_pane *wp;
+
+ if (ft->m.valid) {
+ wp = cmd_mouse_pane(&ft->m, NULL, NULL);
+ if (wp != NULL)
+ return (format_printf("%%%u", wp->id));
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_sgr_flag. */
+static void *
+format_cb_mouse_sgr_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_MOUSE_SGR)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_standard_flag. */
+static void *
+format_cb_mouse_standard_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_MOUSE_STANDARD)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_utf8_flag. */
+static void *
+format_cb_mouse_utf8_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_MOUSE_UTF8)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_x. */
+static void *
+format_cb_mouse_x(struct format_tree *ft)
+{
+ struct window_pane *wp;
+ u_int x, y;
+
+ if (!ft->m.valid)
+ return (NULL);
+ wp = cmd_mouse_pane(&ft->m, NULL, NULL);
+ if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0)
+ return (format_printf("%u", x));
+ if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) {
+ if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines)
+ return (format_printf("%u", ft->m.x));
+ if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat)
+ return (format_printf("%u", ft->m.x));
+ }
+ return (NULL);
+}
+
+/* Callback for mouse_y. */
+static void *
+format_cb_mouse_y(struct format_tree *ft)
+{
+ struct window_pane *wp;
+ u_int x, y;
+
+ if (!ft->m.valid)
+ return (NULL);
+ wp = cmd_mouse_pane(&ft->m, NULL, NULL);
+ if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0)
+ return (format_printf("%u", y));
+ if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) {
+ if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines)
+ return (format_printf("%u", ft->m.y));
+ if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat)
+ return (format_printf("%u", ft->m.y - ft->m.statusat));
+ }
+ return (NULL);
+}
+
+/* Callback for next_session_id. */
+static void *
+format_cb_next_session_id(__unused struct format_tree *ft)
+{
+ return (format_printf("$%u", next_session_id));
+}
+
+/* Callback for origin_flag. */
+static void *
+format_cb_origin_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_ORIGIN)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_active. */
+static void *
+format_cb_pane_active(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp == ft->wp->window->active)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_at_left. */
+static void *
+format_cb_pane_at_left(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->xoff == 0)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_at_right. */
+static void *
+format_cb_pane_at_right(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->xoff + ft->wp->sx == ft->wp->window->sx)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_bottom. */
+static void *
+format_cb_pane_bottom(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->yoff + ft->wp->sy - 1));
+ return (NULL);
+}
+
+/* Callback for pane_dead. */
+static void *
+format_cb_pane_dead(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->fd == -1)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_dead_signal. */
+static void *
+format_cb_pane_dead_signal(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+ const char *name;
+
+ if (wp != NULL) {
+ if ((wp->flags & PANE_STATUSREADY) && WIFSIGNALED(wp->status)) {
+ name = sig2name(WTERMSIG(wp->status));
+ return (format_printf("%s", name));
+ }
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for pane_dead_status. */
+static void *
+format_cb_pane_dead_status(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+
+ if (wp != NULL) {
+ if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(wp->status))
+ return (format_printf("%d", WEXITSTATUS(wp->status)));
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for pane_dead_time. */
+static void *
+format_cb_pane_dead_time(struct format_tree *ft)
+{
+ struct window_pane *wp = ft->wp;
+
+ if (wp != NULL) {
+ if (wp->flags & PANE_STATUSDRAWN)
+ return (&wp->dead_time);
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for pane_format. */
+static void *
+format_cb_pane_format(struct format_tree *ft)
+{
+ if (ft->type == FORMAT_TYPE_PANE)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+}
+
+/* Callback for pane_height. */
+static void *
+format_cb_pane_height(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->sy));
+ return (NULL);
+}
+
+/* Callback for pane_id. */
+static void *
+format_cb_pane_id(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%%%u", ft->wp->id));
+ return (NULL);
+}
+
+/* Callback for pane_index. */
+static void *
+format_cb_pane_index(struct format_tree *ft)
+{
+ u_int idx;
+
+ if (ft->wp != NULL && window_pane_index(ft->wp, &idx) == 0)
+ return (format_printf("%u", idx));
+ return (NULL);
+}
+
+/* Callback for pane_input_off. */
+static void *
+format_cb_pane_input_off(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->flags & PANE_INPUTOFF)
+ 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)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_left. */
+static void *
+format_cb_pane_left(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->xoff));
+ return (NULL);
+}
+
+/* Callback for pane_marked. */
+static void *
+format_cb_pane_marked(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (server_check_marked() && marked_pane.wp == ft->wp)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_marked_set. */
+static void *
+format_cb_pane_marked_set(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (server_check_marked())
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_mode. */
+static void *
+format_cb_pane_mode(struct format_tree *ft)
+{
+ struct window_mode_entry *wme;
+
+ if (ft->wp != NULL) {
+ wme = TAILQ_FIRST(&ft->wp->modes);
+ if (wme != NULL)
+ return (xstrdup(wme->mode->name));
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for pane_path. */
+static void *
+format_cb_pane_path(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.path == NULL)
+ return (xstrdup(""));
+ return (xstrdup(ft->wp->base.path));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_pid. */
+static void *
+format_cb_pane_pid(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%ld", (long)ft->wp->pid));
+ return (NULL);
+}
+
+/* Callback for pane_pipe. */
+static void *
+format_cb_pane_pipe(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->pipe_fd != -1)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_right. */
+static void *
+format_cb_pane_right(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->xoff + ft->wp->sx - 1));
+ return (NULL);
+}
+
+/* Callback for pane_search_string. */
+static void *
+format_cb_pane_search_string(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->searchstr == NULL)
+ return (xstrdup(""));
+ return (xstrdup(ft->wp->searchstr));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_synchronized. */
+static void *
+format_cb_pane_synchronized(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (options_get_number(ft->wp->options, "synchronize-panes"))
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for pane_title. */
+static void *
+format_cb_pane_title(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (xstrdup(ft->wp->base.title));
+ return (NULL);
+}
+
+/* Callback for pane_top. */
+static void *
+format_cb_pane_top(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->yoff));
+ return (NULL);
+}
+
+/* Callback for pane_tty. */
+static void *
+format_cb_pane_tty(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (xstrdup(ft->wp->tty));
+ return (NULL);
+}
+
+/* Callback for pane_width. */
+static void *
+format_cb_pane_width(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->sx));
+ return (NULL);
+}
+
+/* Callback for scroll_region_lower. */
+static void *
+format_cb_scroll_region_lower(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.rlower));
+ return (NULL);
+}
+
+/* Callback for scroll_region_upper. */
+static void *
+format_cb_scroll_region_upper(struct format_tree *ft)
+{
+ if (ft->wp != NULL)
+ return (format_printf("%u", ft->wp->base.rupper));
+ return (NULL);
+}
+
+/* Callback for session_attached. */
+static void *
+format_cb_session_attached(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (format_printf("%u", ft->s->attached));
+ return (NULL);
+}
+
+/* Callback for session_format. */
+static void *
+format_cb_session_format(struct format_tree *ft)
+{
+ if (ft->type == FORMAT_TYPE_SESSION)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+}
+
+/* Callback for session_group. */
+static void *
+format_cb_session_group(struct format_tree *ft)
+{
+ struct session_group *sg;
+
+ if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL)
+ return (xstrdup(sg->name));
+ return (NULL);
+}
+
+/* Callback for session_group_attached. */
+static void *
+format_cb_session_group_attached(struct format_tree *ft)
+{
+ struct session_group *sg;
+
+ if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL)
+ return (format_printf("%u", session_group_attached_count (sg)));
+ return (NULL);
+}
+
+/* Callback for session_group_many_attached. */
+static void *
+format_cb_session_group_many_attached(struct format_tree *ft)
+{
+ struct session_group *sg;
+
+ if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) {
+ if (session_group_attached_count (sg) > 1)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for session_group_size. */
+static void *
+format_cb_session_group_size(struct format_tree *ft)
+{
+ struct session_group *sg;
+
+ if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL)
+ return (format_printf("%u", session_group_count (sg)));
+ return (NULL);
+}
+
+/* Callback for session_grouped. */
+static void *
+format_cb_session_grouped(struct format_tree *ft)
+{
+ if (ft->s != NULL) {
+ if (session_group_contains(ft->s) != NULL)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for session_id. */
+static void *
+format_cb_session_id(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (format_printf("$%u", ft->s->id));
+ return (NULL);
+}
+
+/* Callback for session_many_attached. */
+static void *
+format_cb_session_many_attached(struct format_tree *ft)
+{
+ if (ft->s != NULL) {
+ if (ft->s->attached > 1)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for session_marked. */
+static void *
+format_cb_session_marked(struct format_tree *ft)
+{
+ if (ft->s != NULL) {
+ if (server_check_marked() && marked_pane.s == ft->s)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for session_name. */
+static void *
+format_cb_session_name(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (xstrdup(ft->s->name));
+ return (NULL);
+}
+
+/* Callback for session_path. */
+static void *
+format_cb_session_path(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (xstrdup(ft->s->cwd));
+ return (NULL);
+}
+
+/* Callback for session_windows. */
+static void *
+format_cb_session_windows(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (format_printf("%u", winlink_count(&ft->s->windows)));
+ return (NULL);
+}
+
+/* Callback for socket_path. */
+static void *
+format_cb_socket_path(__unused struct format_tree *ft)
+{
+ return (xstrdup(socket_path));
+}
+
+/* Callback for version. */
+static void *
+format_cb_version(__unused struct format_tree *ft)
+{
+ return (xstrdup(getversion()));
+}
+
+/* Callback for active_window_index. */
+static void *
+format_cb_active_window_index(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (format_printf("%u", ft->s->curw->idx));
+ return (NULL);
+}
+
+/* Callback for last_window_index. */
+static void *
+format_cb_last_window_index(struct format_tree *ft)
+{
+ struct winlink *wl;
+
+ if (ft->s != NULL) {
+ wl = RB_MAX(winlinks, &ft->s->windows);
+ return (format_printf("%u", wl->idx));
+ }
+ return (NULL);
+}
+
+/* Callback for window_active. */
+static void *
+format_cb_window_active(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl == ft->wl->session->curw)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_activity_flag. */
+static void *
+format_cb_window_activity_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl->flags & WINLINK_ACTIVITY)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_bell_flag. */
+static void *
+format_cb_window_bell_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl->flags & WINLINK_BELL)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_bigger. */
+static void *
+format_cb_window_bigger(struct format_tree *ft)
+{
+ u_int ox, oy, sx, sy;
+
+ if (ft->c != NULL) {
+ if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy))
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_cell_height. */
+static void *
+format_cb_window_cell_height(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("%u", ft->w->ypixel));
+ return (NULL);
+}
+
+/* Callback for window_cell_width. */
+static void *
+format_cb_window_cell_width(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("%u", ft->w->xpixel));
+ return (NULL);
+}
+
+/* Callback for window_end_flag. */
+static void *
+format_cb_window_end_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl == RB_MAX(winlinks, &ft->wl->session->windows))
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_flags. */
+static void *
+format_cb_window_flags(struct format_tree *ft)
+{
+ if (ft->wl != NULL)
+ return (xstrdup(window_printable_flags(ft->wl, 1)));
+ return (NULL);
+}
+
+/* Callback for window_format. */
+static void *
+format_cb_window_format(struct format_tree *ft)
+{
+ if (ft->type == FORMAT_TYPE_WINDOW)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+}
+
+/* Callback for window_height. */
+static void *
+format_cb_window_height(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("%u", ft->w->sy));
+ return (NULL);
+}
+
+/* Callback for window_id. */
+static void *
+format_cb_window_id(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("@%u", ft->w->id));
+ return (NULL);
+}
+
+/* Callback for window_index. */
+static void *
+format_cb_window_index(struct format_tree *ft)
+{
+ if (ft->wl != NULL)
+ return (format_printf("%d", ft->wl->idx));
+ return (NULL);
+}
+
+/* Callback for window_last_flag. */
+static void *
+format_cb_window_last_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl == TAILQ_FIRST(&ft->wl->session->lastw))
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_linked. */
+static void *
+format_cb_window_linked(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (session_is_linked(ft->wl->session, ft->wl->window))
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_linked_sessions. */
+static void *
+format_cb_window_linked_sessions(struct format_tree *ft)
+{
+ if (ft->wl != NULL)
+ return (format_printf("%u", ft->wl->window->references));
+ return (NULL);
+}
+
+/* Callback for window_marked_flag. */
+static void *
+format_cb_window_marked_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (server_check_marked() && marked_pane.wl == ft->wl)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_name. */
+static void *
+format_cb_window_name(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("%s", ft->w->name));
+ return (NULL);
+}
+
+/* Callback for window_offset_x. */
+static void *
+format_cb_window_offset_x(struct format_tree *ft)
+{
+ u_int ox, oy, sx, sy;
+
+ if (ft->c != NULL) {
+ if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy))
+ return (format_printf("%u", ox));
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for window_offset_y. */
+static void *
+format_cb_window_offset_y(struct format_tree *ft)
+{
+ u_int ox, oy, sx, sy;
+
+ if (ft->c != NULL) {
+ if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy))
+ return (format_printf("%u", oy));
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Callback for window_panes. */
+static void *
+format_cb_window_panes(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("%u", window_count_panes(ft->w)));
+ return (NULL);
+}
+
+/* Callback for window_raw_flags. */
+static void *
+format_cb_window_raw_flags(struct format_tree *ft)
+{
+ if (ft->wl != NULL)
+ return (xstrdup(window_printable_flags(ft->wl, 0)));
+ return (NULL);
+}
+
+/* Callback for window_silence_flag. */
+static void *
+format_cb_window_silence_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl->flags & WINLINK_SILENCE)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_start_flag. */
+static void *
+format_cb_window_start_flag(struct format_tree *ft)
+{
+ if (ft->wl != NULL) {
+ if (ft->wl == RB_MIN(winlinks, &ft->wl->session->windows))
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for window_width. */
+static void *
+format_cb_window_width(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (format_printf("%u", ft->w->sx));
+ return (NULL);
+}
+
+/* Callback for window_zoomed_flag. */
+static void *
+format_cb_window_zoomed_flag(struct format_tree *ft)
+{
+ if (ft->w != NULL) {
+ if (ft->w->flags & WINDOW_ZOOMED)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for wrap_flag. */
+static void *
+format_cb_wrap_flag(struct format_tree *ft)
+{
+ if (ft->wp != NULL) {
+ if (ft->wp->base.mode & MODE_WRAP)
+ return (xstrdup("1"));
+ return (xstrdup("0"));
+ }
+ return (NULL);
+}
+
+/* Callback for buffer_created. */
+static void *
+format_cb_buffer_created(struct format_tree *ft)
+{
+ static struct timeval tv;
+
+ if (ft->pb != NULL) {
+ timerclear(&tv);
+ tv.tv_sec = paste_buffer_created(ft->pb);
+ return (&tv);
+ }
+ return (NULL);
+}
+
+/* Callback for client_activity. */
+static void *
+format_cb_client_activity(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (&ft->c->activity_time);
+ return (NULL);
+}
+
+/* Callback for client_created. */
+static void *
+format_cb_client_created(struct format_tree *ft)
+{
+ if (ft->c != NULL)
+ return (&ft->c->creation_time);
+ return (NULL);
+}
+
+/* Callback for session_activity. */
+static void *
+format_cb_session_activity(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (&ft->s->activity_time);
+ return (NULL);
+}
+
+/* Callback for session_created. */
+static void *
+format_cb_session_created(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (&ft->s->creation_time);
+ return (NULL);
+}
+
+/* Callback for session_last_attached. */
+static void *
+format_cb_session_last_attached(struct format_tree *ft)
+{
+ if (ft->s != NULL)
+ return (&ft->s->last_attached_time);
+ return (NULL);
+}
+
+/* Callback for start_time. */
+static void *
+format_cb_start_time(__unused struct format_tree *ft)
+{
+ return (&start_time);
+}
+
+/* Callback for window_activity. */
+static void *
+format_cb_window_activity(struct format_tree *ft)
+{
+ if (ft->w != NULL)
+ return (&ft->w->activity_time);
+ return (NULL);
+}
+
+/* Callback for buffer_mode_format, */
+static void *
+format_cb_buffer_mode_format(__unused struct format_tree *ft)
+{
+ return (xstrdup(window_buffer_mode.default_format));
+}
+
+/* Callback for client_mode_format, */
+static void *
+format_cb_client_mode_format(__unused struct format_tree *ft)
+{
+ return (xstrdup(window_client_mode.default_format));
+}
+
+/* Callback for tree_mode_format, */
+static void *
+format_cb_tree_mode_format(__unused struct format_tree *ft)
+{
+ return (xstrdup(window_tree_mode.default_format));
+}
+
+/* Callback for uid. */
+static void *
+format_cb_uid(__unused struct format_tree *ft)
+{
+ return (format_printf("%ld", (long)getuid()));
+}
+
+/* Callback for user. */
+static void *
+format_cb_user(__unused struct format_tree *ft)
+{
+ struct passwd *pw;
+
+ if ((pw = getpwuid(getuid())) != NULL)
+ return (xstrdup(pw->pw_name));
+ return (NULL);
+}
+
+/* Format table type. */
+enum format_table_type {
+ FORMAT_TABLE_STRING,
+ FORMAT_TABLE_TIME
+};
+
+/* Format table entry. */
+struct format_table_entry {
+ const char *key;
+ enum format_table_type type;
+ format_cb cb;
+};
+
+/*
+ * Format table. Default format variables (that are almost always in the tree
+ * and where the value is expanded by a callback in this file) are listed here.
+ * Only variables which are added by the caller go into the tree.
+ */
+static const struct format_table_entry format_table[] = {
+ { "active_window_index", FORMAT_TABLE_STRING,
+ format_cb_active_window_index
+ },
+ { "alternate_on", FORMAT_TABLE_STRING,
+ format_cb_alternate_on
+ },
+ { "alternate_saved_x", FORMAT_TABLE_STRING,
+ format_cb_alternate_saved_x
+ },
+ { "alternate_saved_y", FORMAT_TABLE_STRING,
+ format_cb_alternate_saved_y
+ },
+ { "buffer_created", FORMAT_TABLE_TIME,
+ format_cb_buffer_created
+ },
+ { "buffer_mode_format", FORMAT_TABLE_STRING,
+ format_cb_buffer_mode_format
+ },
+ { "buffer_name", FORMAT_TABLE_STRING,
+ format_cb_buffer_name
+ },
+ { "buffer_sample", FORMAT_TABLE_STRING,
+ format_cb_buffer_sample
+ },
+ { "buffer_size", FORMAT_TABLE_STRING,
+ format_cb_buffer_size
+ },
+ { "client_activity", FORMAT_TABLE_TIME,
+ format_cb_client_activity
+ },
+ { "client_cell_height", FORMAT_TABLE_STRING,
+ format_cb_client_cell_height
+ },
+ { "client_cell_width", FORMAT_TABLE_STRING,
+ format_cb_client_cell_width
+ },
+ { "client_control_mode", FORMAT_TABLE_STRING,
+ format_cb_client_control_mode
+ },
+ { "client_created", FORMAT_TABLE_TIME,
+ format_cb_client_created
+ },
+ { "client_discarded", FORMAT_TABLE_STRING,
+ format_cb_client_discarded
+ },
+ { "client_flags", FORMAT_TABLE_STRING,
+ format_cb_client_flags
+ },
+ { "client_height", FORMAT_TABLE_STRING,
+ format_cb_client_height
+ },
+ { "client_key_table", FORMAT_TABLE_STRING,
+ format_cb_client_key_table
+ },
+ { "client_last_session", FORMAT_TABLE_STRING,
+ format_cb_client_last_session
+ },
+ { "client_mode_format", FORMAT_TABLE_STRING,
+ format_cb_client_mode_format
+ },
+ { "client_name", FORMAT_TABLE_STRING,
+ format_cb_client_name
+ },
+ { "client_pid", FORMAT_TABLE_STRING,
+ format_cb_client_pid
+ },
+ { "client_prefix", FORMAT_TABLE_STRING,
+ format_cb_client_prefix
+ },
+ { "client_readonly", FORMAT_TABLE_STRING,
+ format_cb_client_readonly
+ },
+ { "client_session", FORMAT_TABLE_STRING,
+ format_cb_client_session
+ },
+ { "client_termfeatures", FORMAT_TABLE_STRING,
+ format_cb_client_termfeatures
+ },
+ { "client_termname", FORMAT_TABLE_STRING,
+ format_cb_client_termname
+ },
+ { "client_termtype", FORMAT_TABLE_STRING,
+ format_cb_client_termtype
+ },
+ { "client_tty", FORMAT_TABLE_STRING,
+ format_cb_client_tty
+ },
+ { "client_uid", FORMAT_TABLE_STRING,
+ format_cb_client_uid
+ },
+ { "client_user", FORMAT_TABLE_STRING,
+ format_cb_client_user
+ },
+ { "client_utf8", FORMAT_TABLE_STRING,
+ format_cb_client_utf8
+ },
+ { "client_width", FORMAT_TABLE_STRING,
+ format_cb_client_width
+ },
+ { "client_written", FORMAT_TABLE_STRING,
+ format_cb_client_written
+ },
+ { "config_files", FORMAT_TABLE_STRING,
+ format_cb_config_files
+ },
+ { "cursor_character", FORMAT_TABLE_STRING,
+ format_cb_cursor_character
+ },
+ { "cursor_flag", FORMAT_TABLE_STRING,
+ format_cb_cursor_flag
+ },
+ { "cursor_x", FORMAT_TABLE_STRING,
+ format_cb_cursor_x
+ },
+ { "cursor_y", FORMAT_TABLE_STRING,
+ format_cb_cursor_y
+ },
+ { "history_all_bytes", FORMAT_TABLE_STRING,
+ format_cb_history_all_bytes
+ },
+ { "history_bytes", FORMAT_TABLE_STRING,
+ format_cb_history_bytes
+ },
+ { "history_limit", FORMAT_TABLE_STRING,
+ format_cb_history_limit
+ },
+ { "history_size", FORMAT_TABLE_STRING,
+ format_cb_history_size
+ },
+ { "host", FORMAT_TABLE_STRING,
+ format_cb_host
+ },
+ { "host_short", FORMAT_TABLE_STRING,
+ format_cb_host_short
+ },
+ { "insert_flag", FORMAT_TABLE_STRING,
+ format_cb_insert_flag
+ },
+ { "keypad_cursor_flag", FORMAT_TABLE_STRING,
+ format_cb_keypad_cursor_flag
+ },
+ { "keypad_flag", FORMAT_TABLE_STRING,
+ format_cb_keypad_flag
+ },
+ { "last_window_index", FORMAT_TABLE_STRING,
+ format_cb_last_window_index
+ },
+ { "mouse_all_flag", FORMAT_TABLE_STRING,
+ format_cb_mouse_all_flag
+ },
+ { "mouse_any_flag", FORMAT_TABLE_STRING,
+ format_cb_mouse_any_flag
+ },
+ { "mouse_button_flag", FORMAT_TABLE_STRING,
+ format_cb_mouse_button_flag
+ },
+ { "mouse_line", FORMAT_TABLE_STRING,
+ format_cb_mouse_line
+ },
+ { "mouse_pane", FORMAT_TABLE_STRING,
+ format_cb_mouse_pane
+ },
+ { "mouse_sgr_flag", FORMAT_TABLE_STRING,
+ format_cb_mouse_sgr_flag
+ },
+ { "mouse_standard_flag", FORMAT_TABLE_STRING,
+ format_cb_mouse_standard_flag
+ },
+ { "mouse_utf8_flag", FORMAT_TABLE_STRING,
+ format_cb_mouse_utf8_flag
+ },
+ { "mouse_word", FORMAT_TABLE_STRING,
+ format_cb_mouse_word
+ },
+ { "mouse_x", FORMAT_TABLE_STRING,
+ format_cb_mouse_x
+ },
+ { "mouse_y", FORMAT_TABLE_STRING,
+ format_cb_mouse_y
+ },
+ { "next_session_id", FORMAT_TABLE_STRING,
+ format_cb_next_session_id
+ },
+ { "origin_flag", FORMAT_TABLE_STRING,
+ format_cb_origin_flag
+ },
+ { "pane_active", FORMAT_TABLE_STRING,
+ format_cb_pane_active
+ },
+ { "pane_at_bottom", FORMAT_TABLE_STRING,
+ format_cb_pane_at_bottom
+ },
+ { "pane_at_left", FORMAT_TABLE_STRING,
+ format_cb_pane_at_left
+ },
+ { "pane_at_right", FORMAT_TABLE_STRING,
+ format_cb_pane_at_right
+ },
+ { "pane_at_top", FORMAT_TABLE_STRING,
+ format_cb_pane_at_top
+ },
+ { "pane_bg", FORMAT_TABLE_STRING,
+ format_cb_pane_bg
+ },
+ { "pane_bottom", FORMAT_TABLE_STRING,
+ format_cb_pane_bottom
+ },
+ { "pane_current_command", FORMAT_TABLE_STRING,
+ format_cb_current_command
+ },
+ { "pane_current_path", FORMAT_TABLE_STRING,
+ format_cb_current_path
+ },
+ { "pane_dead", FORMAT_TABLE_STRING,
+ format_cb_pane_dead
+ },
+ { "pane_dead_signal", FORMAT_TABLE_STRING,
+ format_cb_pane_dead_signal
+ },
+ { "pane_dead_status", FORMAT_TABLE_STRING,
+ format_cb_pane_dead_status
+ },
+ { "pane_dead_time", FORMAT_TABLE_TIME,
+ format_cb_pane_dead_time
+ },
+ { "pane_fg", FORMAT_TABLE_STRING,
+ format_cb_pane_fg
+ },
+ { "pane_format", FORMAT_TABLE_STRING,
+ format_cb_pane_format
+ },
+ { "pane_height", FORMAT_TABLE_STRING,
+ format_cb_pane_height
+ },
+ { "pane_id", FORMAT_TABLE_STRING,
+ format_cb_pane_id
+ },
+ { "pane_in_mode", FORMAT_TABLE_STRING,
+ format_cb_pane_in_mode
+ },
+ { "pane_index", FORMAT_TABLE_STRING,
+ format_cb_pane_index
+ },
+ { "pane_input_off", FORMAT_TABLE_STRING,
+ format_cb_pane_input_off
+ },
+ { "pane_last", FORMAT_TABLE_STRING,
+ format_cb_pane_last
+ },
+ { "pane_left", FORMAT_TABLE_STRING,
+ format_cb_pane_left
+ },
+ { "pane_marked", FORMAT_TABLE_STRING,
+ format_cb_pane_marked
+ },
+ { "pane_marked_set", FORMAT_TABLE_STRING,
+ format_cb_pane_marked_set
+ },
+ { "pane_mode", FORMAT_TABLE_STRING,
+ format_cb_pane_mode
+ },
+ { "pane_path", FORMAT_TABLE_STRING,
+ format_cb_pane_path
+ },
+ { "pane_pid", FORMAT_TABLE_STRING,
+ format_cb_pane_pid
+ },
+ { "pane_pipe", FORMAT_TABLE_STRING,
+ format_cb_pane_pipe
+ },
+ { "pane_right", FORMAT_TABLE_STRING,
+ format_cb_pane_right
+ },
+ { "pane_search_string", FORMAT_TABLE_STRING,
+ format_cb_pane_search_string
+ },
+ { "pane_start_command", FORMAT_TABLE_STRING,
+ format_cb_start_command
+ },
+ { "pane_start_path", FORMAT_TABLE_STRING,
+ format_cb_start_path
+ },
+ { "pane_synchronized", FORMAT_TABLE_STRING,
+ format_cb_pane_synchronized
+ },
+ { "pane_tabs", FORMAT_TABLE_STRING,
+ format_cb_pane_tabs
+ },
+ { "pane_title", FORMAT_TABLE_STRING,
+ format_cb_pane_title
+ },
+ { "pane_top", FORMAT_TABLE_STRING,
+ format_cb_pane_top
+ },
+ { "pane_tty", FORMAT_TABLE_STRING,
+ format_cb_pane_tty
+ },
+ { "pane_width", FORMAT_TABLE_STRING,
+ format_cb_pane_width
+ },
+ { "pid", FORMAT_TABLE_STRING,
+ format_cb_pid
+ },
+ { "scroll_region_lower", FORMAT_TABLE_STRING,
+ format_cb_scroll_region_lower
+ },
+ { "scroll_region_upper", FORMAT_TABLE_STRING,
+ format_cb_scroll_region_upper
+ },
+ { "session_activity", FORMAT_TABLE_TIME,
+ format_cb_session_activity
+ },
+ { "session_alerts", FORMAT_TABLE_STRING,
+ format_cb_session_alerts
+ },
+ { "session_attached", FORMAT_TABLE_STRING,
+ format_cb_session_attached
+ },
+ { "session_attached_list", FORMAT_TABLE_STRING,
+ format_cb_session_attached_list
+ },
+ { "session_created", FORMAT_TABLE_TIME,
+ format_cb_session_created
+ },
+ { "session_format", FORMAT_TABLE_STRING,
+ format_cb_session_format
+ },
+ { "session_group", FORMAT_TABLE_STRING,
+ format_cb_session_group
+ },
+ { "session_group_attached", FORMAT_TABLE_STRING,
+ format_cb_session_group_attached
+ },
+ { "session_group_attached_list", FORMAT_TABLE_STRING,
+ format_cb_session_group_attached_list
+ },
+ { "session_group_list", FORMAT_TABLE_STRING,
+ format_cb_session_group_list
+ },
+ { "session_group_many_attached", FORMAT_TABLE_STRING,
+ format_cb_session_group_many_attached
+ },
+ { "session_group_size", FORMAT_TABLE_STRING,
+ format_cb_session_group_size
+ },
+ { "session_grouped", FORMAT_TABLE_STRING,
+ format_cb_session_grouped
+ },
+ { "session_id", FORMAT_TABLE_STRING,
+ format_cb_session_id
+ },
+ { "session_last_attached", FORMAT_TABLE_TIME,
+ format_cb_session_last_attached
+ },
+ { "session_many_attached", FORMAT_TABLE_STRING,
+ format_cb_session_many_attached
+ },
+ { "session_marked", FORMAT_TABLE_STRING,
+ format_cb_session_marked,
+ },
+ { "session_name", FORMAT_TABLE_STRING,
+ format_cb_session_name
+ },
+ { "session_path", FORMAT_TABLE_STRING,
+ format_cb_session_path
+ },
+ { "session_stack", FORMAT_TABLE_STRING,
+ format_cb_session_stack
+ },
+ { "session_windows", FORMAT_TABLE_STRING,
+ format_cb_session_windows
+ },
+ { "socket_path", FORMAT_TABLE_STRING,
+ format_cb_socket_path
+ },
+ { "start_time", FORMAT_TABLE_TIME,
+ format_cb_start_time
+ },
+ { "tree_mode_format", FORMAT_TABLE_STRING,
+ format_cb_tree_mode_format
+ },
+ { "uid", FORMAT_TABLE_STRING,
+ format_cb_uid
+ },
+ { "user", FORMAT_TABLE_STRING,
+ format_cb_user
+ },
+ { "version", FORMAT_TABLE_STRING,
+ format_cb_version
+ },
+ { "window_active", FORMAT_TABLE_STRING,
+ format_cb_window_active
+ },
+ { "window_active_clients", FORMAT_TABLE_STRING,
+ format_cb_window_active_clients
+ },
+ { "window_active_clients_list", FORMAT_TABLE_STRING,
+ format_cb_window_active_clients_list
+ },
+ { "window_active_sessions", FORMAT_TABLE_STRING,
+ format_cb_window_active_sessions
+ },
+ { "window_active_sessions_list", FORMAT_TABLE_STRING,
+ format_cb_window_active_sessions_list
+ },
+ { "window_activity", FORMAT_TABLE_TIME,
+ format_cb_window_activity
+ },
+ { "window_activity_flag", FORMAT_TABLE_STRING,
+ format_cb_window_activity_flag
+ },
+ { "window_bell_flag", FORMAT_TABLE_STRING,
+ format_cb_window_bell_flag
+ },
+ { "window_bigger", FORMAT_TABLE_STRING,
+ format_cb_window_bigger
+ },
+ { "window_cell_height", FORMAT_TABLE_STRING,
+ format_cb_window_cell_height
+ },
+ { "window_cell_width", FORMAT_TABLE_STRING,
+ format_cb_window_cell_width
+ },
+ { "window_end_flag", FORMAT_TABLE_STRING,
+ format_cb_window_end_flag
+ },
+ { "window_flags", FORMAT_TABLE_STRING,
+ format_cb_window_flags
+ },
+ { "window_format", FORMAT_TABLE_STRING,
+ format_cb_window_format
+ },
+ { "window_height", FORMAT_TABLE_STRING,
+ format_cb_window_height
+ },
+ { "window_id", FORMAT_TABLE_STRING,
+ format_cb_window_id
+ },
+ { "window_index", FORMAT_TABLE_STRING,
+ format_cb_window_index
+ },
+ { "window_last_flag", FORMAT_TABLE_STRING,
+ format_cb_window_last_flag
+ },
+ { "window_layout", FORMAT_TABLE_STRING,
+ format_cb_window_layout
+ },
+ { "window_linked", FORMAT_TABLE_STRING,
+ format_cb_window_linked
+ },
+ { "window_linked_sessions", FORMAT_TABLE_STRING,
+ format_cb_window_linked_sessions
+ },
+ { "window_linked_sessions_list", FORMAT_TABLE_STRING,
+ format_cb_window_linked_sessions_list
+ },
+ { "window_marked_flag", FORMAT_TABLE_STRING,
+ format_cb_window_marked_flag
+ },
+ { "window_name", FORMAT_TABLE_STRING,
+ format_cb_window_name
+ },
+ { "window_offset_x", FORMAT_TABLE_STRING,
+ format_cb_window_offset_x
+ },
+ { "window_offset_y", FORMAT_TABLE_STRING,
+ format_cb_window_offset_y
+ },
+ { "window_panes", FORMAT_TABLE_STRING,
+ format_cb_window_panes
+ },
+ { "window_raw_flags", FORMAT_TABLE_STRING,
+ format_cb_window_raw_flags
+ },
+ { "window_silence_flag", FORMAT_TABLE_STRING,
+ format_cb_window_silence_flag
+ },
+ { "window_stack_index", FORMAT_TABLE_STRING,
+ format_cb_window_stack_index
+ },
+ { "window_start_flag", FORMAT_TABLE_STRING,
+ format_cb_window_start_flag
+ },
+ { "window_visible_layout", FORMAT_TABLE_STRING,
+ format_cb_window_visible_layout
+ },
+ { "window_width", FORMAT_TABLE_STRING,
+ format_cb_window_width
+ },
+ { "window_zoomed_flag", FORMAT_TABLE_STRING,
+ format_cb_window_zoomed_flag
+ },
+ { "wrap_flag", FORMAT_TABLE_STRING,
+ format_cb_wrap_flag
+ }
+};
+
+/* Compare format table entries. */
+static int
+format_table_compare(const void *key0, const void *entry0)
+{
+ const char *key = key0;
+ const struct format_table_entry *entry = entry0;
+
+ return (strcmp(key, entry->key));
+}
+
+/* Get a format callback. */
+static struct format_table_entry *
+format_table_get(const char *key)
+{
+ return (bsearch(key, format_table, nitems(format_table),
+ sizeof *format_table, format_table_compare));
+}
+
+/* Merge one format tree into another. */
+void
+format_merge(struct format_tree *ft, struct format_tree *from)
+{
+ struct format_entry *fe;
+
+ RB_FOREACH(fe, format_entry_tree, &from->tree) {
+ if (fe->value != NULL)
+ format_add(ft, fe->key, "%s", fe->value);
+ }
+}
+
+/* Get format pane. */
+struct window_pane *
+format_get_pane(struct format_tree *ft)
+{
+ return (ft->wp);
+}
+
+/* Add item bits to tree. */
+static void
+format_create_add_item(struct format_tree *ft, struct cmdq_item *item)
+{
+ struct key_event *event = cmdq_get_event(item);
+ struct mouse_event *m = &event->m;
+
+ cmdq_merge_formats(item, ft);
+ memcpy(&ft->m, m, sizeof ft->m);
+}
+
+/* Create a new tree. */
+struct format_tree *
+format_create(struct client *c, struct cmdq_item *item, int tag, int flags)
+{
+ struct format_tree *ft;
+
+ ft = xcalloc(1, sizeof *ft);
+ RB_INIT(&ft->tree);
+
+ if (c != NULL) {
+ ft->client = c;
+ ft->client->references++;
+ }
+ ft->item = item;
+
+ ft->tag = tag;
+ ft->flags = flags;
+
+ if (item != NULL)
+ format_create_add_item(ft, item);
+
+ return (ft);
+}
+
+/* Free a tree. */
+void
+format_free(struct format_tree *ft)
+{
+ struct format_entry *fe, *fe1;
+
+ RB_FOREACH_SAFE(fe, format_entry_tree, &ft->tree, fe1) {
+ RB_REMOVE(format_entry_tree, &ft->tree, fe);
+ free(fe->value);
+ free(fe->key);
+ free(fe);
+ }
+
+ if (ft->client != NULL)
+ server_client_unref(ft->client);
+ free(ft);
+}
+
+/* Log each format. */
+static void
+format_log_debug_cb(const char *key, const char *value, void *arg)
+{
+ const char *prefix = arg;
+
+ log_debug("%s: %s=%s", prefix, key, value);
+}
+
+/* Log a format tree. */
+void
+format_log_debug(struct format_tree *ft, const char *prefix)
+{
+ format_each(ft, format_log_debug_cb, (void *)prefix);
+}
+
+/* Walk each format. */
+void
+format_each(struct format_tree *ft, void (*cb)(const char *, const char *,
+ void *), void *arg)
+{
+ const struct format_table_entry *fte;
+ struct format_entry *fe;
+ u_int i;
+ char s[64];
+ void *value;
+ struct timeval *tv;
+
+ for (i = 0; i < nitems(format_table); i++) {
+ fte = &format_table[i];
+
+ value = fte->cb(ft);
+ if (value == NULL)
+ continue;
+ if (fte->type == FORMAT_TABLE_TIME) {
+ tv = value;
+ xsnprintf(s, sizeof s, "%lld", (long long)tv->tv_sec);
+ cb(fte->key, s, arg);
+ } else {
+ cb(fte->key, value, arg);
+ free(value);
+ }
+ }
+ RB_FOREACH(fe, format_entry_tree, &ft->tree) {
+ if (fe->time != 0) {
+ xsnprintf(s, sizeof s, "%lld", (long long)fe->time);
+ cb(fe->key, s, arg);
+ } else {
+ if (fe->value == NULL && fe->cb != NULL) {
+ fe->value = fe->cb(ft);
+ if (fe->value == NULL)
+ fe->value = xstrdup("");
+ }
+ cb(fe->key, fe->value, arg);
+ }
+ }
+}
+
+/* Add a key-value pair. */
+void
+format_add(struct format_tree *ft, const char *key, const char *fmt, ...)
+{
+ struct format_entry *fe;
+ struct format_entry *fe_now;
+ va_list ap;
+
+ fe = xmalloc(sizeof *fe);
+ fe->key = xstrdup(key);
+
+ fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe);
+ if (fe_now != NULL) {
+ free(fe->key);
+ free(fe);
+ free(fe_now->value);
+ fe = fe_now;
+ }
+
+ fe->cb = NULL;
+ fe->time = 0;
+
+ va_start(ap, fmt);
+ xvasprintf(&fe->value, fmt, ap);
+ va_end(ap);
+}
+
+/* Add a key and time. */
+void
+format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv)
+{
+ struct format_entry *fe, *fe_now;
+
+ fe = xmalloc(sizeof *fe);
+ fe->key = xstrdup(key);
+
+ fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe);
+ if (fe_now != NULL) {
+ free(fe->key);
+ free(fe);
+ free(fe_now->value);
+ fe = fe_now;
+ }
+
+ fe->cb = NULL;
+ fe->time = tv->tv_sec;
+
+ fe->value = NULL;
+}
+
+/* Add a key and function. */
+void
+format_add_cb(struct format_tree *ft, const char *key, format_cb cb)
+{
+ struct format_entry *fe;
+ struct format_entry *fe_now;
+
+ fe = xmalloc(sizeof *fe);
+ fe->key = xstrdup(key);
+
+ fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe);
+ if (fe_now != NULL) {
+ free(fe->key);
+ free(fe);
+ free(fe_now->value);
+ fe = fe_now;
+ }
+
+ fe->cb = cb;
+ fe->time = 0;
+
+ fe->value = NULL;
+}
+
+/* Quote shell special characters in string. */
+static char *
+format_quote_shell(const char *s)
+{
+ const char *cp;
+ char *out, *at;
+
+ at = out = xmalloc(strlen(s) * 2 + 1);
+ for (cp = s; *cp != '\0'; cp++) {
+ if (strchr("|&;<>()$`\\\"'*?[# =%", *cp) != NULL)
+ *at++ = '\\';
+ *at++ = *cp;
+ }
+ *at = '\0';
+ return (out);
+}
+
+/* Quote #s in string. */
+static char *
+format_quote_style(const char *s)
+{
+ const char *cp;
+ char *out, *at;
+
+ at = out = xmalloc(strlen(s) * 2 + 1);
+ for (cp = s; *cp != '\0'; cp++) {
+ if (*cp == '#')
+ *at++ = '#';
+ *at++ = *cp;
+ }
+ *at = '\0';
+ return (out);
+}
+
+/* Make a prettier time. */
+static char *
+format_pretty_time(time_t t)
+{
+ struct tm now_tm, tm;
+ time_t now, age;
+ char s[6];
+
+ time(&now);
+ if (now < t)
+ now = t;
+ age = now - t;
+
+ localtime_r(&now, &now_tm);
+ localtime_r(&t, &tm);
+
+ /* Last 24 hours. */
+ if (age < 24 * 3600) {
+ strftime(s, sizeof s, "%H:%M", &tm);
+ return (xstrdup(s));
+ }
+
+ /* This month or last 28 days. */
+ if ((tm.tm_year == now_tm.tm_year && tm.tm_mon == now_tm.tm_mon) ||
+ age < 28 * 24 * 3600) {
+ strftime(s, sizeof s, "%a%d", &tm);
+ return (xstrdup(s));
+ }
+
+ /* Last 12 months. */
+ if ((tm.tm_year == now_tm.tm_year && tm.tm_mon < now_tm.tm_mon) ||
+ (tm.tm_year == now_tm.tm_year - 1 && tm.tm_mon > now_tm.tm_mon)) {
+ strftime(s, sizeof s, "%d%b", &tm);
+ return (xstrdup(s));
+ }
+
+ /* Older than that. */
+ strftime(s, sizeof s, "%h%y", &tm);
+ return (xstrdup(s));
+}
+
+/* Find a format entry. */
+static char *
+format_find(struct format_tree *ft, const char *key, int modifiers,
+ const char *time_format)
+{
+ struct format_table_entry *fte;
+ void *value;
+ struct format_entry *fe, fe_find;
+ struct environ_entry *envent;
+ struct options_entry *o;
+ int idx;
+ char *found = NULL, *saved, s[512];
+ const char *errstr;
+ time_t t = 0;
+ struct tm tm;
+
+ o = options_parse_get(global_options, key, &idx, 0);
+ if (o == NULL && ft->wp != NULL)
+ o = options_parse_get(ft->wp->options, key, &idx, 0);
+ if (o == NULL && ft->w != NULL)
+ o = options_parse_get(ft->w->options, key, &idx, 0);
+ if (o == NULL)
+ o = options_parse_get(global_w_options, key, &idx, 0);
+ if (o == NULL && ft->s != NULL)
+ o = options_parse_get(ft->s->options, key, &idx, 0);
+ if (o == NULL)
+ o = options_parse_get(global_s_options, key, &idx, 0);
+ if (o != NULL) {
+ found = options_to_string(o, idx, 1);
+ goto found;
+ }
+
+ fte = format_table_get(key);
+ if (fte != NULL) {
+ value = fte->cb(ft);
+ if (fte->type == FORMAT_TABLE_TIME && value != NULL)
+ t = ((struct timeval *)value)->tv_sec;
+ else
+ found = value;
+ goto found;
+ }
+ fe_find.key = (char *)key;
+ fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find);
+ if (fe != NULL) {
+ if (fe->time != 0) {
+ t = fe->time;
+ goto found;
+ }
+ if (fe->value == NULL && fe->cb != NULL) {
+ fe->value = fe->cb(ft);
+ if (fe->value == NULL)
+ fe->value = xstrdup("");
+ }
+ found = xstrdup(fe->value);
+ goto found;
+ }
+
+ if (~modifiers & FORMAT_TIMESTRING) {
+ envent = NULL;
+ if (ft->s != NULL)
+ envent = environ_find(ft->s->environ, key);
+ if (envent == NULL)
+ envent = environ_find(global_environ, key);
+ if (envent != NULL && envent->value != NULL) {
+ found = xstrdup(envent->value);
+ goto found;
+ }
+ }
+
+ return (NULL);
+
+found:
+ if (modifiers & FORMAT_TIMESTRING) {
+ if (t == 0 && found != NULL) {
+ t = strtonum(found, 0, INT64_MAX, &errstr);
+ if (errstr != NULL)
+ t = 0;
+ free(found);
+ }
+ if (t == 0)
+ return (NULL);
+ if (modifiers & FORMAT_PRETTY)
+ found = format_pretty_time(t);
+ else {
+ if (time_format != NULL) {
+ localtime_r(&t, &tm);
+ strftime(s, sizeof s, time_format, &tm);
+ } else {
+ ctime_r(&t, s);
+ s[strcspn(s, "\n")] = '\0';
+ }
+ found = xstrdup(s);
+ }
+ return (found);
+ }
+
+ if (t != 0)
+ xasprintf(&found, "%lld", (long long)t);
+ else if (found == NULL)
+ return (NULL);
+ if (modifiers & FORMAT_BASENAME) {
+ saved = found;
+ found = xstrdup(basename(saved));
+ free(saved);
+ }
+ if (modifiers & FORMAT_DIRNAME) {
+ saved = found;
+ found = xstrdup(dirname(saved));
+ free(saved);
+ }
+ if (modifiers & FORMAT_QUOTE_SHELL) {
+ saved = found;
+ found = xstrdup(format_quote_shell(saved));
+ free(saved);
+ }
+ if (modifiers & FORMAT_QUOTE_STYLE) {
+ saved = found;
+ found = xstrdup(format_quote_style(saved));
+ free(saved);
+ }
+ return (found);
+}
+
+/* Remove escaped characters from string. */
+static char *
+format_strip(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 (*s == '#' && strchr(",#{}:", s[1]) != NULL) {
+ if (brackets != 0)
+ *cp++ = *s;
+ continue;
+ }
+ if (*s == '}')
+ brackets--;
+ *cp++ = *s;
+ }
+ *cp = '\0';
+ return (out);
+}
+
+/* Skip until end. */
+const char *
+format_skip(const char *s, const char *end)
+{
+ int brackets = 0;
+
+ for (; *s != '\0'; s++) {
+ if (*s == '#' && s[1] == '{')
+ brackets++;
+ if (*s == '#' && strchr(",#{}:", s[1]) != NULL) {
+ s++;
+ continue;
+ }
+ if (*s == '}')
+ brackets--;
+ if (strchr(end, *s) != NULL && brackets == 0)
+ break;
+ }
+ if (*s == '\0')
+ return (NULL);
+ return (s);
+}
+
+/* Return left and right alternatives separated by commas. */
+static int
+format_choose(struct format_expand_state *es, const char *s, char **left,
+ char **right, int expand)
+{
+ const char *cp;
+ char *left0, *right0;
+
+ cp = format_skip(s, ",");
+ if (cp == NULL)
+ return (-1);
+ left0 = xstrndup(s, cp - s);
+ right0 = xstrdup(cp + 1);
+
+ if (expand) {
+ *left = format_expand1(es, left0);
+ free(left0);
+ *right = format_expand1(es, right0);
+ free(right0);
+ } else {
+ *left = left0;
+ *right = right0;
+ }
+ return (0);
+}
+
+/* Is this true? */
+int
+format_true(const char *s)
+{
+ if (s != NULL && *s != '\0' && (s[0] != '0' || s[1] != '\0'))
+ return (1);
+ return (0);
+}
+
+/* Check if modifier end. */
+static int
+format_is_end(char c)
+{
+ return (c == ';' || c == ':');
+}
+
+/* Add to modifier list. */
+static void
+format_add_modifier(struct format_modifier **list, u_int *count,
+ const char *c, size_t n, char **argv, int argc)
+{
+ struct format_modifier *fm;
+
+ *list = xreallocarray(*list, (*count) + 1, sizeof **list);
+ fm = &(*list)[(*count)++];
+
+ memcpy(fm->modifier, c, n);
+ fm->modifier[n] = '\0';
+ fm->size = n;
+
+ fm->argv = argv;
+ fm->argc = argc;
+}
+
+/* Free modifier list. */
+static void
+format_free_modifiers(struct format_modifier *list, u_int count)
+{
+ u_int i;
+
+ for (i = 0; i < count; i++)
+ cmd_free_argv(list[i].argc, list[i].argv);
+ free(list);
+}
+
+/* Build modifier list. */
+static struct format_modifier *
+format_build_modifiers(struct format_expand_state *es, const char **s,
+ u_int *count)
+{
+ const char *cp = *s, *end;
+ struct format_modifier *list = NULL;
+ char c, last[] = "X;:", **argv, *value;
+ int argc;
+
+ /*
+ * Modifiers are a ; separated list of the forms:
+ * l,m,C,a,b,c,d,n,t,w,q,E,T,S,W,P,<,>
+ * =a
+ * =/a
+ * =/a/
+ * s/a/b/
+ * s/a/b
+ * ||,&&,!=,==,<=,>=
+ */
+
+ *count = 0;
+
+ while (*cp != '\0' && *cp != ':') {
+ /* Skip any separator character. */
+ if (*cp == ';')
+ cp++;
+
+ /* Check single character modifiers with no arguments. */
+ if (strchr("labcdnwETSWP<>", cp[0]) != NULL &&
+ format_is_end(cp[1])) {
+ format_add_modifier(&list, count, cp, 1, NULL, 0);
+ cp++;
+ continue;
+ }
+
+ /* Then try double character with no arguments. */
+ if ((memcmp("||", cp, 2) == 0 ||
+ memcmp("&&", cp, 2) == 0 ||
+ memcmp("!=", cp, 2) == 0 ||
+ memcmp("==", cp, 2) == 0 ||
+ memcmp("<=", cp, 2) == 0 ||
+ memcmp(">=", cp, 2) == 0) &&
+ format_is_end(cp[2])) {
+ format_add_modifier(&list, count, cp, 2, NULL, 0);
+ cp += 2;
+ continue;
+ }
+
+ /* Now try single character with arguments. */
+ if (strchr("mCNst=peq", cp[0]) == NULL)
+ break;
+ c = cp[0];
+
+ /* No arguments provided. */
+ if (format_is_end(cp[1])) {
+ format_add_modifier(&list, count, cp, 1, NULL, 0);
+ cp++;
+ continue;
+ }
+ argv = NULL;
+ argc = 0;
+
+ /* Single argument with no wrapper character. */
+ if (!ispunct(cp[1]) || cp[1] == '-') {
+ end = format_skip(cp + 1, ":;");
+ if (end == NULL)
+ break;
+
+ argv = xcalloc(1, sizeof *argv);
+ value = xstrndup(cp + 1, end - (cp + 1));
+ argv[0] = format_expand1(es, value);
+ free(value);
+ argc = 1;
+
+ format_add_modifier(&list, count, &c, 1, argv, argc);
+ cp = end;
+ continue;
+ }
+
+ /* Multiple arguments with a wrapper character. */
+ last[0] = cp[1];
+ cp++;
+ do {
+ if (cp[0] == last[0] && format_is_end(cp[1])) {
+ cp++;
+ break;
+ }
+ end = format_skip(cp + 1, last);
+ if (end == NULL)
+ break;
+ cp++;
+
+ argv = xreallocarray(argv, argc + 1, sizeof *argv);
+ value = xstrndup(cp, end - cp);
+ argv[argc++] = format_expand1(es, value);
+ free(value);
+
+ cp = end;
+ } while (!format_is_end(cp[0]));
+ format_add_modifier(&list, count, &c, 1, argv, argc);
+ }
+ if (*cp != ':') {
+ format_free_modifiers(list, *count);
+ *count = 0;
+ return (NULL);
+ }
+ *s = cp + 1;
+ return (list);
+}
+
+/* Match against an fnmatch(3) pattern or regular expression. */
+static char *
+format_match(struct format_modifier *fm, const char *pattern, const char *text)
+{
+ const char *s = "";
+ regex_t r;
+ int flags = 0;
+
+ if (fm->argc >= 1)
+ s = fm->argv[0];
+ if (strchr(s, 'r') == NULL) {
+ if (strchr(s, 'i') != NULL)
+ flags |= FNM_CASEFOLD;
+ if (fnmatch(pattern, text, flags) != 0)
+ return (xstrdup("0"));
+ } else {
+ flags = REG_EXTENDED|REG_NOSUB;
+ if (strchr(s, 'i') != NULL)
+ flags |= REG_ICASE;
+ if (regcomp(&r, pattern, flags) != 0)
+ return (xstrdup("0"));
+ if (regexec(&r, text, 0, NULL, 0) != 0) {
+ regfree(&r);
+ return (xstrdup("0"));
+ }
+ regfree(&r);
+ }
+ return (xstrdup("1"));
+}
+
+/* Perform substitution in string. */
+static char *
+format_sub(struct format_modifier *fm, const char *text, const char *pattern,
+ const char *with)
+{
+ char *value;
+ int flags = REG_EXTENDED;
+
+ if (fm->argc >= 3 && strchr(fm->argv[2], 'i') != NULL)
+ flags |= REG_ICASE;
+ value = regsub(pattern, with, text, flags);
+ if (value == NULL)
+ return (xstrdup(text));
+ return (value);
+}
+
+/* Search inside pane. */
+static char *
+format_search(struct format_modifier *fm, struct window_pane *wp, const char *s)
+{
+ int ignore = 0, regex = 0;
+ char *value;
+
+ if (fm->argc >= 1) {
+ if (strchr(fm->argv[0], 'i') != NULL)
+ ignore = 1;
+ if (strchr(fm->argv[0], 'r') != NULL)
+ regex = 1;
+ }
+ xasprintf(&value, "%u", window_pane_search(wp, s, regex, ignore));
+ return (value);
+}
+
+/* Does session name exist? */
+static char *
+format_session_name(struct format_expand_state *es, const char *fmt)
+{
+ char *name;
+ struct session *s;
+
+ name = format_expand1(es, fmt);
+ RB_FOREACH(s, sessions, &sessions) {
+ if (strcmp(s->name, name) == 0) {
+ free(name);
+ return (xstrdup("1"));
+ }
+ }
+ free(name);
+ return (xstrdup("0"));
+}
+
+/* Loop over sessions. */
+static char *
+format_loop_sessions(struct format_expand_state *es, const char *fmt)
+{
+ struct format_tree *ft = es->ft;
+ struct client *c = ft->client;
+ struct cmdq_item *item = ft->item;
+ struct format_tree *nft;
+ struct format_expand_state next;
+ char *expanded, *value;
+ size_t valuelen;
+ struct session *s;
+
+ value = xcalloc(1, 1);
+ valuelen = 1;
+
+ RB_FOREACH(s, sessions, &sessions) {
+ format_log(es, "session loop: $%u", s->id);
+ nft = format_create(c, item, FORMAT_NONE, ft->flags);
+ format_defaults(nft, ft->c, s, NULL, NULL);
+ format_copy_state(&next, es, 0);
+ next.ft = nft;
+ expanded = format_expand1(&next, fmt);
+ format_free(next.ft);
+
+ valuelen += strlen(expanded);
+ value = xrealloc(value, valuelen);
+
+ strlcat(value, expanded, valuelen);
+ free(expanded);
+ }
+
+ return (value);
+}
+
+/* Does window name exist? */
+static char *
+format_window_name(struct format_expand_state *es, const char *fmt)
+{
+ struct format_tree *ft = es->ft;
+ char *name;
+ struct winlink *wl;
+
+ if (ft->s == NULL) {
+ format_log(es, "window name but no session");
+ return (NULL);
+ }
+
+ name = format_expand1(es, fmt);
+ RB_FOREACH(wl, winlinks, &ft->s->windows) {
+ if (strcmp(wl->window->name, name) == 0) {
+ free(name);
+ return (xstrdup("1"));
+ }
+ }
+ free(name);
+ return (xstrdup("0"));
+}
+
+/* Loop over windows. */
+static char *
+format_loop_windows(struct format_expand_state *es, const char *fmt)
+{
+ struct format_tree *ft = es->ft;
+ struct client *c = ft->client;
+ struct cmdq_item *item = ft->item;
+ struct format_tree *nft;
+ struct format_expand_state next;
+ char *all, *active, *use, *expanded, *value;
+ size_t valuelen;
+ struct winlink *wl;
+ struct window *w;
+
+ if (ft->s == NULL) {
+ format_log(es, "window loop but no session");
+ return (NULL);
+ }
+
+ if (format_choose(es, fmt, &all, &active, 0) != 0) {
+ all = xstrdup(fmt);
+ active = NULL;
+ }
+
+ value = xcalloc(1, 1);
+ valuelen = 1;
+
+ RB_FOREACH(wl, winlinks, &ft->s->windows) {
+ w = wl->window;
+ format_log(es, "window loop: %u @%u", wl->idx, w->id);
+ if (active != NULL && wl == ft->s->curw)
+ use = active;
+ else
+ use = all;
+ nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags);
+ format_defaults(nft, ft->c, ft->s, wl, NULL);
+ format_copy_state(&next, es, 0);
+ next.ft = nft;
+ expanded = format_expand1(&next, use);
+ format_free(nft);
+
+ valuelen += strlen(expanded);
+ value = xrealloc(value, valuelen);
+
+ strlcat(value, expanded, valuelen);
+ free(expanded);
+ }
+
+ free(active);
+ free(all);
+
+ return (value);
+}
+
+/* Loop over panes. */
+static char *
+format_loop_panes(struct format_expand_state *es, const char *fmt)
+{
+ struct format_tree *ft = es->ft;
+ struct client *c = ft->client;
+ struct cmdq_item *item = ft->item;
+ struct format_tree *nft;
+ struct format_expand_state next;
+ char *all, *active, *use, *expanded, *value;
+ size_t valuelen;
+ struct window_pane *wp;
+
+ if (ft->w == NULL) {
+ format_log(es, "pane loop but no window");
+ return (NULL);
+ }
+
+ if (format_choose(es, fmt, &all, &active, 0) != 0) {
+ all = xstrdup(fmt);
+ active = NULL;
+ }
+
+ value = xcalloc(1, 1);
+ valuelen = 1;
+
+ TAILQ_FOREACH(wp, &ft->w->panes, entry) {
+ format_log(es, "pane loop: %%%u", wp->id);
+ if (active != NULL && wp == ft->w->active)
+ use = active;
+ else
+ use = all;
+ nft = format_create(c, item, FORMAT_PANE|wp->id, ft->flags);
+ format_defaults(nft, ft->c, ft->s, ft->wl, wp);
+ format_copy_state(&next, es, 0);
+ next.ft = nft;
+ expanded = format_expand1(&next, use);
+ format_free(nft);
+
+ valuelen += strlen(expanded);
+ value = xrealloc(value, valuelen);
+
+ strlcat(value, expanded, valuelen);
+ free(expanded);
+ }
+
+ free(active);
+ free(all);
+
+ return (value);
+}
+
+static char *
+format_replace_expression(struct format_modifier *mexp,
+ struct format_expand_state *es, const char *copy)
+{
+ int argc = mexp->argc;
+ const char *errstr;
+ char *endch, *value, *left = NULL, *right = NULL;
+ int use_fp = 0;
+ u_int prec = 0;
+ double mleft, mright, result;
+ enum { ADD,
+ SUBTRACT,
+ MULTIPLY,
+ DIVIDE,
+ MODULUS,
+ EQUAL,
+ NOT_EQUAL,
+ GREATER_THAN,
+ GREATER_THAN_EQUAL,
+ LESS_THAN,
+ LESS_THAN_EQUAL } operator;
+
+ if (strcmp(mexp->argv[0], "+") == 0)
+ operator = ADD;
+ else if (strcmp(mexp->argv[0], "-") == 0)
+ operator = SUBTRACT;
+ else if (strcmp(mexp->argv[0], "*") == 0)
+ operator = MULTIPLY;
+ else if (strcmp(mexp->argv[0], "/") == 0)
+ operator = DIVIDE;
+ else if (strcmp(mexp->argv[0], "%") == 0 ||
+ strcmp(mexp->argv[0], "m") == 0)
+ operator = MODULUS;
+ else if (strcmp(mexp->argv[0], "==") == 0)
+ operator = EQUAL;
+ else if (strcmp(mexp->argv[0], "!=") == 0)
+ operator = NOT_EQUAL;
+ else if (strcmp(mexp->argv[0], ">") == 0)
+ operator = GREATER_THAN;
+ else if (strcmp(mexp->argv[0], "<") == 0)
+ operator = LESS_THAN;
+ else if (strcmp(mexp->argv[0], ">=") == 0)
+ operator = GREATER_THAN_EQUAL;
+ else if (strcmp(mexp->argv[0], "<=") == 0)
+ operator = LESS_THAN_EQUAL;
+ else {
+ format_log(es, "expression has no valid operator: '%s'",
+ mexp->argv[0]);
+ goto fail;
+ }
+
+ /* The second argument may be flags. */
+ if (argc >= 2 && strchr(mexp->argv[1], 'f') != NULL) {
+ use_fp = 1;
+ prec = 2;
+ }
+
+ /* The third argument may be precision. */
+ if (argc >= 3) {
+ prec = strtonum(mexp->argv[2], INT_MIN, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ format_log(es, "expression precision %s: %s", errstr,
+ mexp->argv[2]);
+ goto fail;
+ }
+ }
+
+ if (format_choose(es, copy, &left, &right, 1) != 0) {
+ format_log(es, "expression syntax error");
+ goto fail;
+ }
+
+ mleft = strtod(left, &endch);
+ if (*endch != '\0') {
+ format_log(es, "expression left side is invalid: %s", left);
+ goto fail;
+ }
+
+ mright = strtod(right, &endch);
+ if (*endch != '\0') {
+ format_log(es, "expression right side is invalid: %s", right);
+ goto fail;
+ }
+
+ if (!use_fp) {
+ mleft = (long long)mleft;
+ mright = (long long)mright;
+ }
+ format_log(es, "expression left side is: %.*f", prec, mleft);
+ format_log(es, "expression right side is: %.*f", prec, mright);
+
+ switch (operator) {
+ case ADD:
+ result = mleft + mright;
+ break;
+ case SUBTRACT:
+ result = mleft - mright;
+ break;
+ case MULTIPLY:
+ result = mleft * mright;
+ break;
+ case DIVIDE:
+ result = mleft / mright;
+ break;
+ case MODULUS:
+ result = fmod(mleft, mright);
+ break;
+ case EQUAL:
+ result = fabs(mleft - mright) < 1e-9;
+ break;
+ case NOT_EQUAL:
+ result = fabs(mleft - mright) > 1e-9;
+ break;
+ case GREATER_THAN:
+ result = (mleft > mright);
+ break;
+ case GREATER_THAN_EQUAL:
+ result = (mleft >= mright);
+ break;
+ case LESS_THAN:
+ result = (mleft < mright);
+ break;
+ case LESS_THAN_EQUAL:
+ result = (mleft <= mright);
+ break;
+ }
+ if (use_fp)
+ xasprintf(&value, "%.*f", prec, result);
+ else
+ xasprintf(&value, "%.*f", prec, (double)(long long)result);
+ format_log(es, "expression result is %s", value);
+
+ free(right);
+ free(left);
+ return (value);
+
+fail:
+ free(right);
+ free(left);
+ return (NULL);
+}
+
+/* Replace a key. */
+static int
+format_replace(struct format_expand_state *es, const char *key, size_t keylen,
+ char **buf, size_t *len, size_t *off)
+{
+ struct format_tree *ft = es->ft;
+ struct window_pane *wp = ft->wp;
+ const char *errstr, *copy, *cp, *marker = NULL;
+ const char *time_format = NULL;
+ char *copy0, *condition, *found, *new;
+ char *value, *left, *right;
+ size_t valuelen;
+ int modifiers = 0, limit = 0, width = 0;
+ int j, c;
+ struct format_modifier *list, *cmp = NULL, *search = NULL;
+ struct format_modifier **sub = NULL, *mexp = NULL, *fm;
+ u_int i, count, nsub = 0;
+ struct format_expand_state next;
+
+ /* Make a copy of the key. */
+ copy = copy0 = xstrndup(key, keylen);
+
+ /* Process modifier list. */
+ list = format_build_modifiers(es, &copy, &count);
+ for (i = 0; i < count; i++) {
+ fm = &list[i];
+ if (format_logging(ft)) {
+ format_log(es, "modifier %u is %s", i, fm->modifier);
+ for (j = 0; j < fm->argc; j++) {
+ format_log(es, "modifier %u argument %d: %s", i,
+ j, fm->argv[j]);
+ }
+ }
+ if (fm->size == 1) {
+ switch (fm->modifier[0]) {
+ case 'm':
+ case '<':
+ case '>':
+ cmp = fm;
+ break;
+ case 'C':
+ search = fm;
+ break;
+ case 's':
+ if (fm->argc < 2)
+ break;
+ sub = xreallocarray(sub, nsub + 1, sizeof *sub);
+ sub[nsub++] = fm;
+ break;
+ case '=':
+ if (fm->argc < 1)
+ break;
+ limit = strtonum(fm->argv[0], INT_MIN, INT_MAX,
+ &errstr);
+ if (errstr != NULL)
+ limit = 0;
+ if (fm->argc >= 2 && fm->argv[1] != NULL)
+ marker = fm->argv[1];
+ break;
+ case 'p':
+ if (fm->argc < 1)
+ break;
+ width = strtonum(fm->argv[0], INT_MIN, INT_MAX,
+ &errstr);
+ if (errstr != NULL)
+ width = 0;
+ break;
+ case 'w':
+ modifiers |= FORMAT_WIDTH;
+ break;
+ case 'e':
+ if (fm->argc < 1 || fm->argc > 3)
+ break;
+ mexp = fm;
+ break;
+ case 'l':
+ modifiers |= FORMAT_LITERAL;
+ break;
+ case 'a':
+ modifiers |= FORMAT_CHARACTER;
+ break;
+ case 'b':
+ modifiers |= FORMAT_BASENAME;
+ break;
+ case 'c':
+ modifiers |= FORMAT_COLOUR;
+ break;
+ case 'd':
+ modifiers |= FORMAT_DIRNAME;
+ break;
+ case 'n':
+ modifiers |= FORMAT_LENGTH;
+ break;
+ case 't':
+ modifiers |= FORMAT_TIMESTRING;
+ if (fm->argc < 1)
+ break;
+ if (strchr(fm->argv[0], 'p') != NULL)
+ modifiers |= FORMAT_PRETTY;
+ else if (fm->argc >= 2 &&
+ strchr(fm->argv[0], 'f') != NULL)
+ time_format = format_strip(fm->argv[1]);
+ break;
+ case 'q':
+ if (fm->argc < 1)
+ modifiers |= FORMAT_QUOTE_SHELL;
+ else if (strchr(fm->argv[0], 'e') != NULL ||
+ strchr(fm->argv[0], 'h') != NULL)
+ modifiers |= FORMAT_QUOTE_STYLE;
+ break;
+ case 'E':
+ modifiers |= FORMAT_EXPAND;
+ break;
+ case 'T':
+ modifiers |= FORMAT_EXPANDTIME;
+ break;
+ case 'N':
+ if (fm->argc < 1 ||
+ strchr(fm->argv[0], 'w') != NULL)
+ modifiers |= FORMAT_WINDOW_NAME;
+ else if (strchr(fm->argv[0], 's') != NULL)
+ modifiers |= FORMAT_SESSION_NAME;
+ break;
+ case 'S':
+ modifiers |= FORMAT_SESSIONS;
+ break;
+ case 'W':
+ modifiers |= FORMAT_WINDOWS;
+ break;
+ case 'P':
+ modifiers |= FORMAT_PANES;
+ break;
+ }
+ } else if (fm->size == 2) {
+ if (strcmp(fm->modifier, "||") == 0 ||
+ strcmp(fm->modifier, "&&") == 0 ||
+ strcmp(fm->modifier, "==") == 0 ||
+ strcmp(fm->modifier, "!=") == 0 ||
+ strcmp(fm->modifier, ">=") == 0 ||
+ strcmp(fm->modifier, "<=") == 0)
+ cmp = fm;
+ }
+ }
+
+ /* Is this a literal string? */
+ if (modifiers & FORMAT_LITERAL) {
+ value = xstrdup(copy);
+ goto done;
+ }
+
+ /* Is this a character? */
+ if (modifiers & FORMAT_CHARACTER) {
+ new = format_expand1(es, copy);
+ c = strtonum(new, 32, 126, &errstr);
+ if (errstr != NULL)
+ value = xstrdup("");
+ else
+ xasprintf(&value, "%c", c);
+ free(new);
+ goto done;
+ }
+
+ /* Is this a colour? */
+ if (modifiers & FORMAT_COLOUR) {
+ new = format_expand1(es, copy);
+ c = colour_fromstring(new);
+ if (c == -1 || (c = colour_force_rgb(c)) == -1)
+ value = xstrdup("");
+ else
+ xasprintf(&value, "%06x", c & 0xffffff);
+ free(new);
+ goto done;
+ }
+
+ /* Is this a loop, comparison or condition? */
+ if (modifiers & FORMAT_SESSIONS) {
+ value = format_loop_sessions(es, copy);
+ if (value == NULL)
+ goto fail;
+ } else if (modifiers & FORMAT_WINDOWS) {
+ value = format_loop_windows(es, copy);
+ if (value == NULL)
+ goto fail;
+ } else if (modifiers & FORMAT_PANES) {
+ value = format_loop_panes(es, copy);
+ if (value == NULL)
+ goto fail;
+ } else if (modifiers & FORMAT_WINDOW_NAME) {
+ value = format_window_name(es, copy);
+ if (value == NULL)
+ goto fail;
+ } else if (modifiers & FORMAT_SESSION_NAME) {
+ value = format_session_name(es, copy);
+ if (value == NULL)
+ goto fail;
+ } else if (search != NULL) {
+ /* Search in pane. */
+ new = format_expand1(es, copy);
+ if (wp == NULL) {
+ format_log(es, "search '%s' but no pane", new);
+ value = xstrdup("0");
+ } else {
+ format_log(es, "search '%s' pane %%%u", new, wp->id);
+ value = format_search(search, wp, new);
+ }
+ free(new);
+ } else if (cmp != NULL) {
+ /* Comparison of left and right. */
+ if (format_choose(es, copy, &left, &right, 1) != 0) {
+ format_log(es, "compare %s syntax error: %s",
+ cmp->modifier, copy);
+ goto fail;
+ }
+ format_log(es, "compare %s left is: %s", cmp->modifier, left);
+ format_log(es, "compare %s right is: %s", cmp->modifier, right);
+
+ if (strcmp(cmp->modifier, "||") == 0) {
+ if (format_true(left) || format_true(right))
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, "&&") == 0) {
+ if (format_true(left) && format_true(right))
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, "==") == 0) {
+ if (strcmp(left, right) == 0)
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, "!=") == 0) {
+ if (strcmp(left, right) != 0)
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, "<") == 0) {
+ if (strcmp(left, right) < 0)
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, ">") == 0) {
+ if (strcmp(left, right) > 0)
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, "<=") == 0) {
+ if (strcmp(left, right) <= 0)
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, ">=") == 0) {
+ if (strcmp(left, right) >= 0)
+ value = xstrdup("1");
+ else
+ value = xstrdup("0");
+ } else if (strcmp(cmp->modifier, "m") == 0)
+ value = format_match(cmp, left, right);
+
+ free(right);
+ free(left);
+ } else if (*copy == '?') {
+ /* Conditional: check first and choose second or third. */
+ cp = format_skip(copy + 1, ",");
+ if (cp == NULL) {
+ format_log(es, "condition syntax error: %s", copy + 1);
+ goto fail;
+ }
+ condition = xstrndup(copy + 1, cp - (copy + 1));
+ format_log(es, "condition is: %s", condition);
+
+ found = format_find(ft, condition, modifiers, time_format);
+ if (found == NULL) {
+ /*
+ * If the condition not found, try to expand it. If
+ * the expansion doesn't have any effect, then assume
+ * false.
+ */
+ found = format_expand1(es, condition);
+ if (strcmp(found, condition) == 0) {
+ free(found);
+ found = xstrdup("");
+ format_log(es,
+ "condition '%s' not found; assuming false",
+ condition);
+ }
+ } else {
+ format_log(es, "condition '%s' found: %s", condition,
+ found);
+ }
+
+ if (format_choose(es, cp + 1, &left, &right, 0) != 0) {
+ format_log(es, "condition '%s' syntax error: %s",
+ condition, cp + 1);
+ free(found);
+ goto fail;
+ }
+ if (format_true(found)) {
+ format_log(es, "condition '%s' is true", condition);
+ value = format_expand1(es, left);
+ } else {
+ format_log(es, "condition '%s' is false", condition);
+ value = format_expand1(es, right);
+ }
+ free(right);
+ free(left);
+
+ free(condition);
+ free(found);
+ } else if (mexp != NULL) {
+ value = format_replace_expression(mexp, es, copy);
+ if (value == NULL)
+ value = xstrdup("");
+ } else {
+ if (strstr(copy, "#{") != 0) {
+ format_log(es, "expanding inner format '%s'", copy);
+ value = format_expand1(es, copy);
+ } else {
+ value = format_find(ft, copy, modifiers, time_format);
+ if (value == NULL) {
+ format_log(es, "format '%s' not found", copy);
+ value = xstrdup("");
+ } else {
+ format_log(es, "format '%s' found: %s", copy,
+ value);
+ }
+ }
+ }
+
+done:
+ /* Expand again if required. */
+ if (modifiers & FORMAT_EXPAND) {
+ new = format_expand1(es, value);
+ free(value);
+ value = new;
+ } else if (modifiers & FORMAT_EXPANDTIME) {
+ format_copy_state(&next, es, FORMAT_EXPAND_TIME);
+ new = format_expand1(&next, value);
+ free(value);
+ value = new;
+ }
+
+ /* Perform substitution if any. */
+ for (i = 0; i < nsub; i++) {
+ left = format_expand1(es, sub[i]->argv[0]);
+ right = format_expand1(es, sub[i]->argv[1]);
+ new = format_sub(sub[i], value, left, right);
+ format_log(es, "substitute '%s' to '%s': %s", left, right, new);
+ free(value);
+ value = new;
+ free(right);
+ free(left);
+ }
+
+ /* Truncate the value if needed. */
+ if (limit > 0) {
+ new = format_trim_left(value, limit);
+ if (marker != NULL && strcmp(new, value) != 0) {
+ free(value);
+ xasprintf(&value, "%s%s", new, marker);
+ } else {
+ free(value);
+ value = new;
+ }
+ format_log(es, "applied length limit %d: %s", limit, value);
+ } else if (limit < 0) {
+ new = format_trim_right(value, -limit);
+ if (marker != NULL && strcmp(new, value) != 0) {
+ free(value);
+ xasprintf(&value, "%s%s", marker, new);
+ } else {
+ free(value);
+ value = new;
+ }
+ format_log(es, "applied length limit %d: %s", limit, value);
+ }
+
+ /* Pad the value if needed. */
+ if (width > 0) {
+ new = utf8_padcstr(value, width);
+ free(value);
+ value = new;
+ format_log(es, "applied padding width %d: %s", width, value);
+ } else if (width < 0) {
+ new = utf8_rpadcstr(value, -width);
+ free(value);
+ value = new;
+ format_log(es, "applied padding width %d: %s", width, value);
+ }
+
+ /* Replace with the length or width if needed. */
+ if (modifiers & FORMAT_LENGTH) {
+ xasprintf(&new, "%zu", strlen(value));
+ free(value);
+ value = new;
+ format_log(es, "replacing with length: %s", new);
+ }
+ if (modifiers & FORMAT_WIDTH) {
+ xasprintf(&new, "%u", format_width(value));
+ free(value);
+ value = new;
+ format_log(es, "replacing with width: %s", new);
+ }
+
+ /* Expand the buffer and copy in the value. */
+ valuelen = strlen(value);
+ while (*len - *off < valuelen + 1) {
+ *buf = xreallocarray(*buf, 2, *len);
+ *len *= 2;
+ }
+ memcpy(*buf + *off, value, valuelen);
+ *off += valuelen;
+
+ format_log(es, "replaced '%s' with '%s'", copy0, value);
+ free(value);
+
+ free(sub);
+ format_free_modifiers(list, count);
+ free(copy0);
+ return (0);
+
+fail:
+ format_log(es, "failed %s", copy0);
+
+ free(sub);
+ format_free_modifiers(list, count);
+ free(copy0);
+ return (-1);
+}
+
+/* Expand keys in a template. */
+static char *
+format_expand1(struct format_expand_state *es, const char *fmt)
+{
+ struct format_tree *ft = es->ft;
+ char *buf, *out, *name;
+ const char *ptr, *s;
+ size_t off, len, n, outlen;
+ int ch, brackets;
+ char expanded[8192];
+
+ if (fmt == NULL || *fmt == '\0')
+ return (xstrdup(""));
+
+ if (es->loop == FORMAT_LOOP_LIMIT) {
+ format_log(es, "reached loop limit (%u)", FORMAT_LOOP_LIMIT);
+ return (xstrdup(""));
+ }
+ es->loop++;
+
+ format_log(es, "expanding format: %s", fmt);
+
+ if ((es->flags & FORMAT_EXPAND_TIME) && strchr(fmt, '%') != NULL) {
+ if (es->time == 0) {
+ es->time = time(NULL);
+ localtime_r(&es->time, &es->tm);
+ }
+ if (strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) {
+ format_log(es, "format is too long");
+ return (xstrdup(""));
+ }
+ if (format_logging(ft) && strcmp(expanded, fmt) != 0)
+ format_log(es, "after time expanded: %s", expanded);
+ fmt = expanded;
+ }
+
+ len = 64;
+ buf = xmalloc(len);
+ off = 0;
+
+ while (*fmt != '\0') {
+ if (*fmt != '#') {
+ while (len - off < 2) {
+ buf = xreallocarray(buf, 2, len);
+ len *= 2;
+ }
+ buf[off++] = *fmt++;
+ continue;
+ }
+ fmt++;
+
+ ch = (u_char)*fmt++;
+ switch (ch) {
+ case '(':
+ brackets = 1;
+ for (ptr = fmt; *ptr != '\0'; ptr++) {
+ if (*ptr == '(')
+ brackets++;
+ if (*ptr == ')' && --brackets == 0)
+ break;
+ }
+ if (*ptr != ')' || brackets != 0)
+ break;
+ n = ptr - fmt;
+
+ name = xstrndup(fmt, n);
+ format_log(es, "found #(): %s", name);
+
+ if ((ft->flags & FORMAT_NOJOBS) ||
+ (es->flags & FORMAT_EXPAND_NOJOBS)) {
+ out = xstrdup("");
+ format_log(es, "#() is disabled");
+ } else {
+ out = format_job_get(es, name);
+ format_log(es, "#() result: %s", out);
+ }
+ free(name);
+
+ outlen = strlen(out);
+ while (len - off < outlen + 1) {
+ buf = xreallocarray(buf, 2, len);
+ len *= 2;
+ }
+ memcpy(buf + off, out, outlen);
+ off += outlen;
+
+ free(out);
+
+ fmt += n + 1;
+ continue;
+ case '{':
+ ptr = format_skip((char *)fmt - 2, "}");
+ if (ptr == NULL)
+ break;
+ n = ptr - fmt;
+
+ format_log(es, "found #{}: %.*s", (int)n, fmt);
+ if (format_replace(es, fmt, n, &buf, &len, &off) != 0)
+ break;
+ fmt += n + 1;
+ continue;
+ 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;
+ while (*ptr == '#') {
+ ptr++;
+ n++;
+ }
+ if (*ptr == '[') {
+ format_log(es, "found #*%zu[", n);
+ while (len - off < n + 2) {
+ buf = xreallocarray(buf, 2, len);
+ len *= 2;
+ }
+ memcpy(buf + off, fmt - 2, n + 1);
+ off += n + 1;
+ fmt = ptr + 1;
+ continue;
+ }
+ /* FALLTHROUGH */
+ case '}':
+ case ',':
+ format_log(es, "found #%c", ch);
+ while (len - off < 2) {
+ buf = xreallocarray(buf, 2, len);
+ len *= 2;
+ }
+ buf[off++] = ch;
+ 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 (s == NULL) {
+ while (len - off < 3) {
+ buf = xreallocarray(buf, 2, len);
+ len *= 2;
+ }
+ buf[off++] = '#';
+ buf[off++] = ch;
+ continue;
+ }
+ n = strlen(s);
+ format_log(es, "found #%c: %s", ch, s);
+ if (format_replace(es, s, n, &buf, &len, &off) != 0)
+ break;
+ continue;
+ }
+
+ break;
+ }
+ buf[off] = '\0';
+
+ format_log(es, "result is: %s", buf);
+ es->loop--;
+
+ return (buf);
+}
+
+/* Expand keys in a template, passing through strftime first. */
+char *
+format_expand_time(struct format_tree *ft, const char *fmt)
+{
+ struct format_expand_state es;
+
+ memset(&es, 0, sizeof es);
+ es.ft = ft;
+ es.flags = FORMAT_EXPAND_TIME;
+ return (format_expand1(&es, fmt));
+}
+
+/* Expand keys in a template. */
+char *
+format_expand(struct format_tree *ft, const char *fmt)
+{
+ struct format_expand_state es;
+
+ memset(&es, 0, sizeof es);
+ es.ft = ft;
+ es.flags = 0;
+ return (format_expand1(&es, fmt));
+}
+
+/* Expand a single string. */
+char *
+format_single(struct cmdq_item *item, const char *fmt, struct client *c,
+ struct session *s, struct winlink *wl, struct window_pane *wp)
+{
+ struct format_tree *ft;
+ char *expanded;
+
+ ft = format_create_defaults(item, c, s, wl, wp);
+ expanded = format_expand(ft, fmt);
+ format_free(ft);
+ return (expanded);
+}
+
+/* Expand a single string using state. */
+char *
+format_single_from_state(struct cmdq_item *item, const char *fmt,
+ struct client *c, struct cmd_find_state *fs)
+{
+ return (format_single(item, fmt, c, fs->s, fs->wl, fs->wp));
+}
+
+/* Expand a single string using target. */
+char *
+format_single_from_target(struct cmdq_item *item, const char *fmt)
+{
+ struct client *tc = cmdq_get_target_client(item);
+
+ return (format_single_from_state(item, fmt, tc, cmdq_get_target(item)));
+}
+
+/* Create and add defaults. */
+struct format_tree *
+format_create_defaults(struct cmdq_item *item, struct client *c,
+ struct session *s, struct winlink *wl, struct window_pane *wp)
+{
+ struct format_tree *ft;
+
+ if (item != NULL)
+ ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
+ else
+ ft = format_create(NULL, item, FORMAT_NONE, 0);
+ format_defaults(ft, c, s, wl, wp);
+ return (ft);
+}
+
+/* Create and add defaults using state. */
+struct format_tree *
+format_create_from_state(struct cmdq_item *item, struct client *c,
+ struct cmd_find_state *fs)
+{
+ return (format_create_defaults(item, c, fs->s, fs->wl, fs->wp));
+}
+
+/* Create and add defaults using target. */
+struct format_tree *
+format_create_from_target(struct cmdq_item *item)
+{
+ struct client *tc = cmdq_get_target_client(item);
+
+ return (format_create_from_state(item, tc, cmdq_get_target(item)));
+}
+
+/* Set defaults for any of arguments that are not NULL. */
+void
+format_defaults(struct format_tree *ft, struct client *c, struct session *s,
+ struct winlink *wl, struct window_pane *wp)
+{
+ struct paste_buffer *pb;
+
+ if (c != NULL && c->name != NULL)
+ log_debug("%s: c=%s", __func__, c->name);
+ else
+ log_debug("%s: c=none", __func__);
+ if (s != NULL)
+ log_debug("%s: s=$%u", __func__, s->id);
+ else
+ log_debug("%s: s=none", __func__);
+ if (wl != NULL)
+ log_debug("%s: wl=%u", __func__, wl->idx);
+ else
+ log_debug("%s: wl=none", __func__);
+ if (wp != NULL)
+ log_debug("%s: wp=%%%u", __func__, wp->id);
+ else
+ log_debug("%s: wp=none", __func__);
+
+ if (c != NULL && s != NULL && c->session != s)
+ log_debug("%s: session does not match", __func__);
+
+ if (wp != NULL)
+ ft->type = FORMAT_TYPE_PANE;
+ else if (wl != NULL)
+ ft->type = FORMAT_TYPE_WINDOW;
+ else if (s != NULL)
+ ft->type = FORMAT_TYPE_SESSION;
+ else
+ ft->type = FORMAT_TYPE_UNKNOWN;
+
+ if (s == NULL && c != NULL)
+ s = c->session;
+ if (wl == NULL && s != NULL)
+ wl = s->curw;
+ if (wp == NULL && wl != NULL)
+ wp = wl->window->active;
+
+ if (c != NULL)
+ format_defaults_client(ft, c);
+ if (s != NULL)
+ format_defaults_session(ft, s);
+ if (wl != NULL)
+ format_defaults_winlink(ft, wl);
+ if (wp != NULL)
+ format_defaults_pane(ft, wp);
+
+ pb = paste_get_top(NULL);
+ if (pb != NULL)
+ format_defaults_paste_buffer(ft, pb);
+}
+
+/* Set default format keys for a session. */
+static void
+format_defaults_session(struct format_tree *ft, struct session *s)
+{
+ ft->s = s;
+}
+
+/* Set default format keys for a client. */
+static void
+format_defaults_client(struct format_tree *ft, struct client *c)
+{
+ if (ft->s == NULL)
+ ft->s = c->session;
+ ft->c = c;
+}
+
+/* Set default format keys for a window. */
+void
+format_defaults_window(struct format_tree *ft, struct window *w)
+{
+ ft->w = w;
+}
+
+/* Set default format keys for a winlink. */
+static void
+format_defaults_winlink(struct format_tree *ft, struct winlink *wl)
+{
+ if (ft->w == NULL)
+ format_defaults_window(ft, wl->window);
+ ft->wl = wl;
+}
+
+/* Set default format keys for a window pane. */
+void
+format_defaults_pane(struct format_tree *ft, struct window_pane *wp)
+{
+ struct window_mode_entry *wme;
+
+ if (ft->w == NULL)
+ format_defaults_window(ft, wp->window);
+ ft->wp = wp;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme != NULL && wme->mode->formats != NULL)
+ wme->mode->formats(wme, ft);
+}
+
+/* Set default format keys for paste buffer. */
+void
+format_defaults_paste_buffer(struct format_tree *ft, struct paste_buffer *pb)
+{
+ ft->pb = pb;
+}
+
+/* Return word at given coordinates. Caller frees. */
+char *
+format_grid_word(struct grid *gd, u_int x, u_int y)
+{
+ const struct grid_line *gl;
+ struct grid_cell gc;
+ const char *ws;
+ struct utf8_data *ud = NULL;
+ u_int end;
+ size_t size = 0;
+ int found = 0;
+ char *s = NULL;
+
+ ws = options_get_string(global_s_options, "word-separators");
+
+ for (;;) {
+ grid_get_cell(gd, x, y, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ break;
+ if (utf8_cstrhas(ws, &gc.data) ||
+ (gc.data.size == 1 && *gc.data.data == ' ')) {
+ found = 1;
+ break;
+ }
+
+ if (x == 0) {
+ if (y == 0)
+ break;
+ gl = grid_peek_line(gd, y - 1);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ y--;
+ x = grid_line_length(gd, y);
+ if (x == 0)
+ break;
+ }
+ x--;
+ }
+ for (;;) {
+ if (found) {
+ end = grid_line_length(gd, y);
+ if (end == 0 || x == end - 1) {
+ if (y == gd->hsize + gd->sy - 1)
+ break;
+ gl = grid_peek_line(gd, y);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ y++;
+ x = 0;
+ } else
+ x++;
+ }
+ found = 1;
+
+ grid_get_cell(gd, x, y, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ break;
+ if (utf8_cstrhas(ws, &gc.data) ||
+ (gc.data.size == 1 && *gc.data.data == ' '))
+ break;
+
+ ud = xreallocarray(ud, size + 2, sizeof *ud);
+ memcpy(&ud[size++], &gc.data, sizeof *ud);
+ }
+ if (size != 0) {
+ ud[size].size = 0;
+ s = utf8_tocstr(ud);
+ free(ud);
+ }
+ return (s);
+}
+
+/* Return line at given coordinates. Caller frees. */
+char *
+format_grid_line(struct grid *gd, u_int y)
+{
+ struct grid_cell gc;
+ struct utf8_data *ud = NULL;
+ u_int x;
+ size_t size = 0;
+ char *s = NULL;
+
+ for (x = 0; x < grid_line_length(gd, y); x++) {
+ grid_get_cell(gd, x, y, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ break;
+
+ ud = xreallocarray(ud, size + 2, sizeof *ud);
+ memcpy(&ud[size++], &gc.data, sizeof *ud);
+ }
+ if (size != 0) {
+ ud[size].size = 0;
+ s = utf8_tocstr(ud);
+ free(ud);
+ }
+ return (s);
+}
diff --git a/fuzz/input-fuzzer.c b/fuzz/input-fuzzer.c
new file mode 100644
index 0000000..049762b
--- /dev/null
+++ b/fuzz/input-fuzzer.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2020 Sergey Nizovtsev <snizovtsev@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 <stddef.h>
+#include <assert.h>
+#include <fcntl.h>
+
+#include "tmux.h"
+
+#define FUZZER_MAXLEN 512
+#define PANE_WIDTH 80
+#define PANE_HEIGHT 25
+
+struct event_base *libevent;
+
+int
+LLVMFuzzerTestOneInput(const u_char *data, size_t size)
+{
+ struct bufferevent *vpty[2];
+ struct window *w;
+ struct window_pane *wp;
+ int error;
+
+ /*
+ * Since AFL doesn't support -max_len paramenter we have to
+ * discard long inputs manually.
+ */
+ if (size > FUZZER_MAXLEN)
+ return 0;
+
+ w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0);
+ wp = window_add_pane(w, NULL, 0, 0);
+ bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty);
+ wp->ictx = input_init(wp, vpty[0], NULL);
+ window_add_ref(w, __func__);
+
+ wp->fd = open("/dev/null", O_WRONLY);
+ if (wp->fd == -1)
+ errx(1, "open(\"/dev/null\") failed");
+ wp->event = bufferevent_new(wp->fd, NULL, NULL, NULL, NULL);
+
+ input_parse_buffer(wp, (u_char *)data, size);
+ while (cmdq_next(NULL) != 0)
+ ;
+ error = event_base_loop(libevent, EVLOOP_NONBLOCK);
+ if (error == -1)
+ errx(1, "event_base_loop failed");
+
+ assert(w->references == 1);
+ window_remove_ref(w, __func__);
+
+ bufferevent_free(vpty[0]);
+ bufferevent_free(vpty[1]);
+
+ return 0;
+}
+
+int
+LLVMFuzzerInitialize(__unused int *argc, __unused char ***argv)
+{
+ const struct options_table_entry *oe;
+
+ global_environ = environ_create();
+ global_options = options_create(NULL);
+ global_s_options = options_create(NULL);
+ global_w_options = options_create(NULL);
+ for (oe = options_table; oe->name != NULL; oe++) {
+ if (oe->scope & OPTIONS_TABLE_SERVER)
+ options_default(global_options, oe);
+ if (oe->scope & OPTIONS_TABLE_SESSION)
+ options_default(global_s_options, oe);
+ if (oe->scope & OPTIONS_TABLE_WINDOW)
+ options_default(global_w_options, oe);
+ }
+ libevent = osdep_event_init();
+
+ options_set_number(global_w_options, "monitor-bell", 0);
+ options_set_number(global_w_options, "allow-rename", 1);
+ options_set_number(global_options, "set-clipboard", 2);
+ socket_path = xstrdup("dummy");
+
+ return 0;
+}
diff --git a/grid-reader.c b/grid-reader.c
new file mode 100644
index 0000000..c14e3d3
--- /dev/null
+++ b/grid-reader.c
@@ -0,0 +1,429 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.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 "tmux.h"
+#include <string.h>
+
+/* Initialise virtual cursor. */
+void
+grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy)
+{
+ gr->gd = gd;
+ gr->cx = cx;
+ gr->cy = cy;
+}
+
+/* Get cursor position from reader. */
+void
+grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy)
+{
+ *cx = gr->cx;
+ *cy = gr->cy;
+}
+
+/* Get length of line containing the cursor. */
+u_int
+grid_reader_line_length(struct grid_reader *gr)
+{
+ return (grid_line_length(gr->gd, gr->cy));
+}
+
+/* Move cursor forward one position. */
+void
+grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all)
+{
+ u_int px;
+ struct grid_cell gc;
+
+ if (all)
+ px = gr->gd->sx;
+ else
+ px = grid_reader_line_length(gr);
+
+ if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) {
+ grid_reader_cursor_start_of_line(gr, 0);
+ grid_reader_cursor_down(gr);
+ } else if (gr->cx < px) {
+ gr->cx++;
+ while (gr->cx < px) {
+ grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ break;
+ gr->cx++;
+ }
+ }
+}
+
+/* Move cursor back one position. */
+void
+grid_reader_cursor_left(struct grid_reader *gr, int wrap)
+{
+ struct grid_cell gc;
+
+ while (gr->cx > 0) {
+ grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ break;
+ gr->cx--;
+ }
+ if (gr->cx == 0 && gr->cy > 0 &&
+ (wrap ||
+ grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) {
+ grid_reader_cursor_up(gr);
+ grid_reader_cursor_end_of_line(gr, 0, 0);
+ } else if (gr->cx > 0)
+ gr->cx--;
+}
+
+/* Move cursor down one line. */
+void
+grid_reader_cursor_down(struct grid_reader *gr)
+{
+ struct grid_cell gc;
+
+ if (gr->cy < gr->gd->hsize + gr->gd->sy - 1)
+ gr->cy++;
+ while (gr->cx > 0) {
+ grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ break;
+ gr->cx--;
+ }
+}
+
+/* Move cursor up one line. */
+void
+grid_reader_cursor_up(struct grid_reader *gr)
+{
+ struct grid_cell gc;
+
+ if (gr->cy > 0)
+ gr->cy--;
+ while (gr->cx > 0) {
+ grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ break;
+ gr->cx--;
+ }
+}
+
+/* Move cursor to the start of the line. */
+void
+grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap)
+{
+ if (wrap) {
+ while (gr->cy > 0 &&
+ grid_get_line(gr->gd, gr->cy - 1)->flags &
+ GRID_LINE_WRAPPED)
+ gr->cy--;
+ }
+ gr->cx = 0;
+}
+
+/* Move cursor to the end of the line. */
+void
+grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all)
+{
+ u_int yy;
+
+ if (wrap) {
+ yy = gr->gd->hsize + gr->gd->sy - 1;
+ while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags &
+ GRID_LINE_WRAPPED)
+ gr->cy++;
+ }
+ if (all)
+ gr->cx = gr->gd->sx;
+ else
+ gr->cx = grid_reader_line_length(gr);
+}
+
+/* Handle line wrapping while moving the cursor. */
+static int
+grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy)
+{
+ /*
+ * Make sure the cursor lies within the grid reader's bounding area,
+ * wrapping to the next line as necessary. Return zero if the cursor
+ * would wrap past the bottom of the grid.
+ */
+ while (gr->cx > *xx) {
+ if (gr->cy == *yy)
+ return (0);
+ grid_reader_cursor_start_of_line(gr, 0);
+ grid_reader_cursor_down(gr);
+
+ if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
+ *xx = gr->gd->sx - 1;
+ else
+ *xx = grid_reader_line_length(gr);
+ }
+ return (1);
+}
+
+/* Check if character under cursor is in set. */
+int
+grid_reader_in_set(struct grid_reader *gr, const char *set)
+{
+ struct grid_cell gc;
+
+ grid_get_cell(gr->gd, gr->cx, gr->cy, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ return (0);
+ return (utf8_cstrhas(set, &gc.data));
+}
+
+/* Move cursor to the start of the next word. */
+void
+grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators)
+{
+ u_int xx, yy;
+
+ /* Do not break up wrapped words. */
+ if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
+ xx = gr->gd->sx - 1;
+ else
+ xx = grid_reader_line_length(gr);
+ yy = gr->gd->hsize + gr->gd->sy - 1;
+
+ /*
+ * When navigating via spaces (for example with next-space) separators
+ * should be empty.
+ *
+ * If we started on a separator that is not whitespace, skip over
+ * subsequent separators that are not whitespace. Otherwise, if we
+ * started on a non-whitespace character, skip over subsequent
+ * characters that are neither whitespace nor separators. Then, skip
+ * over whitespace (if any) until the next non-whitespace character.
+ */
+ if (!grid_reader_handle_wrap(gr, &xx, &yy))
+ return;
+ if (!grid_reader_in_set(gr, WHITESPACE)) {
+ if (grid_reader_in_set(gr, separators)) {
+ do
+ gr->cx++;
+ while (grid_reader_handle_wrap(gr, &xx, &yy) &&
+ grid_reader_in_set(gr, separators) &&
+ !grid_reader_in_set(gr, WHITESPACE));
+ } else {
+ do
+ gr->cx++;
+ while (grid_reader_handle_wrap(gr, &xx, &yy) &&
+ !(grid_reader_in_set(gr, separators) ||
+ grid_reader_in_set(gr, WHITESPACE)));
+ }
+ }
+ while (grid_reader_handle_wrap(gr, &xx, &yy) &&
+ grid_reader_in_set(gr, WHITESPACE))
+ gr->cx++;
+}
+
+/* Move cursor to the end of the next word. */
+void
+grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators)
+{
+ u_int xx, yy;
+
+ /* Do not break up wrapped words. */
+ if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED)
+ xx = gr->gd->sx - 1;
+ else
+ xx = grid_reader_line_length(gr);
+ yy = gr->gd->hsize + gr->gd->sy - 1;
+
+ /*
+ * When navigating via spaces (for example with next-space), separators
+ * should be empty in both modes.
+ *
+ * If we started on a whitespace, move until reaching the first
+ * non-whitespace character. If that character is a separator, treat
+ * subsequent separators as a word, and continue moving until the first
+ * non-separator. Otherwise, continue moving until the first separator
+ * or whitespace.
+ */
+
+ while (grid_reader_handle_wrap(gr, &xx, &yy)) {
+ if (grid_reader_in_set(gr, WHITESPACE))
+ gr->cx++;
+ else if (grid_reader_in_set(gr, separators)) {
+ do
+ gr->cx++;
+ while (grid_reader_handle_wrap(gr, &xx, &yy) &&
+ grid_reader_in_set(gr, separators) &&
+ !grid_reader_in_set(gr, WHITESPACE));
+ return;
+ } else {
+ do
+ gr->cx++;
+ while (grid_reader_handle_wrap(gr, &xx, &yy) &&
+ !(grid_reader_in_set(gr, WHITESPACE) ||
+ grid_reader_in_set(gr, separators)));
+ return;
+ }
+ }
+}
+
+/* Move to the previous place where a word begins. */
+void
+grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators,
+ int already, int stop_at_eol)
+{
+ int oldx, oldy, at_eol, word_is_letters;
+
+ /* Move back to the previous word character. */
+ if (already || grid_reader_in_set(gr, WHITESPACE)) {
+ for (;;) {
+ if (gr->cx > 0) {
+ gr->cx--;
+ if (!grid_reader_in_set(gr, WHITESPACE)) {
+ word_is_letters =
+ !grid_reader_in_set(gr, separators);
+ break;
+ }
+ } else {
+ if (gr->cy == 0)
+ return;
+ grid_reader_cursor_up(gr);
+ grid_reader_cursor_end_of_line(gr, 0, 0);
+
+ /* Stop if separator at EOL. */
+ if (stop_at_eol && gr->cx > 0) {
+ oldx = gr->cx;
+ gr->cx--;
+ at_eol = grid_reader_in_set(gr,
+ WHITESPACE);
+ gr->cx = oldx;
+ if (at_eol) {
+ word_is_letters = 0;
+ break;
+ }
+ }
+ }
+ }
+ } else
+ word_is_letters = !grid_reader_in_set(gr, separators);
+
+ /* Move back to the beginning of this word. */
+ do {
+ oldx = gr->cx;
+ oldy = gr->cy;
+ if (gr->cx == 0) {
+ if (gr->cy == 0 ||
+ (~grid_get_line(gr->gd, gr->cy - 1)->flags &
+ GRID_LINE_WRAPPED))
+ break;
+ grid_reader_cursor_up(gr);
+ grid_reader_cursor_end_of_line(gr, 0, 1);
+ }
+ if (gr->cx > 0)
+ gr->cx--;
+ } while (!grid_reader_in_set(gr, WHITESPACE) &&
+ word_is_letters != grid_reader_in_set(gr, separators));
+ gr->cx = oldx;
+ gr->cy = oldy;
+}
+
+/* Jump forward to character. */
+int
+grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc)
+{
+ struct grid_cell gc;
+ u_int px, py, xx, yy;
+
+ px = gr->cx;
+ yy = gr->gd->hsize + gr->gd->sy - 1;
+
+ for (py = gr->cy; py <= yy; py++) {
+ xx = grid_line_length(gr->gd, py);
+ while (px < xx) {
+ grid_get_cell(gr->gd, px, py, &gc);
+ if (!(gc.flags & GRID_FLAG_PADDING) &&
+ gc.data.size == jc->size &&
+ memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
+ gr->cx = px;
+ gr->cy = py;
+ return (1);
+ }
+ px++;
+ }
+
+ if (py == yy ||
+ !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED))
+ return (0);
+ px = 0;
+ }
+ return (0);
+}
+
+/* Jump back to character. */
+int
+grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
+{
+ struct grid_cell gc;
+ u_int px, py, xx;
+
+ xx = gr->cx + 1;
+
+ for (py = gr->cy + 1; py > 0; py--) {
+ for (px = xx; px > 0; px--) {
+ grid_get_cell(gr->gd, px - 1, py - 1, &gc);
+ if (!(gc.flags & GRID_FLAG_PADDING) &&
+ gc.data.size == jc->size &&
+ memcmp(gc.data.data, jc->data, gc.data.size) == 0) {
+ gr->cx = px - 1;
+ gr->cy = py - 1;
+ return (1);
+ }
+ }
+
+ if (py == 1 ||
+ !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED))
+ return (0);
+ xx = grid_line_length(gr->gd, py - 2);
+ }
+ return (0);
+}
+
+/* Jump back to the first non-blank character of the line. */
+void
+grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
+{
+ struct grid_cell gc;
+ u_int px, py, xx, yy, oldx, oldy;
+
+ yy = gr->gd->hsize + gr->gd->sy - 1;
+ oldx = gr->cx;
+ oldy = gr->cy;
+ grid_reader_cursor_start_of_line(gr, 1);
+
+ for (py = gr->cy; py <= yy; py++) {
+ xx = grid_line_length(gr->gd, py);
+ for (px = 0; px < xx; px++) {
+ grid_get_cell(gr->gd, px, py, &gc);
+ if (gc.data.size != 1 || *gc.data.data != ' ') {
+ gr->cx = px;
+ gr->cy = py;
+ return;
+ }
+ }
+ if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)
+ break;
+ }
+ gr->cx = oldx;
+ gr->cy = oldy;
+}
diff --git a/grid-view.c b/grid-view.c
new file mode 100644
index 0000000..f230d3c
--- /dev/null
+++ b/grid-view.c
@@ -0,0 +1,235 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 <string.h>
+
+#include "tmux.h"
+
+/*
+ * Grid view functions. These work using coordinates relative to the visible
+ * screen area.
+ */
+
+#define grid_view_x(gd, x) (x)
+#define grid_view_y(gd, y) ((gd)->hsize + (y))
+
+/* Get cell. */
+void
+grid_view_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc)
+{
+ grid_get_cell(gd, grid_view_x(gd, px), grid_view_y(gd, py), gc);
+}
+
+/* Set cell. */
+void
+grid_view_set_cell(struct grid *gd, u_int px, u_int py,
+ const struct grid_cell *gc)
+{
+ grid_set_cell(gd, grid_view_x(gd, px), grid_view_y(gd, py), gc);
+}
+
+/* Set padding. */
+void
+grid_view_set_padding(struct grid *gd, u_int px, u_int py)
+{
+ grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py));
+}
+
+/* Set cells. */
+void
+grid_view_set_cells(struct grid *gd, u_int px, u_int py,
+ const struct grid_cell *gc, const char *s, size_t slen)
+{
+ grid_set_cells(gd, grid_view_x(gd, px), grid_view_y(gd, py), gc, s,
+ slen);
+}
+
+/* Clear into history. */
+void
+grid_view_clear_history(struct grid *gd, u_int bg)
+{
+ struct grid_line *gl;
+ u_int yy, last;
+
+ /* Find the last used line. */
+ last = 0;
+ for (yy = 0; yy < gd->sy; yy++) {
+ gl = grid_get_line(gd, grid_view_y(gd, yy));
+ if (gl->cellused != 0)
+ last = yy + 1;
+ }
+ if (last == 0) {
+ grid_view_clear(gd, 0, 0, gd->sx, gd->sy, bg);
+ return;
+ }
+
+ /* Scroll the lines into the history. */
+ for (yy = 0; yy < last; yy++) {
+ grid_collect_history(gd);
+ grid_scroll_history(gd, bg);
+ }
+ if (last < gd->sy)
+ grid_view_clear(gd, 0, 0, gd->sx, gd->sy - last, bg);
+ gd->hscrolled = 0;
+}
+
+/* Clear area. */
+void
+grid_view_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny,
+ u_int bg)
+{
+ px = grid_view_x(gd, px);
+ py = grid_view_y(gd, py);
+
+ grid_clear(gd, px, py, nx, ny, bg);
+}
+
+/* Scroll region up. */
+void
+grid_view_scroll_region_up(struct grid *gd, u_int rupper, u_int rlower,
+ u_int bg)
+{
+ if (gd->flags & GRID_HISTORY) {
+ grid_collect_history(gd);
+ if (rupper == 0 && rlower == gd->sy - 1)
+ grid_scroll_history(gd, bg);
+ else {
+ rupper = grid_view_y(gd, rupper);
+ rlower = grid_view_y(gd, rlower);
+ grid_scroll_history_region(gd, rupper, rlower, bg);
+ }
+ } else {
+ rupper = grid_view_y(gd, rupper);
+ rlower = grid_view_y(gd, rlower);
+ grid_move_lines(gd, rupper, rupper + 1, rlower - rupper, bg);
+ }
+}
+
+/* Scroll region down. */
+void
+grid_view_scroll_region_down(struct grid *gd, u_int rupper, u_int rlower,
+ u_int bg)
+{
+ rupper = grid_view_y(gd, rupper);
+ rlower = grid_view_y(gd, rlower);
+
+ grid_move_lines(gd, rupper + 1, rupper, rlower - rupper, bg);
+}
+
+/* Insert lines. */
+void
+grid_view_insert_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
+{
+ u_int sy;
+
+ py = grid_view_y(gd, py);
+
+ sy = grid_view_y(gd, gd->sy);
+
+ grid_move_lines(gd, py + ny, py, sy - py - ny, bg);
+}
+
+/* Insert lines in region. */
+void
+grid_view_insert_lines_region(struct grid *gd, u_int rlower, u_int py,
+ u_int ny, u_int bg)
+{
+ u_int ny2;
+
+ rlower = grid_view_y(gd, rlower);
+
+ py = grid_view_y(gd, py);
+
+ ny2 = rlower + 1 - py - ny;
+ grid_move_lines(gd, rlower + 1 - ny2, py, ny2, bg);
+ grid_clear(gd, 0, py + ny2, gd->sx, ny - ny2, bg);
+}
+
+/* Delete lines. */
+void
+grid_view_delete_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
+{
+ u_int sy;
+
+ py = grid_view_y(gd, py);
+
+ sy = grid_view_y(gd, gd->sy);
+
+ grid_move_lines(gd, py, py + ny, sy - py - ny, bg);
+ grid_clear(gd, 0, sy - ny, gd->sx, py + ny - (sy - ny), bg);
+}
+
+/* Delete lines inside scroll region. */
+void
+grid_view_delete_lines_region(struct grid *gd, u_int rlower, u_int py,
+ u_int ny, u_int bg)
+{
+ u_int ny2;
+
+ rlower = grid_view_y(gd, rlower);
+
+ py = grid_view_y(gd, py);
+
+ ny2 = rlower + 1 - py - ny;
+ grid_move_lines(gd, py, py + ny, ny2, bg);
+ grid_clear(gd, 0, py + ny2, gd->sx, ny - ny2, bg);
+}
+
+/* Insert characters. */
+void
+grid_view_insert_cells(struct grid *gd, u_int px, u_int py, u_int nx, u_int bg)
+{
+ u_int sx;
+
+ px = grid_view_x(gd, px);
+ py = grid_view_y(gd, py);
+
+ sx = grid_view_x(gd, gd->sx);
+
+ if (px >= sx - 1)
+ grid_clear(gd, px, py, 1, 1, bg);
+ else
+ grid_move_cells(gd, px + nx, px, py, sx - px - nx, bg);
+}
+
+/* Delete characters. */
+void
+grid_view_delete_cells(struct grid *gd, u_int px, u_int py, u_int nx, u_int bg)
+{
+ u_int sx;
+
+ px = grid_view_x(gd, px);
+ py = grid_view_y(gd, py);
+
+ sx = grid_view_x(gd, gd->sx);
+
+ grid_move_cells(gd, px, px + nx, py, sx - px - nx, bg);
+ grid_clear(gd, sx - nx, py, px + nx - (sx - nx), 1, bg);
+}
+
+/* Convert cells into a string. */
+char *
+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));
+}
diff --git a/grid.c b/grid.c
new file mode 100644
index 0000000..1109ac5
--- /dev/null
+++ b/grid.c
@@ -0,0 +1,1461 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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"
+
+/*
+ * Grid data. This is the basic data structure that represents what is shown on
+ * screen.
+ *
+ * A grid is a grid of cells (struct grid_cell). Lines are not allocated until
+ * cells in that line are written to. The grid is split into history and
+ * viewable data with the history starting at row (line) 0 and extending to
+ * (hsize - 1); from hsize to hsize + (sy - 1) is the viewable data. All
+ * functions in this file work on absolute coordinates, grid-view.c has
+ * functions which work on the screen data.
+ */
+
+/* Default grid cell data. */
+const struct grid_cell grid_default_cell = {
+ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0
+};
+
+/*
+ * Padding grid cell data. Padding cells are the only zero width cell that
+ * appears in the grid - because of this, they are always extended cells.
+ */
+static const struct grid_cell grid_padding_cell = {
+ { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 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
+};
+static const struct grid_cell_entry grid_cleared_entry = {
+ GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } }
+};
+
+/* Store cell in entry. */
+static void
+grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc,
+ u_char c)
+{
+ gce->flags = (gc->flags & ~GRID_FLAG_CLEARED);
+
+ gce->data.fg = gc->fg & 0xff;
+ if (gc->fg & COLOUR_FLAG_256)
+ gce->flags |= GRID_FLAG_FG256;
+
+ gce->data.bg = gc->bg & 0xff;
+ if (gc->bg & COLOUR_FLAG_256)
+ gce->flags |= GRID_FLAG_BG256;
+
+ gce->data.attr = gc->attr;
+ gce->data.data = c;
+}
+
+/* Check if a cell should be an extended cell. */
+static int
+grid_need_extended_cell(const struct grid_cell_entry *gce,
+ const struct grid_cell *gc)
+{
+ if (gce->flags & GRID_FLAG_EXTENDED)
+ return (1);
+ if (gc->attr > 0xff)
+ return (1);
+ if (gc->data.size != 1 || gc->data.width != 1)
+ return (1);
+ if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB))
+ return (1);
+ if (gc->us != 0) /* only supports 256 or RGB */
+ return (1);
+ return (0);
+}
+
+/* Get an extended cell. */
+static void
+grid_get_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
+ int flags)
+{
+ u_int at = gl->extdsize + 1;
+
+ gl->extddata = xreallocarray(gl->extddata, at, sizeof *gl->extddata);
+ gl->extdsize = at;
+
+ gce->offset = at - 1;
+ gce->flags = (flags | GRID_FLAG_EXTENDED);
+}
+
+/* Set cell as extended. */
+static struct grid_extd_entry *
+grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
+ const struct grid_cell *gc)
+{
+ struct grid_extd_entry *gee;
+ int flags = (gc->flags & ~GRID_FLAG_CLEARED);
+ utf8_char uc;
+
+ if (~gce->flags & GRID_FLAG_EXTENDED)
+ grid_get_extended_cell(gl, gce, flags);
+ else if (gce->offset >= gl->extdsize)
+ fatalx("offset too big");
+ gl->flags |= GRID_LINE_EXTENDED;
+
+ utf8_from_data(&gc->data, &uc);
+
+ gee = &gl->extddata[gce->offset];
+ gee->data = uc;
+ gee->attr = gc->attr;
+ gee->flags = flags;
+ gee->fg = gc->fg;
+ gee->bg = gc->bg;
+ gee->us = gc->us;
+ return (gee);
+}
+
+/* Free up unused extended cells. */
+static void
+grid_compact_line(struct grid_line *gl)
+{
+ int new_extdsize = 0;
+ struct grid_extd_entry *new_extddata;
+ struct grid_cell_entry *gce;
+ struct grid_extd_entry *gee;
+ u_int px, idx;
+
+ if (gl->extdsize == 0)
+ return;
+
+ for (px = 0; px < gl->cellsize; px++) {
+ gce = &gl->celldata[px];
+ if (gce->flags & GRID_FLAG_EXTENDED)
+ new_extdsize++;
+ }
+
+ if (new_extdsize == 0) {
+ free(gl->extddata);
+ gl->extddata = NULL;
+ gl->extdsize = 0;
+ return;
+ }
+ new_extddata = xreallocarray(NULL, new_extdsize, sizeof *gl->extddata);
+
+ idx = 0;
+ for (px = 0; px < gl->cellsize; px++) {
+ gce = &gl->celldata[px];
+ if (gce->flags & GRID_FLAG_EXTENDED) {
+ gee = &gl->extddata[gce->offset];
+ memcpy(&new_extddata[idx], gee, sizeof *gee);
+ gce->offset = idx++;
+ }
+ }
+
+ free(gl->extddata);
+ gl->extddata = new_extddata;
+ gl->extdsize = new_extdsize;
+}
+
+/* Get line data. */
+struct grid_line *
+grid_get_line(struct grid *gd, u_int line)
+{
+ return (&gd->linedata[line]);
+}
+
+/* Adjust number of lines. */
+void
+grid_adjust_lines(struct grid *gd, u_int lines)
+{
+ gd->linedata = xreallocarray(gd->linedata, lines, sizeof *gd->linedata);
+}
+
+/* Copy default into a cell. */
+static void
+grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg)
+{
+ struct grid_line *gl = &gd->linedata[py];
+ struct grid_cell_entry *gce = &gl->celldata[px];
+ struct grid_extd_entry *gee;
+
+ memcpy(gce, &grid_cleared_entry, sizeof *gce);
+ if (bg != 8) {
+ if (bg & COLOUR_FLAG_RGB) {
+ grid_get_extended_cell(gl, gce, gce->flags);
+ gee = grid_extended_cell(gl, gce, &grid_cleared_cell);
+ gee->bg = bg;
+ } else {
+ if (bg & COLOUR_FLAG_256)
+ gce->flags |= GRID_FLAG_BG256;
+ gce->data.bg = bg;
+ }
+ }
+}
+
+/* Check grid y position. */
+static int
+grid_check_y(struct grid *gd, const char *from, u_int py)
+{
+ if (py >= gd->hsize + gd->sy) {
+ log_debug("%s: y out of range: %u", from, py);
+ return (-1);
+ }
+ return (0);
+}
+
+/* Check if two styles are (visibly) the same. */
+int
+grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
+{
+ if (gc1->fg != gc2->fg || gc1->bg != gc2->bg)
+ return (0);
+ if (gc1->attr != gc2->attr || gc1->flags != gc2->flags)
+ return (0);
+ return (1);
+}
+
+/* Compare grid cells. Return 1 if equal, 0 if not. */
+int
+grid_cells_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
+{
+ if (!grid_cells_look_equal(gc1, gc2))
+ return (0);
+ if (gc1->data.width != gc2->data.width)
+ return (0);
+ if (gc1->data.size != gc2->data.size)
+ return (0);
+ return (memcmp(gc1->data.data, gc2->data.data, gc1->data.size) == 0);
+}
+
+/* Free one line. */
+static void
+grid_free_line(struct grid *gd, u_int py)
+{
+ free(gd->linedata[py].celldata);
+ gd->linedata[py].celldata = NULL;
+ free(gd->linedata[py].extddata);
+ gd->linedata[py].extddata = NULL;
+}
+
+/* Free several lines. */
+static void
+grid_free_lines(struct grid *gd, u_int py, u_int ny)
+{
+ u_int yy;
+
+ for (yy = py; yy < py + ny; yy++)
+ grid_free_line(gd, yy);
+}
+
+/* Create a new grid. */
+struct grid *
+grid_create(u_int sx, u_int sy, u_int hlimit)
+{
+ struct grid *gd;
+
+ gd = xmalloc(sizeof *gd);
+ gd->sx = sx;
+ gd->sy = sy;
+
+ if (hlimit != 0)
+ gd->flags = GRID_HISTORY;
+ else
+ gd->flags = 0;
+
+ gd->hscrolled = 0;
+ gd->hsize = 0;
+ gd->hlimit = hlimit;
+
+ if (gd->sy != 0)
+ gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata);
+ else
+ gd->linedata = NULL;
+
+ return (gd);
+}
+
+/* Destroy grid. */
+void
+grid_destroy(struct grid *gd)
+{
+ grid_free_lines(gd, 0, gd->hsize + gd->sy);
+
+ free(gd->linedata);
+
+ free(gd);
+}
+
+/* Compare grids. */
+int
+grid_compare(struct grid *ga, struct grid *gb)
+{
+ struct grid_line *gla, *glb;
+ struct grid_cell gca, gcb;
+ u_int xx, yy;
+
+ if (ga->sx != gb->sx || ga->sy != gb->sy)
+ return (1);
+
+ for (yy = 0; yy < ga->sy; yy++) {
+ gla = &ga->linedata[yy];
+ glb = &gb->linedata[yy];
+ if (gla->cellsize != glb->cellsize)
+ return (1);
+ for (xx = 0; xx < gla->cellsize; xx++) {
+ grid_get_cell(ga, xx, yy, &gca);
+ grid_get_cell(gb, xx, yy, &gcb);
+ if (!grid_cells_equal(&gca, &gcb))
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/* Trim lines from the history. */
+static void
+grid_trim_history(struct grid *gd, u_int ny)
+{
+ grid_free_lines(gd, 0, ny);
+ memmove(&gd->linedata[0], &gd->linedata[ny],
+ (gd->hsize + gd->sy - ny) * (sizeof *gd->linedata));
+}
+
+/*
+ * Collect lines from the history if at the limit. Free the top (oldest) 10%
+ * and shift up.
+ */
+void
+grid_collect_history(struct grid *gd)
+{
+ u_int ny;
+
+ if (gd->hsize == 0 || gd->hsize < gd->hlimit)
+ return;
+
+ ny = gd->hlimit / 10;
+ if (ny < 1)
+ ny = 1;
+ if (ny > gd->hsize)
+ ny = gd->hsize;
+
+ /*
+ * Free the lines from 0 to ny then move the remaining lines over
+ * them.
+ */
+ grid_trim_history(gd, ny);
+
+ gd->hsize -= ny;
+ if (gd->hscrolled > gd->hsize)
+ gd->hscrolled = gd->hsize;
+}
+
+/* Remove lines from the bottom of the history. */
+void
+grid_remove_history(struct grid *gd, u_int ny)
+{
+ u_int yy;
+
+ if (ny > gd->hsize)
+ return;
+ for (yy = 0; yy < ny; yy++)
+ grid_free_line(gd, gd->hsize + gd->sy - 1 - yy);
+ gd->hsize -= ny;
+}
+
+/*
+ * Scroll the entire visible screen, moving one line into the history. Just
+ * allocate a new line at the bottom and move the history size indicator.
+ */
+void
+grid_scroll_history(struct grid *gd, u_int bg)
+{
+ u_int yy;
+
+ yy = gd->hsize + gd->sy;
+ gd->linedata = xreallocarray(gd->linedata, yy + 1,
+ sizeof *gd->linedata);
+ grid_empty_line(gd, yy, bg);
+
+ gd->hscrolled++;
+ grid_compact_line(&gd->linedata[gd->hsize]);
+ gd->hsize++;
+}
+
+/* Clear the history. */
+void
+grid_clear_history(struct grid *gd)
+{
+ grid_trim_history(gd, gd->hsize);
+
+ gd->hscrolled = 0;
+ gd->hsize = 0;
+
+ gd->linedata = xreallocarray(gd->linedata, gd->sy,
+ sizeof *gd->linedata);
+}
+
+/* Scroll a region up, moving the top line into the history. */
+void
+grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
+{
+ struct grid_line *gl_history, *gl_upper;
+ u_int yy;
+
+ /* Create a space for a new line. */
+ yy = gd->hsize + gd->sy;
+ gd->linedata = xreallocarray(gd->linedata, yy + 1,
+ sizeof *gd->linedata);
+
+ /* Move the entire screen down to free a space for this line. */
+ gl_history = &gd->linedata[gd->hsize];
+ memmove(gl_history + 1, gl_history, gd->sy * sizeof *gl_history);
+
+ /* Adjust the region and find its start and end. */
+ upper++;
+ gl_upper = &gd->linedata[upper];
+ lower++;
+
+ /* Move the line into the history. */
+ memcpy(gl_history, gl_upper, sizeof *gl_history);
+
+ /* Then move the region up and clear the bottom line. */
+ memmove(gl_upper, gl_upper + 1, (lower - upper) * sizeof *gl_upper);
+ grid_empty_line(gd, lower, bg);
+
+ /* Move the history offset down over the line. */
+ gd->hscrolled++;
+ gd->hsize++;
+}
+
+/* Expand line to fit to cell. */
+static void
+grid_expand_line(struct grid *gd, u_int py, u_int sx, u_int bg)
+{
+ struct grid_line *gl;
+ u_int xx;
+
+ gl = &gd->linedata[py];
+ if (sx <= gl->cellsize)
+ return;
+
+ if (sx < gd->sx / 4)
+ sx = gd->sx / 4;
+ else if (sx < gd->sx / 2)
+ sx = gd->sx / 2;
+ else if (gd->sx > sx)
+ sx = gd->sx;
+
+ gl->celldata = xreallocarray(gl->celldata, sx, sizeof *gl->celldata);
+ for (xx = gl->cellsize; xx < sx; xx++)
+ grid_clear_cell(gd, xx, py, bg);
+ gl->cellsize = sx;
+}
+
+/* Empty a line and set background colour if needed. */
+void
+grid_empty_line(struct grid *gd, u_int py, u_int bg)
+{
+ memset(&gd->linedata[py], 0, sizeof gd->linedata[py]);
+ if (!COLOUR_DEFAULT(bg))
+ grid_expand_line(gd, py, gd->sx, bg);
+}
+
+/* Peek at grid line. */
+const struct grid_line *
+grid_peek_line(struct grid *gd, u_int py)
+{
+ if (grid_check_y(gd, __func__, py) != 0)
+ return (NULL);
+ return (&gd->linedata[py]);
+}
+
+/* Get cell from line. */
+static void
+grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
+{
+ struct grid_cell_entry *gce = &gl->celldata[px];
+ struct grid_extd_entry *gee;
+
+ if (gce->flags & GRID_FLAG_EXTENDED) {
+ if (gce->offset >= gl->extdsize)
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ else {
+ gee = &gl->extddata[gce->offset];
+ gc->flags = gee->flags;
+ gc->attr = gee->attr;
+ gc->fg = gee->fg;
+ gc->bg = gee->bg;
+ gc->us = gee->us;
+ utf8_to_data(gee->data, &gc->data);
+ }
+ return;
+ }
+
+ gc->flags = gce->flags & ~(GRID_FLAG_FG256|GRID_FLAG_BG256);
+ gc->attr = gce->data.attr;
+ gc->fg = gce->data.fg;
+ if (gce->flags & GRID_FLAG_FG256)
+ gc->fg |= COLOUR_FLAG_256;
+ gc->bg = gce->data.bg;
+ if (gce->flags & GRID_FLAG_BG256)
+ gc->bg |= COLOUR_FLAG_256;
+ gc->us = 0;
+ utf8_set(&gc->data, gce->data.data);
+}
+
+/* Get cell for reading. */
+void
+grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc)
+{
+ if (grid_check_y(gd, __func__, py) != 0 ||
+ px >= gd->linedata[py].cellsize)
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ else
+ grid_get_cell1(&gd->linedata[py], px, gc);
+}
+
+/* Set cell at position. */
+void
+grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
+{
+ struct grid_line *gl;
+ struct grid_cell_entry *gce;
+
+ if (grid_check_y(gd, __func__, py) != 0)
+ return;
+
+ grid_expand_line(gd, py, px + 1, 8);
+
+ gl = &gd->linedata[py];
+ if (px + 1 > gl->cellused)
+ gl->cellused = px + 1;
+
+ gce = &gl->celldata[px];
+ if (grid_need_extended_cell(gce, gc))
+ grid_extended_cell(gl, gce, gc);
+ else
+ grid_store_cell(gce, gc, gc->data.data[0]);
+}
+
+/* Set padding at position. */
+void
+grid_set_padding(struct grid *gd, u_int px, u_int py)
+{
+ grid_set_cell(gd, px, py, &grid_padding_cell);
+}
+
+/* Set cells at position. */
+void
+grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
+ const char *s, size_t slen)
+{
+ struct grid_line *gl;
+ struct grid_cell_entry *gce;
+ struct grid_extd_entry *gee;
+ u_int i;
+
+ if (grid_check_y(gd, __func__, py) != 0)
+ return;
+
+ grid_expand_line(gd, py, px + slen, 8);
+
+ gl = &gd->linedata[py];
+ if (px + slen > gl->cellused)
+ gl->cellused = px + slen;
+
+ for (i = 0; i < slen; i++) {
+ gce = &gl->celldata[px + i];
+ if (grid_need_extended_cell(gce, gc)) {
+ gee = grid_extended_cell(gl, gce, gc);
+ gee->data = utf8_build_one(s[i]);
+ } else
+ grid_store_cell(gce, gc, s[i]);
+ }
+}
+
+/* Clear area. */
+void
+grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
+{
+ struct grid_line *gl;
+ u_int xx, yy, ox, sx;
+
+ if (nx == 0 || ny == 0)
+ return;
+
+ if (px == 0 && nx == gd->sx) {
+ grid_clear_lines(gd, py, ny, bg);
+ return;
+ }
+
+ if (grid_check_y(gd, __func__, py) != 0)
+ return;
+ if (grid_check_y(gd, __func__, py + ny - 1) != 0)
+ return;
+
+ for (yy = py; yy < py + ny; yy++) {
+ gl = &gd->linedata[yy];
+
+ sx = gd->sx;
+ if (sx > gl->cellsize)
+ sx = gl->cellsize;
+ ox = nx;
+ if (COLOUR_DEFAULT(bg)) {
+ if (px > sx)
+ continue;
+ if (px + nx > sx)
+ ox = sx - px;
+ }
+
+ grid_expand_line(gd, yy, px + ox, 8); /* default bg first */
+ for (xx = px; xx < px + ox; xx++)
+ grid_clear_cell(gd, xx, yy, bg);
+ }
+}
+
+/* Clear lines. This just frees and truncates the lines. */
+void
+grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
+{
+ u_int yy;
+
+ if (ny == 0)
+ return;
+
+ if (grid_check_y(gd, __func__, py) != 0)
+ return;
+ if (grid_check_y(gd, __func__, py + ny - 1) != 0)
+ return;
+
+ for (yy = py; yy < py + ny; yy++) {
+ grid_free_line(gd, yy);
+ grid_empty_line(gd, yy, bg);
+ }
+ if (py != 0)
+ gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
+}
+
+/* Move a group of lines. */
+void
+grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg)
+{
+ u_int yy;
+
+ if (ny == 0 || py == dy)
+ return;
+
+ if (grid_check_y(gd, __func__, py) != 0)
+ return;
+ if (grid_check_y(gd, __func__, py + ny - 1) != 0)
+ return;
+ if (grid_check_y(gd, __func__, dy) != 0)
+ return;
+ if (grid_check_y(gd, __func__, dy + ny - 1) != 0)
+ return;
+
+ /* Free any lines which are being replaced. */
+ for (yy = dy; yy < dy + ny; yy++) {
+ if (yy >= py && yy < py + ny)
+ continue;
+ grid_free_line(gd, yy);
+ }
+ if (dy != 0)
+ gd->linedata[dy - 1].flags &= ~GRID_LINE_WRAPPED;
+
+ memmove(&gd->linedata[dy], &gd->linedata[py],
+ ny * (sizeof *gd->linedata));
+
+ /*
+ * Wipe any lines that have been moved (without freeing them - they are
+ * still present).
+ */
+ for (yy = py; yy < py + ny; yy++) {
+ if (yy < dy || yy >= dy + ny)
+ grid_empty_line(gd, yy, bg);
+ }
+ if (py != 0 && (py < dy || py >= dy + ny))
+ gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
+}
+
+/* Move a group of cells. */
+void
+grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
+ u_int bg)
+{
+ struct grid_line *gl;
+ u_int xx;
+
+ if (nx == 0 || px == dx)
+ return;
+
+ if (grid_check_y(gd, __func__, py) != 0)
+ return;
+ gl = &gd->linedata[py];
+
+ grid_expand_line(gd, py, px + nx, 8);
+ grid_expand_line(gd, py, dx + nx, 8);
+ memmove(&gl->celldata[dx], &gl->celldata[px],
+ nx * sizeof *gl->celldata);
+ if (dx + nx > gl->cellused)
+ gl->cellused = dx + nx;
+
+ /* Wipe any cells that have been moved. */
+ for (xx = px; xx < px + nx; xx++) {
+ if (xx >= dx && xx < dx + nx)
+ continue;
+ grid_clear_cell(gd, xx, py, bg);
+ }
+}
+
+/* Get ANSI foreground sequence. */
+static size_t
+grid_string_cells_fg(const struct grid_cell *gc, int *values)
+{
+ size_t n;
+ u_char r, g, b;
+
+ n = 0;
+ if (gc->fg & COLOUR_FLAG_256) {
+ values[n++] = 38;
+ values[n++] = 5;
+ values[n++] = gc->fg & 0xff;
+ } else if (gc->fg & COLOUR_FLAG_RGB) {
+ values[n++] = 38;
+ values[n++] = 2;
+ colour_split_rgb(gc->fg, &r, &g, &b);
+ values[n++] = r;
+ values[n++] = g;
+ values[n++] = b;
+ } else {
+ switch (gc->fg) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ values[n++] = gc->fg + 30;
+ break;
+ case 8:
+ values[n++] = 39;
+ break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ values[n++] = gc->fg;
+ break;
+ }
+ }
+ return (n);
+}
+
+/* Get ANSI background sequence. */
+static size_t
+grid_string_cells_bg(const struct grid_cell *gc, int *values)
+{
+ size_t n;
+ u_char r, g, b;
+
+ n = 0;
+ if (gc->bg & COLOUR_FLAG_256) {
+ values[n++] = 48;
+ values[n++] = 5;
+ values[n++] = gc->bg & 0xff;
+ } else if (gc->bg & COLOUR_FLAG_RGB) {
+ values[n++] = 48;
+ values[n++] = 2;
+ colour_split_rgb(gc->bg, &r, &g, &b);
+ values[n++] = r;
+ values[n++] = g;
+ values[n++] = b;
+ } else {
+ switch (gc->bg) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ values[n++] = gc->bg + 40;
+ break;
+ case 8:
+ values[n++] = 49;
+ break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ values[n++] = gc->bg + 10;
+ break;
+ }
+ }
+ return (n);
+}
+
+/* Get underscore colour sequence. */
+static size_t
+grid_string_cells_us(const struct grid_cell *gc, int *values)
+{
+ size_t n;
+ u_char r, g, b;
+
+ n = 0;
+ if (gc->us & COLOUR_FLAG_256) {
+ values[n++] = 58;
+ values[n++] = 5;
+ values[n++] = gc->us & 0xff;
+ } else if (gc->us & COLOUR_FLAG_RGB) {
+ values[n++] = 58;
+ values[n++] = 2;
+ colour_split_rgb(gc->us, &r, &g, &b);
+ values[n++] = r;
+ values[n++] = g;
+ values[n++] = b;
+ }
+ return (n);
+}
+
+/* Add on SGR code. */
+static void
+grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc,
+ int *oldc, size_t nnewc, size_t noldc, int escape_c0)
+{
+ u_int i;
+ char tmp[64];
+
+ if (nnewc != 0 &&
+ (nnewc != noldc ||
+ memcmp(newc, oldc, nnewc * sizeof newc[0]) != 0 ||
+ (n != 0 && s[0] == 0))) {
+ if (escape_c0)
+ strlcat(buf, "\\033[", len);
+ else
+ strlcat(buf, "\033[", len);
+ for (i = 0; i < nnewc; i++) {
+ if (i + 1 < nnewc)
+ xsnprintf(tmp, sizeof tmp, "%d;", newc[i]);
+ else
+ xsnprintf(tmp, sizeof tmp, "%d", newc[i]);
+ strlcat(buf, tmp, len);
+ }
+ strlcat(buf, "m", len);
+ }
+}
+
+/*
+ * Returns ANSI code to set particular attributes (colour, bold and so on)
+ * given a current state.
+ */
+static void
+grid_string_cells_code(const struct grid_cell *lastgc,
+ const struct grid_cell *gc, char *buf, size_t len, int escape_c0)
+{
+ int oldc[64], newc[64], s[128];
+ size_t noldc, nnewc, n, i;
+ u_int attr = gc->attr, lastattr = lastgc->attr;
+ char tmp[64];
+
+ struct {
+ u_int mask;
+ u_int code;
+ } attrs[] = {
+ { GRID_ATTR_BRIGHT, 1 },
+ { GRID_ATTR_DIM, 2 },
+ { GRID_ATTR_ITALICS, 3 },
+ { GRID_ATTR_UNDERSCORE, 4 },
+ { GRID_ATTR_BLINK, 5 },
+ { GRID_ATTR_REVERSE, 7 },
+ { GRID_ATTR_HIDDEN, 8 },
+ { GRID_ATTR_STRIKETHROUGH, 9 },
+ { GRID_ATTR_UNDERSCORE_2, 42 },
+ { GRID_ATTR_UNDERSCORE_3, 43 },
+ { GRID_ATTR_UNDERSCORE_4, 44 },
+ { GRID_ATTR_UNDERSCORE_5, 45 },
+ { GRID_ATTR_OVERLINE, 53 },
+ };
+ n = 0;
+
+ /* If any attribute is removed, begin with 0. */
+ for (i = 0; i < nitems(attrs); i++) {
+ if (((~attr & attrs[i].mask) &&
+ (lastattr & attrs[i].mask)) ||
+ (lastgc->us != 0 && gc->us == 0)) {
+ s[n++] = 0;
+ lastattr &= GRID_ATTR_CHARSET;
+ break;
+ }
+ }
+ /* For each attribute that is newly set, add its code. */
+ for (i = 0; i < nitems(attrs); i++) {
+ if ((attr & attrs[i].mask) && !(lastattr & attrs[i].mask))
+ s[n++] = attrs[i].code;
+ }
+
+ /* Write the attributes. */
+ *buf = '\0';
+ if (n > 0) {
+ if (escape_c0)
+ strlcat(buf, "\\033[", len);
+ else
+ strlcat(buf, "\033[", len);
+ for (i = 0; i < n; i++) {
+ if (s[i] < 10)
+ xsnprintf(tmp, sizeof tmp, "%d", s[i]);
+ else {
+ xsnprintf(tmp, sizeof tmp, "%d:%d", s[i] / 10,
+ s[i] % 10);
+ }
+ strlcat(buf, tmp, len);
+ if (i + 1 < n)
+ strlcat(buf, ";", len);
+ }
+ strlcat(buf, "m", len);
+ }
+
+ /* If the foreground colour changed, write its parameters. */
+ 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);
+
+ /* 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);
+
+ /* If the underscore colour changed, append its parameters. */
+ nnewc = grid_string_cells_us(gc, newc);
+ noldc = grid_string_cells_us(lastgc, oldc);
+ grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc,
+ escape_c0);
+
+ /* Append shift in/shift out if needed. */
+ if ((attr & GRID_ATTR_CHARSET) && !(lastattr & GRID_ATTR_CHARSET)) {
+ if (escape_c0)
+ strlcat(buf, "\\016", len); /* SO */
+ else
+ strlcat(buf, "\016", len); /* SO */
+ }
+ if (!(attr & GRID_ATTR_CHARSET) && (lastattr & GRID_ATTR_CHARSET)) {
+ if (escape_c0)
+ strlcat(buf, "\\017", len); /* SI */
+ else
+ strlcat(buf, "\017", len); /* SI */
+ }
+}
+
+/* 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 gc;
+ static struct grid_cell lastgc1;
+ const char *data;
+ char *buf, code[128];
+ size_t len, off, size, codelen;
+ u_int xx;
+ const struct grid_line *gl;
+
+ if (lastgc != NULL && *lastgc == NULL) {
+ memcpy(&lastgc1, &grid_default_cell, sizeof lastgc1);
+ *lastgc = &lastgc1;
+ }
+
+ len = 128;
+ buf = xmalloc(len);
+ off = 0;
+
+ gl = grid_peek_line(gd, py);
+ for (xx = px; xx < px + nx; xx++) {
+ if (gl == NULL || xx >= gl->cellused)
+ break;
+ grid_get_cell(gd, xx, py, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ continue;
+
+ if (with_codes) {
+ grid_string_cells_code(*lastgc, &gc, code, sizeof code,
+ escape_c0);
+ codelen = strlen(code);
+ memcpy(*lastgc, &gc, sizeof **lastgc);
+ } else
+ codelen = 0;
+
+ data = gc.data.data;
+ size = gc.data.size;
+ if (escape_c0 && size == 1 && *data == '\\') {
+ data = "\\\\";
+ size = 2;
+ }
+
+ while (len < off + size + codelen + 1) {
+ buf = xreallocarray(buf, 2, len);
+ len *= 2;
+ }
+
+ if (codelen != 0) {
+ memcpy(buf + off, code, codelen);
+ off += codelen;
+ }
+ memcpy(buf + off, data, size);
+ off += size;
+ }
+
+ if (trim) {
+ while (off > 0 && buf[off - 1] == ' ')
+ off--;
+ }
+ buf[off] = '\0';
+
+ return (buf);
+}
+
+/*
+ * Duplicate a set of lines between two grids. Both source and destination
+ * should be big enough.
+ */
+void
+grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
+ u_int ny)
+{
+ struct grid_line *dstl, *srcl;
+ u_int yy;
+
+ if (dy + ny > dst->hsize + dst->sy)
+ ny = dst->hsize + dst->sy - dy;
+ if (sy + ny > src->hsize + src->sy)
+ ny = src->hsize + src->sy - sy;
+ grid_free_lines(dst, dy, ny);
+
+ for (yy = 0; yy < ny; yy++) {
+ srcl = &src->linedata[sy];
+ dstl = &dst->linedata[dy];
+
+ memcpy(dstl, srcl, sizeof *dstl);
+ if (srcl->cellsize != 0) {
+ dstl->celldata = xreallocarray(NULL,
+ srcl->cellsize, sizeof *dstl->celldata);
+ memcpy(dstl->celldata, srcl->celldata,
+ srcl->cellsize * sizeof *dstl->celldata);
+ } else
+ dstl->celldata = NULL;
+ if (srcl->extdsize != 0) {
+ dstl->extdsize = srcl->extdsize;
+ dstl->extddata = xreallocarray(NULL, dstl->extdsize,
+ sizeof *dstl->extddata);
+ memcpy(dstl->extddata, srcl->extddata, dstl->extdsize *
+ sizeof *dstl->extddata);
+ } else
+ dstl->extddata = NULL;
+
+ sy++;
+ dy++;
+ }
+}
+
+/* Mark line as dead. */
+static void
+grid_reflow_dead(struct grid_line *gl)
+{
+ memset(gl, 0, sizeof *gl);
+ gl->flags = GRID_LINE_DEAD;
+}
+
+/* Add lines, return the first new one. */
+static struct grid_line *
+grid_reflow_add(struct grid *gd, u_int n)
+{
+ struct grid_line *gl;
+ u_int sy = gd->sy + n;
+
+ gd->linedata = xreallocarray(gd->linedata, sy, sizeof *gd->linedata);
+ gl = &gd->linedata[gd->sy];
+ memset(gl, 0, n * (sizeof *gl));
+ gd->sy = sy;
+ return (gl);
+}
+
+/* Move a line across. */
+static struct grid_line *
+grid_reflow_move(struct grid *gd, struct grid_line *from)
+{
+ struct grid_line *to;
+
+ to = grid_reflow_add(gd, 1);
+ memcpy(to, from, sizeof *to);
+ grid_reflow_dead(from);
+ return (to);
+}
+
+/* Join line below onto this one. */
+static void
+grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
+ u_int width, int already)
+{
+ struct grid_line *gl, *from = NULL;
+ struct grid_cell gc;
+ u_int lines, left, i, to, line, want = 0;
+ u_int at;
+ int wrapped = 1;
+
+ /*
+ * Add a new target line.
+ */
+ if (!already) {
+ to = target->sy;
+ gl = grid_reflow_move(target, &gd->linedata[yy]);
+ } else {
+ to = target->sy - 1;
+ gl = &target->linedata[to];
+ }
+ at = gl->cellused;
+
+ /*
+ * Loop until no more to consume or the target line is full.
+ */
+ lines = 0;
+ for (;;) {
+ /*
+ * If this is now the last line, there is nothing more to be
+ * done.
+ */
+ if (yy + 1 + lines == gd->hsize + gd->sy)
+ break;
+ line = yy + 1 + lines;
+
+ /* If the next line is empty, skip it. */
+ if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
+ wrapped = 0;
+ if (gd->linedata[line].cellused == 0) {
+ if (!wrapped)
+ break;
+ lines++;
+ continue;
+ }
+
+ /*
+ * Is the destination line now full? Copy the first character
+ * separately because we need to leave "from" set to the last
+ * line if this line is full.
+ */
+ grid_get_cell1(&gd->linedata[line], 0, &gc);
+ if (width + gc.data.width > sx)
+ break;
+ width += gc.data.width;
+ grid_set_cell(target, at, to, &gc);
+ at++;
+
+ /* Join as much more as possible onto the current line. */
+ from = &gd->linedata[line];
+ for (want = 1; want < from->cellused; want++) {
+ grid_get_cell1(from, want, &gc);
+ if (width + gc.data.width > sx)
+ break;
+ width += gc.data.width;
+
+ grid_set_cell(target, at, to, &gc);
+ at++;
+ }
+ lines++;
+
+ /*
+ * If this line wasn't wrapped or we didn't consume the entire
+ * line, don't try to join any further lines.
+ */
+ if (!wrapped || want != from->cellused || width == sx)
+ break;
+ }
+ if (lines == 0)
+ return;
+
+ /*
+ * If we didn't consume the entire final line, then remove what we did
+ * consume. If we consumed the entire line and it wasn't wrapped,
+ * remove the wrap flag from this line.
+ */
+ left = from->cellused - want;
+ if (left != 0) {
+ grid_move_cells(gd, 0, want, yy + lines, left, 8);
+ from->cellsize = from->cellused = left;
+ lines--;
+ } else if (!wrapped)
+ gl->flags &= ~GRID_LINE_WRAPPED;
+
+ /* Remove the lines that were completely consumed. */
+ for (i = yy + 1; i < yy + 1 + lines; i++) {
+ free(gd->linedata[i].celldata);
+ free(gd->linedata[i].extddata);
+ grid_reflow_dead(&gd->linedata[i]);
+ }
+
+ /* Adjust scroll position. */
+ if (gd->hscrolled > to + lines)
+ gd->hscrolled -= lines;
+ else if (gd->hscrolled > to)
+ gd->hscrolled = to;
+}
+
+/* Split this line into several new ones */
+static void
+grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
+ u_int at)
+{
+ struct grid_line *gl = &gd->linedata[yy], *first;
+ struct grid_cell gc;
+ u_int line, lines, width, i, xx;
+ u_int used = gl->cellused;
+ int flags = gl->flags;
+
+ /* How many lines do we need to insert? We know we need at least two. */
+ if (~gl->flags & GRID_LINE_EXTENDED)
+ lines = 1 + (gl->cellused - 1) / sx;
+ else {
+ lines = 2;
+ width = 0;
+ for (i = at; i < used; i++) {
+ grid_get_cell1(gl, i, &gc);
+ if (width + gc.data.width > sx) {
+ lines++;
+ width = 0;
+ }
+ width += gc.data.width;
+ }
+ }
+
+ /* Insert new lines. */
+ line = target->sy + 1;
+ first = grid_reflow_add(target, lines);
+
+ /* Copy sections from the original line. */
+ width = 0;
+ xx = 0;
+ for (i = at; i < used; i++) {
+ grid_get_cell1(gl, i, &gc);
+ if (width + gc.data.width > sx) {
+ target->linedata[line].flags |= GRID_LINE_WRAPPED;
+
+ line++;
+ width = 0;
+ xx = 0;
+ }
+ width += gc.data.width;
+ grid_set_cell(target, xx, line, &gc);
+ xx++;
+ }
+ if (flags & GRID_LINE_WRAPPED)
+ target->linedata[line].flags |= GRID_LINE_WRAPPED;
+
+ /* Move the remainder of the original line. */
+ gl->cellsize = gl->cellused = at;
+ gl->flags |= GRID_LINE_WRAPPED;
+ memcpy(first, gl, sizeof *first);
+ grid_reflow_dead(gl);
+
+ /* Adjust the scroll position. */
+ if (yy <= gd->hscrolled)
+ gd->hscrolled += lines - 1;
+
+ /*
+ * If the original line had the wrapped flag and there is still space
+ * in the last new line, try to join with the next lines.
+ */
+ if (width < sx && (flags & GRID_LINE_WRAPPED))
+ grid_reflow_join(target, gd, sx, yy, width, 1);
+}
+
+/* Reflow lines on grid to new width. */
+void
+grid_reflow(struct grid *gd, u_int sx)
+{
+ struct grid *target;
+ struct grid_line *gl;
+ struct grid_cell gc;
+ u_int yy, width, i, at;
+
+ /*
+ * Create a destination grid. This is just used as a container for the
+ * line data and may not be fully valid.
+ */
+ target = grid_create(gd->sx, 0, 0);
+
+ /*
+ * Loop over each source line.
+ */
+ for (yy = 0; yy < gd->hsize + gd->sy; yy++) {
+ gl = &gd->linedata[yy];
+ if (gl->flags & GRID_LINE_DEAD)
+ continue;
+
+ /*
+ * Work out the width of this line. at is the point at which
+ * the available width is hit, and width is the full line
+ * width.
+ */
+ at = width = 0;
+ if (~gl->flags & GRID_LINE_EXTENDED) {
+ width = gl->cellused;
+ if (width > sx)
+ at = sx;
+ else
+ at = width;
+ } else {
+ for (i = 0; i < gl->cellused; i++) {
+ grid_get_cell1(gl, i, &gc);
+ if (at == 0 && width + gc.data.width > sx)
+ at = i;
+ width += gc.data.width;
+ }
+ }
+
+ /*
+ * If the line is exactly right, just move it across
+ * unchanged.
+ */
+ if (width == sx) {
+ grid_reflow_move(target, gl);
+ continue;
+ }
+
+ /*
+ * If the line is too big, it needs to be split, whether or not
+ * it was previously wrapped.
+ */
+ if (width > sx) {
+ grid_reflow_split(target, gd, sx, yy, at);
+ continue;
+ }
+
+ /*
+ * If the line was previously wrapped, join as much as possible
+ * of the next line.
+ */
+ if (gl->flags & GRID_LINE_WRAPPED)
+ grid_reflow_join(target, gd, sx, yy, width, 0);
+ else
+ grid_reflow_move(target, gl);
+ }
+
+ /*
+ * Replace the old grid with the new.
+ */
+ if (target->sy < gd->sy)
+ grid_reflow_add(target, gd->sy - target->sy);
+ gd->hsize = target->sy - gd->sy;
+ if (gd->hscrolled > gd->hsize)
+ gd->hscrolled = gd->hsize;
+ free(gd->linedata);
+ gd->linedata = target->linedata;
+ free(target);
+}
+
+/* Convert to position based on wrapped lines. */
+void
+grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
+{
+ u_int ax = 0, ay = 0, yy;
+
+ for (yy = 0; yy < py; yy++) {
+ if (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
+ ax += gd->linedata[yy].cellused;
+ else {
+ ax = 0;
+ ay++;
+ }
+ }
+ if (px >= gd->linedata[yy].cellused)
+ ax = UINT_MAX;
+ else
+ ax += px;
+ *wx = ax;
+ *wy = ay;
+}
+
+/* Convert position based on wrapped lines back. */
+void
+grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
+{
+ u_int yy, ay = 0;
+
+ for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
+ if (ay == wy)
+ break;
+ if (~gd->linedata[yy].flags & GRID_LINE_WRAPPED)
+ ay++;
+ }
+
+ /*
+ * yy is now 0 on the unwrapped line which contains wx. Walk forwards
+ * until we find the end or the line now containing wx.
+ */
+ if (wx == UINT_MAX) {
+ while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
+ yy++;
+ wx = gd->linedata[yy].cellused;
+ } else {
+ while (gd->linedata[yy].flags & GRID_LINE_WRAPPED) {
+ if (wx < gd->linedata[yy].cellused)
+ break;
+ wx -= gd->linedata[yy].cellused;
+ yy++;
+ }
+ }
+ *px = wx;
+ *py = yy;
+}
+
+/* Get length of line. */
+u_int
+grid_line_length(struct grid *gd, u_int py)
+{
+ struct grid_cell gc;
+ u_int px;
+
+ px = grid_get_line(gd, py)->cellsize;
+ if (px > gd->sx)
+ px = gd->sx;
+ while (px > 0) {
+ grid_get_cell(gd, px - 1, py, &gc);
+ if ((gc.flags & GRID_FLAG_PADDING) ||
+ gc.data.size != 1 ||
+ *gc.data.data != ' ')
+ break;
+ px--;
+ }
+ return (px);
+}
diff --git a/input-keys.c b/input-keys.c
new file mode 100644
index 0000000..ebf6133
--- /dev/null
+++ b/input-keys.c
@@ -0,0 +1,664 @@
+/* $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"
+
+/*
+ * This file is rather misleadingly named, it contains the code which takes a
+ * key code and translates it into something suitable to be sent to the
+ * application running in a pane (similar to input.c does in the other
+ * direction with output).
+ */
+
+static void input_key_mouse(struct window_pane *, struct mouse_event *);
+
+/* Entry in the key tree. */
+struct input_key_entry {
+ key_code key;
+ const char *data;
+
+ RB_ENTRY(input_key_entry) entry;
+};
+RB_HEAD(input_key_tree, input_key_entry);
+
+/* Tree of input keys. */
+static int input_key_cmp(struct input_key_entry *,
+ struct input_key_entry *);
+RB_GENERATE_STATIC(input_key_tree, input_key_entry, entry, input_key_cmp);
+struct input_key_tree input_key_tree = RB_INITIALIZER(&input_key_tree);
+
+/* List of default keys, the tree is built from this. */
+static struct input_key_entry input_key_defaults[] = {
+ /* Paste keys. */
+ { .key = KEYC_PASTE_START,
+ .data = "\033[200~"
+ },
+ { .key = KEYC_PASTE_END,
+ .data = "\033[201~"
+ },
+
+ /* Function keys. */
+ { .key = KEYC_F1,
+ .data = "\033OP"
+ },
+ { .key = KEYC_F2,
+ .data = "\033OQ"
+ },
+ { .key = KEYC_F3,
+ .data = "\033OR"
+ },
+ { .key = KEYC_F4,
+ .data = "\033OS"
+ },
+ { .key = KEYC_F5,
+ .data = "\033[15~"
+ },
+ { .key = KEYC_F6,
+ .data = "\033[17~"
+ },
+ { .key = KEYC_F7,
+ .data = "\033[18~"
+ },
+ { .key = KEYC_F8,
+ .data = "\033[19~"
+ },
+ { .key = KEYC_F9,
+ .data = "\033[20~"
+ },
+ { .key = KEYC_F10,
+ .data = "\033[21~"
+ },
+ { .key = KEYC_F11,
+ .data = "\033[23~"
+ },
+ { .key = KEYC_F12,
+ .data = "\033[24~"
+ },
+ { .key = KEYC_IC,
+ .data = "\033[2~"
+ },
+ { .key = KEYC_DC,
+ .data = "\033[3~"
+ },
+ { .key = KEYC_HOME,
+ .data = "\033[1~"
+ },
+ { .key = KEYC_END,
+ .data = "\033[4~"
+ },
+ { .key = KEYC_NPAGE,
+ .data = "\033[6~"
+ },
+ { .key = KEYC_PPAGE,
+ .data = "\033[5~"
+ },
+ { .key = KEYC_BTAB,
+ .data = "\033[Z"
+ },
+
+ /* Arrow keys. */
+ { .key = KEYC_UP|KEYC_CURSOR,
+ .data = "\033OA"
+ },
+ { .key = KEYC_DOWN|KEYC_CURSOR,
+ .data = "\033OB"
+ },
+ { .key = KEYC_RIGHT|KEYC_CURSOR,
+ .data = "\033OC"
+ },
+ { .key = KEYC_LEFT|KEYC_CURSOR,
+ .data = "\033OD"
+ },
+ { .key = KEYC_UP,
+ .data = "\033[A"
+ },
+ { .key = KEYC_DOWN,
+ .data = "\033[B"
+ },
+ { .key = KEYC_RIGHT,
+ .data = "\033[C"
+ },
+ { .key = KEYC_LEFT,
+ .data = "\033[D"
+ },
+
+ /* Keypad keys. */
+ { .key = KEYC_KP_SLASH|KEYC_KEYPAD,
+ .data = "\033Oo"
+ },
+ { .key = KEYC_KP_STAR|KEYC_KEYPAD,
+ .data = "\033Oj"
+ },
+ { .key = KEYC_KP_MINUS|KEYC_KEYPAD,
+ .data = "\033Om"
+ },
+ { .key = KEYC_KP_SEVEN|KEYC_KEYPAD,
+ .data = "\033Ow"
+ },
+ { .key = KEYC_KP_EIGHT|KEYC_KEYPAD,
+ .data = "\033Ox"
+ },
+ { .key = KEYC_KP_NINE|KEYC_KEYPAD,
+ .data = "\033Oy"
+ },
+ { .key = KEYC_KP_PLUS|KEYC_KEYPAD,
+ .data = "\033Ok"
+ },
+ { .key = KEYC_KP_FOUR|KEYC_KEYPAD,
+ .data = "\033Ot"
+ },
+ { .key = KEYC_KP_FIVE|KEYC_KEYPAD,
+ .data = "\033Ou"
+ },
+ { .key = KEYC_KP_SIX|KEYC_KEYPAD,
+ .data = "\033Ov"
+ },
+ { .key = KEYC_KP_ONE|KEYC_KEYPAD,
+ .data = "\033Oq"
+ },
+ { .key = KEYC_KP_TWO|KEYC_KEYPAD,
+ .data = "\033Or"
+ },
+ { .key = KEYC_KP_THREE|KEYC_KEYPAD,
+ .data = "\033Os"
+ },
+ { .key = KEYC_KP_ENTER|KEYC_KEYPAD,
+ .data = "\033OM"
+ },
+ { .key = KEYC_KP_ZERO|KEYC_KEYPAD,
+ .data = "\033Op"
+ },
+ { .key = KEYC_KP_PERIOD|KEYC_KEYPAD,
+ .data = "\033On"
+ },
+ { .key = KEYC_KP_SLASH,
+ .data = "/"
+ },
+ { .key = KEYC_KP_STAR,
+ .data = "*"
+ },
+ { .key = KEYC_KP_MINUS,
+ .data = "-"
+ },
+ { .key = KEYC_KP_SEVEN,
+ .data = "7"
+ },
+ { .key = KEYC_KP_EIGHT,
+ .data = "8"
+ },
+ { .key = KEYC_KP_NINE,
+ .data = "9"
+ },
+ { .key = KEYC_KP_PLUS,
+ .data = "+"
+ },
+ { .key = KEYC_KP_FOUR,
+ .data = "4"
+ },
+ { .key = KEYC_KP_FIVE,
+ .data = "5"
+ },
+ { .key = KEYC_KP_SIX,
+ .data = "6"
+ },
+ { .key = KEYC_KP_ONE,
+ .data = "1"
+ },
+ { .key = KEYC_KP_TWO,
+ .data = "2"
+ },
+ { .key = KEYC_KP_THREE,
+ .data = "3"
+ },
+ { .key = KEYC_KP_ENTER,
+ .data = "\n"
+ },
+ { .key = KEYC_KP_ZERO,
+ .data = "0"
+ },
+ { .key = KEYC_KP_PERIOD,
+ .data = "."
+ },
+
+ /* Keys with an embedded modifier. */
+ { .key = KEYC_F1|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_P"
+ },
+ { .key = KEYC_F2|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_Q"
+ },
+ { .key = KEYC_F3|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_R"
+ },
+ { .key = KEYC_F4|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_S"
+ },
+ { .key = KEYC_F5|KEYC_BUILD_MODIFIERS,
+ .data = "\033[15;_~"
+ },
+ { .key = KEYC_F6|KEYC_BUILD_MODIFIERS,
+ .data = "\033[17;_~"
+ },
+ { .key = KEYC_F7|KEYC_BUILD_MODIFIERS,
+ .data = "\033[18;_~"
+ },
+ { .key = KEYC_F8|KEYC_BUILD_MODIFIERS,
+ .data = "\033[19;_~"
+ },
+ { .key = KEYC_F9|KEYC_BUILD_MODIFIERS,
+ .data = "\033[20;_~"
+ },
+ { .key = KEYC_F10|KEYC_BUILD_MODIFIERS,
+ .data = "\033[21;_~"
+ },
+ { .key = KEYC_F11|KEYC_BUILD_MODIFIERS,
+ .data = "\033[23;_~"
+ },
+ { .key = KEYC_F12|KEYC_BUILD_MODIFIERS,
+ .data = "\033[24;_~"
+ },
+ { .key = KEYC_UP|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_A"
+ },
+ { .key = KEYC_DOWN|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_B"
+ },
+ { .key = KEYC_RIGHT|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_C"
+ },
+ { .key = KEYC_LEFT|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_D"
+ },
+ { .key = KEYC_HOME|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_H"
+ },
+ { .key = KEYC_END|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_F"
+ },
+ { .key = KEYC_PPAGE|KEYC_BUILD_MODIFIERS,
+ .data = "\033[5;_~"
+ },
+ { .key = KEYC_NPAGE|KEYC_BUILD_MODIFIERS,
+ .data = "\033[6;_~"
+ },
+ { .key = KEYC_IC|KEYC_BUILD_MODIFIERS,
+ .data = "\033[2;_~"
+ },
+ { .key = KEYC_DC|KEYC_BUILD_MODIFIERS,
+ .data = "\033[3;_~"
+ }
+};
+static const key_code input_key_modifiers[] = {
+ 0,
+ 0,
+ KEYC_SHIFT,
+ KEYC_META|KEYC_IMPLIED_META,
+ KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META,
+ KEYC_CTRL,
+ KEYC_SHIFT|KEYC_CTRL,
+ KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL,
+ KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL
+};
+
+/* Input key comparison function. */
+static int
+input_key_cmp(struct input_key_entry *ike1, struct input_key_entry *ike2)
+{
+ if (ike1->key < ike2->key)
+ return (-1);
+ if (ike1->key > ike2->key)
+ return (1);
+ return (0);
+}
+
+/* Look for key in tree. */
+static struct input_key_entry *
+input_key_get(key_code key)
+{
+ struct input_key_entry entry = { .key = key };
+
+ return (RB_FIND(input_key_tree, &input_key_tree, &entry));
+}
+
+/* Split a character into two UTF-8 bytes. */
+static size_t
+input_key_split2(u_int c, u_char *dst)
+{
+ if (c > 0x7f) {
+ dst[0] = (c >> 6) | 0xc0;
+ dst[1] = (c & 0x3f) | 0x80;
+ return (2);
+ }
+ dst[0] = c;
+ return (1);
+}
+
+/* Build input key tree. */
+void
+input_key_build(void)
+{
+ struct input_key_entry *ike, *new;
+ u_int i, j;
+ char *data;
+ key_code key;
+
+ for (i = 0; i < nitems(input_key_defaults); i++) {
+ ike = &input_key_defaults[i];
+ if (~ike->key & KEYC_BUILD_MODIFIERS) {
+ RB_INSERT(input_key_tree, &input_key_tree, ike);
+ continue;
+ }
+
+ for (j = 2; j < nitems(input_key_modifiers); j++) {
+ key = (ike->key & ~KEYC_BUILD_MODIFIERS);
+ data = xstrdup(ike->data);
+ data[strcspn(data, "_")] = '0' + j;
+
+ new = xcalloc(1, sizeof *new);
+ new->key = key|input_key_modifiers[j];
+ new->data = data;
+ RB_INSERT(input_key_tree, &input_key_tree, new);
+ }
+ }
+
+ RB_FOREACH(ike, input_key_tree, &input_key_tree) {
+ log_debug("%s: 0x%llx (%s) is %s", __func__, ike->key,
+ key_string_lookup_key(ike->key, 1), ike->data);
+ }
+}
+
+/* Translate a key code into an output key sequence for a pane. */
+int
+input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m)
+{
+ if (log_get_level() != 0) {
+ log_debug("writing key 0x%llx (%s) to %%%u", key,
+ key_string_lookup_key(key, 1), wp->id);
+ }
+
+ if (KEYC_IS_MOUSE(key)) {
+ if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id)
+ input_key_mouse(wp, m);
+ return (0);
+ }
+ return (input_key(wp->screen, wp->event, key));
+}
+
+static void
+input_key_write(const char *from, struct bufferevent *bev, const char *data,
+ size_t size)
+{
+ log_debug("%s: %.*s", from, (int)size, data);
+ bufferevent_write(bev, data, size);
+}
+
+/* Translate a key code into an output key sequence. */
+int
+input_key(struct screen *s, struct bufferevent *bev, key_code key)
+{
+ struct input_key_entry *ike;
+ key_code justkey, newkey, outkey, modifiers;
+ struct utf8_data ud;
+ char tmp[64], modifier;
+
+ /* Mouse keys need a pane. */
+ if (KEYC_IS_MOUSE(key))
+ return (0);
+
+ /* Literal keys go as themselves (can't be more than eight bits). */
+ if (key & KEYC_LITERAL) {
+ ud.data[0] = (u_char)key;
+ input_key_write(__func__, bev, &ud.data[0], 1);
+ return (0);
+ }
+
+ /* Is this backspace? */
+ if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) {
+ newkey = options_get_number(global_options, "backspace");
+ if (newkey >= 0x7f)
+ newkey = '\177';
+ key = newkey|(key & (KEYC_MASK_MODIFIERS|KEYC_MASK_FLAGS));
+ }
+
+ /*
+ * If this is a normal 7-bit key, just send it, with a leading escape
+ * if necessary. If it is a UTF-8 key, split it and send it.
+ */
+ justkey = (key & ~(KEYC_META|KEYC_IMPLIED_META));
+ if (justkey <= 0x7f) {
+ if (key & KEYC_META)
+ input_key_write(__func__, bev, "\033", 1);
+ ud.data[0] = justkey;
+ input_key_write(__func__, bev, &ud.data[0], 1);
+ return (0);
+ }
+ if (KEYC_IS_UNICODE(justkey)) {
+ if (key & KEYC_META)
+ input_key_write(__func__, bev, "\033", 1);
+ utf8_to_data(justkey, &ud);
+ input_key_write(__func__, bev, ud.data, ud.size);
+ return (0);
+ }
+
+ /*
+ * Look up in the tree. If not in application keypad or cursor mode,
+ * remove the flags from the key.
+ */
+ if (~s->mode & MODE_KKEYPAD)
+ key &= ~KEYC_KEYPAD;
+ if (~s->mode & MODE_KCURSOR)
+ key &= ~KEYC_CURSOR;
+ 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) {
+ log_debug("found key 0x%llx: \"%s\"", key, ike->data);
+ 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));
+ return (0);
+ }
+
+ /* No builtin key sequence; construct an extended key sequence. */
+ if (~s->mode & MODE_KEXTENDED) {
+ if ((key & KEYC_MASK_MODIFIERS) != KEYC_CTRL)
+ goto missing;
+ justkey = (key & KEYC_MASK_KEY);
+ switch (justkey) {
+ case ' ':
+ case '2':
+ key = 0|(key & ~KEYC_MASK_KEY);
+ break;
+ case '|':
+ key = 28|(key & ~KEYC_MASK_KEY);
+ break;
+ case '6':
+ key = 30|(key & ~KEYC_MASK_KEY);
+ break;
+ case '-':
+ case '/':
+ key = 31|(key & ~KEYC_MASK_KEY);
+ break;
+ case '?':
+ key = 127|(key & ~KEYC_MASK_KEY);
+ break;
+ default:
+ if (justkey >= 'A' && justkey <= '_')
+ key = (justkey - 'A')|(key & ~KEYC_MASK_KEY);
+ else if (justkey >= 'a' && justkey <= '~')
+ key = (justkey - 96)|(key & ~KEYC_MASK_KEY);
+ else
+ return (0);
+ break;
+ }
+ return (input_key(s, bev, key & ~KEYC_CTRL));
+ }
+ outkey = (key & KEYC_MASK_KEY);
+ modifiers = (key & KEYC_MASK_MODIFIERS);
+ if (outkey < 32 && outkey != 9 && outkey != 13 && outkey != 27) {
+ outkey = 64 + outkey;
+ modifiers |= KEYC_CTRL;
+ }
+ switch (modifiers) {
+ case KEYC_SHIFT:
+ modifier = '2';
+ break;
+ case KEYC_META:
+ modifier = '3';
+ break;
+ case KEYC_SHIFT|KEYC_META:
+ modifier = '4';
+ break;
+ case KEYC_CTRL:
+ modifier = '5';
+ break;
+ case KEYC_SHIFT|KEYC_CTRL:
+ modifier = '6';
+ break;
+ case KEYC_META|KEYC_CTRL:
+ modifier = '7';
+ break;
+ case KEYC_SHIFT|KEYC_META|KEYC_CTRL:
+ modifier = '8';
+ break;
+ default:
+ goto missing;
+ }
+ xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier);
+ input_key_write(__func__, bev, tmp, strlen(tmp));
+ return (0);
+
+missing:
+ log_debug("key 0x%llx missing", key);
+ return (-1);
+}
+
+/* Get mouse event string. */
+int
+input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y,
+ const char **rbuf, size_t *rlen)
+{
+ static char buf[40];
+ size_t len;
+
+ *rbuf = NULL;
+ *rlen = 0;
+
+ /* If this pane is not in button or all mode, discard motion events. */
+ if (MOUSE_DRAG(m->b) && (s->mode & MOTION_MOUSE_MODES) == 0)
+ return (0);
+ if ((s->mode & ALL_MOUSE_MODES) == 0)
+ return (0);
+
+ /*
+ * If this event is a release event and not in all mode, discard it.
+ * In SGR mode we can tell absolutely because a release is normally
+ * shown by the last character. Without SGR, we check if the last
+ * buttons was also a release.
+ */
+ if (m->sgr_type != ' ') {
+ if (MOUSE_DRAG(m->sgr_b) &&
+ MOUSE_RELEASE(m->sgr_b) &&
+ (~s->mode & MODE_MOUSE_ALL))
+ return (0);
+ } else {
+ if (MOUSE_DRAG(m->b) &&
+ MOUSE_RELEASE(m->b) &&
+ MOUSE_RELEASE(m->lb) &&
+ (~s->mode & MODE_MOUSE_ALL))
+ return (0);
+ }
+
+ /*
+ * Use the SGR (1006) extension only if the application requested it
+ * and the underlying terminal also sent the event in this format (this
+ * is because an old style mouse release event cannot be converted into
+ * the new SGR format, since the released button is unknown). Otherwise
+ * pretend that tmux doesn't speak this extension, and fall back to the
+ * UTF-8 (1005) extension if the application requested, or to the
+ * legacy format.
+ */
+ if (m->sgr_type != ' ' && (s->mode & MODE_MOUSE_SGR)) {
+ len = xsnprintf(buf, sizeof buf, "\033[<%u;%u;%u%c",
+ m->sgr_b, x + 1, y + 1, m->sgr_type);
+ } else if (s->mode & MODE_MOUSE_UTF8) {
+ if (m->b > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_BTN_OFF ||
+ x > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF ||
+ y > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF)
+ return (0);
+ len = xsnprintf(buf, sizeof buf, "\033[M");
+ len += input_key_split2(m->b + MOUSE_PARAM_BTN_OFF, &buf[len]);
+ len += input_key_split2(x + MOUSE_PARAM_POS_OFF, &buf[len]);
+ len += input_key_split2(y + MOUSE_PARAM_POS_OFF, &buf[len]);
+ } else {
+ if (m->b + MOUSE_PARAM_BTN_OFF > MOUSE_PARAM_MAX)
+ return (0);
+
+ len = xsnprintf(buf, sizeof buf, "\033[M");
+ buf[len++] = m->b + MOUSE_PARAM_BTN_OFF;
+
+ /*
+ * The incoming x and y may be out of the range which can be
+ * supported by the "normal" mouse protocol. Clamp the
+ * coordinates to the supported range.
+ */
+ if (x + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX)
+ buf[len++] = MOUSE_PARAM_MAX;
+ else
+ buf[len++] = x + MOUSE_PARAM_POS_OFF;
+ if (y + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX)
+ buf[len++] = MOUSE_PARAM_MAX;
+ else
+ buf[len++] = y + MOUSE_PARAM_POS_OFF;
+ }
+
+ *rbuf = buf;
+ *rlen = len;
+ return (1);
+}
+
+/* Translate mouse and output. */
+static void
+input_key_mouse(struct window_pane *wp, struct mouse_event *m)
+{
+ struct screen *s = wp->screen;
+ u_int x, y;
+ const char *buf;
+ size_t len;
+
+ /* Ignore events if no mouse mode or the pane is not visible. */
+ if (m->ignore || (s->mode & ALL_MOUSE_MODES) == 0)
+ return;
+ if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
+ return;
+ if (!window_pane_visible(wp))
+ return;
+ if (!input_key_get_mouse(s, m, x, y, &buf, &len))
+ return;
+ log_debug("writing mouse %.*s to %%%u", (int)len, buf, wp->id);
+ input_key_write(__func__, wp->event, buf, len);
+}
diff --git a/input.c b/input.c
new file mode 100644
index 0000000..693b6f3
--- /dev/null
+++ b/input.c
@@ -0,0 +1,2796 @@
+/* $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 <netinet/in.h>
+
+#include <ctype.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "tmux.h"
+
+/*
+ * Based on the description by Paul Williams at:
+ *
+ * https://vt100.net/emu/dec_ansi_parser
+ *
+ * With the following changes:
+ *
+ * - 7-bit only.
+ *
+ * - Support for UTF-8.
+ *
+ * - OSC (but not APC) may be terminated by \007 as well as ST.
+ *
+ * - A state for APC similar to OSC. Some terminals appear to use this to set
+ * the title.
+ *
+ * - A state for the screen \033k...\033\\ sequence to rename a window. This is
+ * pretty stupid but not supporting it is more trouble than it is worth.
+ *
+ * - Special handling for ESC inside a DCS to allow arbitrary byte sequences to
+ * be passed to the underlying terminals.
+ */
+
+/* Input parser cell. */
+struct input_cell {
+ struct grid_cell cell;
+ int set;
+ int g0set; /* 1 if ACS */
+ int g1set; /* 1 if ACS */
+};
+
+/* Input parser argument. */
+struct input_param {
+ enum {
+ INPUT_MISSING,
+ INPUT_NUMBER,
+ INPUT_STRING
+ } type;
+ union {
+ int num;
+ char *str;
+ };
+};
+
+/* Input parser context. */
+struct input_ctx {
+ struct window_pane *wp;
+ struct bufferevent *event;
+ struct screen_write_ctx ctx;
+ struct colour_palette *palette;
+
+ struct input_cell cell;
+
+ struct input_cell old_cell;
+ u_int old_cx;
+ u_int old_cy;
+ int old_mode;
+
+ u_char interm_buf[4];
+ size_t interm_len;
+
+ u_char param_buf[64];
+ size_t param_len;
+
+#define INPUT_BUF_START 32
+#define INPUT_BUF_LIMIT 1048576
+ u_char *input_buf;
+ size_t input_len;
+ size_t input_space;
+ enum {
+ INPUT_END_ST,
+ INPUT_END_BEL
+ } input_end;
+
+ struct input_param param_list[24];
+ u_int param_list_len;
+
+ struct utf8_data utf8data;
+ int utf8started;
+
+ int ch;
+ int last;
+
+ int flags;
+#define INPUT_DISCARD 0x1
+
+ const struct input_state *state;
+
+ struct event timer;
+
+ /*
+ * All input received since we were last in the ground state. Sent to
+ * control clients on connection.
+ */
+ struct evbuffer *since_ground;
+};
+
+/* Helper functions. */
+struct input_transition;
+static int input_split(struct input_ctx *);
+static int input_get(struct input_ctx *, u_int, int, int);
+static void printflike(2, 3) input_reply(struct input_ctx *, const char *, ...);
+static void input_set_state(struct input_ctx *,
+ const struct input_transition *);
+static void input_reset_cell(struct input_ctx *);
+
+static void input_osc_4(struct input_ctx *, const char *);
+static void input_osc_10(struct input_ctx *, const char *);
+static void input_osc_11(struct input_ctx *, const char *);
+static void input_osc_12(struct input_ctx *, const char *);
+static void input_osc_52(struct input_ctx *, const char *);
+static void input_osc_104(struct input_ctx *, const char *);
+static void input_osc_110(struct input_ctx *, const char *);
+static void input_osc_111(struct input_ctx *, const char *);
+static void input_osc_112(struct input_ctx *, const char *);
+
+/* Transition entry/exit handlers. */
+static void input_clear(struct input_ctx *);
+static void input_ground(struct input_ctx *);
+static void input_enter_dcs(struct input_ctx *);
+static void input_enter_osc(struct input_ctx *);
+static void input_exit_osc(struct input_ctx *);
+static void input_enter_apc(struct input_ctx *);
+static void input_exit_apc(struct input_ctx *);
+static void input_enter_rename(struct input_ctx *);
+static void input_exit_rename(struct input_ctx *);
+
+/* Input state handlers. */
+static int input_print(struct input_ctx *);
+static int input_intermediate(struct input_ctx *);
+static int input_parameter(struct input_ctx *);
+static int input_input(struct input_ctx *);
+static int input_c0_dispatch(struct input_ctx *);
+static int input_esc_dispatch(struct input_ctx *);
+static int input_csi_dispatch(struct input_ctx *);
+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_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 *);
+static void input_csi_dispatch_sgr(struct input_ctx *);
+static int input_dcs_dispatch(struct input_ctx *);
+static int input_top_bit_set(struct input_ctx *);
+static int input_end_bel(struct input_ctx *);
+
+/* Command table comparison function. */
+static int input_table_compare(const void *, const void *);
+
+/* Command table entry. */
+struct input_table_entry {
+ int ch;
+ const char *interm;
+ int type;
+};
+
+/* Escape commands. */
+enum input_esc_type {
+ INPUT_ESC_DECALN,
+ INPUT_ESC_DECKPAM,
+ INPUT_ESC_DECKPNM,
+ INPUT_ESC_DECRC,
+ INPUT_ESC_DECSC,
+ INPUT_ESC_HTS,
+ INPUT_ESC_IND,
+ INPUT_ESC_NEL,
+ INPUT_ESC_RI,
+ INPUT_ESC_RIS,
+ INPUT_ESC_SCSG0_OFF,
+ INPUT_ESC_SCSG0_ON,
+ INPUT_ESC_SCSG1_OFF,
+ INPUT_ESC_SCSG1_ON,
+ INPUT_ESC_ST,
+};
+
+/* Escape command table. */
+static const struct input_table_entry input_esc_table[] = {
+ { '0', "(", INPUT_ESC_SCSG0_ON },
+ { '0', ")", INPUT_ESC_SCSG1_ON },
+ { '7', "", INPUT_ESC_DECSC },
+ { '8', "", INPUT_ESC_DECRC },
+ { '8', "#", INPUT_ESC_DECALN },
+ { '=', "", INPUT_ESC_DECKPAM },
+ { '>', "", INPUT_ESC_DECKPNM },
+ { 'B', "(", INPUT_ESC_SCSG0_OFF },
+ { 'B', ")", INPUT_ESC_SCSG1_OFF },
+ { 'D', "", INPUT_ESC_IND },
+ { 'E', "", INPUT_ESC_NEL },
+ { 'H', "", INPUT_ESC_HTS },
+ { 'M', "", INPUT_ESC_RI },
+ { '\\', "", INPUT_ESC_ST },
+ { 'c', "", INPUT_ESC_RIS },
+};
+
+/* Control (CSI) commands. */
+enum input_csi_type {
+ INPUT_CSI_CBT,
+ INPUT_CSI_CNL,
+ INPUT_CSI_CPL,
+ INPUT_CSI_CUB,
+ INPUT_CSI_CUD,
+ INPUT_CSI_CUF,
+ INPUT_CSI_CUP,
+ INPUT_CSI_CUU,
+ INPUT_CSI_DA,
+ INPUT_CSI_DA_TWO,
+ INPUT_CSI_DCH,
+ INPUT_CSI_DECSCUSR,
+ INPUT_CSI_DECSTBM,
+ INPUT_CSI_DL,
+ INPUT_CSI_DSR,
+ INPUT_CSI_ECH,
+ INPUT_CSI_ED,
+ INPUT_CSI_EL,
+ INPUT_CSI_HPA,
+ INPUT_CSI_ICH,
+ INPUT_CSI_IL,
+ INPUT_CSI_MODOFF,
+ INPUT_CSI_MODSET,
+ INPUT_CSI_RCP,
+ INPUT_CSI_REP,
+ INPUT_CSI_RM,
+ INPUT_CSI_RM_PRIVATE,
+ INPUT_CSI_SCP,
+ INPUT_CSI_SD,
+ INPUT_CSI_SGR,
+ INPUT_CSI_SM,
+ INPUT_CSI_SM_PRIVATE,
+ INPUT_CSI_SU,
+ INPUT_CSI_TBC,
+ INPUT_CSI_VPA,
+ INPUT_CSI_WINOPS,
+ INPUT_CSI_XDA,
+};
+
+/* Control (CSI) command table. */
+static const struct input_table_entry input_csi_table[] = {
+ { '@', "", INPUT_CSI_ICH },
+ { 'A', "", INPUT_CSI_CUU },
+ { 'B', "", INPUT_CSI_CUD },
+ { 'C', "", INPUT_CSI_CUF },
+ { 'D', "", INPUT_CSI_CUB },
+ { 'E', "", INPUT_CSI_CNL },
+ { 'F', "", INPUT_CSI_CPL },
+ { 'G', "", INPUT_CSI_HPA },
+ { 'H', "", INPUT_CSI_CUP },
+ { 'J', "", INPUT_CSI_ED },
+ { 'K', "", INPUT_CSI_EL },
+ { 'L', "", INPUT_CSI_IL },
+ { 'M', "", INPUT_CSI_DL },
+ { 'P', "", INPUT_CSI_DCH },
+ { 'S', "", INPUT_CSI_SU },
+ { 'T', "", INPUT_CSI_SD },
+ { 'X', "", INPUT_CSI_ECH },
+ { 'Z', "", INPUT_CSI_CBT },
+ { '`', "", INPUT_CSI_HPA },
+ { 'b', "", INPUT_CSI_REP },
+ { 'c', "", INPUT_CSI_DA },
+ { 'c', ">", INPUT_CSI_DA_TWO },
+ { 'd', "", INPUT_CSI_VPA },
+ { 'f', "", INPUT_CSI_CUP },
+ { 'g', "", INPUT_CSI_TBC },
+ { 'h', "", INPUT_CSI_SM },
+ { 'h', "?", INPUT_CSI_SM_PRIVATE },
+ { 'l', "", INPUT_CSI_RM },
+ { 'l', "?", INPUT_CSI_RM_PRIVATE },
+ { 'm', "", INPUT_CSI_SGR },
+ { 'm', ">", INPUT_CSI_MODSET },
+ { 'n', "", INPUT_CSI_DSR },
+ { 'n', ">", INPUT_CSI_MODOFF },
+ { 'q', " ", INPUT_CSI_DECSCUSR },
+ { 'q', ">", INPUT_CSI_XDA },
+ { 'r', "", INPUT_CSI_DECSTBM },
+ { 's', "", INPUT_CSI_SCP },
+ { 't', "", INPUT_CSI_WINOPS },
+ { 'u', "", INPUT_CSI_RCP },
+};
+
+/* Input transition. */
+struct input_transition {
+ int first;
+ int last;
+
+ int (*handler)(struct input_ctx *);
+ const struct input_state *state;
+};
+
+/* Input state. */
+struct input_state {
+ const char *name;
+ void (*enter)(struct input_ctx *);
+ void (*exit)(struct input_ctx *);
+ const struct input_transition *transitions;
+};
+
+/* State transitions available from all states. */
+#define INPUT_STATE_ANYWHERE \
+ { 0x18, 0x18, input_c0_dispatch, &input_state_ground }, \
+ { 0x1a, 0x1a, input_c0_dispatch, &input_state_ground }, \
+ { 0x1b, 0x1b, NULL, &input_state_esc_enter }
+
+/* Forward declarations of state tables. */
+static const struct input_transition input_state_ground_table[];
+static const struct input_transition input_state_esc_enter_table[];
+static const struct input_transition input_state_esc_intermediate_table[];
+static const struct input_transition input_state_csi_enter_table[];
+static const struct input_transition input_state_csi_parameter_table[];
+static const struct input_transition input_state_csi_intermediate_table[];
+static const struct input_transition input_state_csi_ignore_table[];
+static const struct input_transition input_state_dcs_enter_table[];
+static const struct input_transition input_state_dcs_parameter_table[];
+static const struct input_transition input_state_dcs_intermediate_table[];
+static const struct input_transition input_state_dcs_handler_table[];
+static const struct input_transition input_state_dcs_escape_table[];
+static const struct input_transition input_state_dcs_ignore_table[];
+static const struct input_transition input_state_osc_string_table[];
+static const struct input_transition input_state_apc_string_table[];
+static const struct input_transition input_state_rename_string_table[];
+static const struct input_transition input_state_consume_st_table[];
+
+/* ground state definition. */
+static const struct input_state input_state_ground = {
+ "ground",
+ input_ground, NULL,
+ input_state_ground_table
+};
+
+/* esc_enter state definition. */
+static const struct input_state input_state_esc_enter = {
+ "esc_enter",
+ input_clear, NULL,
+ input_state_esc_enter_table
+};
+
+/* esc_intermediate state definition. */
+static const struct input_state input_state_esc_intermediate = {
+ "esc_intermediate",
+ NULL, NULL,
+ input_state_esc_intermediate_table
+};
+
+/* csi_enter state definition. */
+static const struct input_state input_state_csi_enter = {
+ "csi_enter",
+ input_clear, NULL,
+ input_state_csi_enter_table
+};
+
+/* csi_parameter state definition. */
+static const struct input_state input_state_csi_parameter = {
+ "csi_parameter",
+ NULL, NULL,
+ input_state_csi_parameter_table
+};
+
+/* csi_intermediate state definition. */
+static const struct input_state input_state_csi_intermediate = {
+ "csi_intermediate",
+ NULL, NULL,
+ input_state_csi_intermediate_table
+};
+
+/* csi_ignore state definition. */
+static const struct input_state input_state_csi_ignore = {
+ "csi_ignore",
+ NULL, NULL,
+ input_state_csi_ignore_table
+};
+
+/* dcs_enter state definition. */
+static const struct input_state input_state_dcs_enter = {
+ "dcs_enter",
+ input_enter_dcs, NULL,
+ input_state_dcs_enter_table
+};
+
+/* dcs_parameter state definition. */
+static const struct input_state input_state_dcs_parameter = {
+ "dcs_parameter",
+ NULL, NULL,
+ input_state_dcs_parameter_table
+};
+
+/* dcs_intermediate state definition. */
+static const struct input_state input_state_dcs_intermediate = {
+ "dcs_intermediate",
+ NULL, NULL,
+ input_state_dcs_intermediate_table
+};
+
+/* dcs_handler state definition. */
+static const struct input_state input_state_dcs_handler = {
+ "dcs_handler",
+ NULL, NULL,
+ input_state_dcs_handler_table
+};
+
+/* dcs_escape state definition. */
+static const struct input_state input_state_dcs_escape = {
+ "dcs_escape",
+ NULL, NULL,
+ input_state_dcs_escape_table
+};
+
+/* dcs_ignore state definition. */
+static const struct input_state input_state_dcs_ignore = {
+ "dcs_ignore",
+ NULL, NULL,
+ input_state_dcs_ignore_table
+};
+
+/* osc_string state definition. */
+static const struct input_state input_state_osc_string = {
+ "osc_string",
+ input_enter_osc, input_exit_osc,
+ input_state_osc_string_table
+};
+
+/* apc_string state definition. */
+static const struct input_state input_state_apc_string = {
+ "apc_string",
+ input_enter_apc, input_exit_apc,
+ input_state_apc_string_table
+};
+
+/* rename_string state definition. */
+static const struct input_state input_state_rename_string = {
+ "rename_string",
+ input_enter_rename, input_exit_rename,
+ input_state_rename_string_table
+};
+
+/* consume_st state definition. */
+static const struct input_state input_state_consume_st = {
+ "consume_st",
+ input_enter_rename, NULL, /* rename also waits for ST */
+ input_state_consume_st_table
+};
+
+/* ground state table. */
+static const struct input_transition input_state_ground_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x7e, input_print, NULL },
+ { 0x7f, 0x7f, NULL, NULL },
+ { 0x80, 0xff, input_top_bit_set, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* esc_enter state table. */
+static const struct input_transition input_state_esc_enter_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x2f, input_intermediate, &input_state_esc_intermediate },
+ { 0x30, 0x4f, input_esc_dispatch, &input_state_ground },
+ { 0x50, 0x50, NULL, &input_state_dcs_enter },
+ { 0x51, 0x57, input_esc_dispatch, &input_state_ground },
+ { 0x58, 0x58, NULL, &input_state_consume_st },
+ { 0x59, 0x59, input_esc_dispatch, &input_state_ground },
+ { 0x5a, 0x5a, input_esc_dispatch, &input_state_ground },
+ { 0x5b, 0x5b, NULL, &input_state_csi_enter },
+ { 0x5c, 0x5c, input_esc_dispatch, &input_state_ground },
+ { 0x5d, 0x5d, NULL, &input_state_osc_string },
+ { 0x5e, 0x5e, NULL, &input_state_consume_st },
+ { 0x5f, 0x5f, NULL, &input_state_apc_string },
+ { 0x60, 0x6a, input_esc_dispatch, &input_state_ground },
+ { 0x6b, 0x6b, NULL, &input_state_rename_string },
+ { 0x6c, 0x7e, input_esc_dispatch, &input_state_ground },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* esc_intermediate state table. */
+static const struct input_transition input_state_esc_intermediate_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x2f, input_intermediate, NULL },
+ { 0x30, 0x7e, input_esc_dispatch, &input_state_ground },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* csi_enter state table. */
+static const struct input_transition input_state_csi_enter_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x2f, input_intermediate, &input_state_csi_intermediate },
+ { 0x30, 0x39, input_parameter, &input_state_csi_parameter },
+ { 0x3a, 0x3a, input_parameter, &input_state_csi_parameter },
+ { 0x3b, 0x3b, input_parameter, &input_state_csi_parameter },
+ { 0x3c, 0x3f, input_intermediate, &input_state_csi_parameter },
+ { 0x40, 0x7e, input_csi_dispatch, &input_state_ground },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* csi_parameter state table. */
+static const struct input_transition input_state_csi_parameter_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x2f, input_intermediate, &input_state_csi_intermediate },
+ { 0x30, 0x39, input_parameter, NULL },
+ { 0x3a, 0x3a, input_parameter, NULL },
+ { 0x3b, 0x3b, input_parameter, NULL },
+ { 0x3c, 0x3f, NULL, &input_state_csi_ignore },
+ { 0x40, 0x7e, input_csi_dispatch, &input_state_ground },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* csi_intermediate state table. */
+static const struct input_transition input_state_csi_intermediate_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x2f, input_intermediate, NULL },
+ { 0x30, 0x3f, NULL, &input_state_csi_ignore },
+ { 0x40, 0x7e, input_csi_dispatch, &input_state_ground },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* csi_ignore state table. */
+static const struct input_transition input_state_csi_ignore_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, input_c0_dispatch, NULL },
+ { 0x19, 0x19, input_c0_dispatch, NULL },
+ { 0x1c, 0x1f, input_c0_dispatch, NULL },
+ { 0x20, 0x3f, NULL, NULL },
+ { 0x40, 0x7e, NULL, &input_state_ground },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* dcs_enter state table. */
+static const struct input_transition input_state_dcs_enter_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0x2f, input_intermediate, &input_state_dcs_intermediate },
+ { 0x30, 0x39, input_parameter, &input_state_dcs_parameter },
+ { 0x3a, 0x3a, NULL, &input_state_dcs_ignore },
+ { 0x3b, 0x3b, input_parameter, &input_state_dcs_parameter },
+ { 0x3c, 0x3f, input_intermediate, &input_state_dcs_parameter },
+ { 0x40, 0x7e, input_input, &input_state_dcs_handler },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* dcs_parameter state table. */
+static const struct input_transition input_state_dcs_parameter_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0x2f, input_intermediate, &input_state_dcs_intermediate },
+ { 0x30, 0x39, input_parameter, NULL },
+ { 0x3a, 0x3a, NULL, &input_state_dcs_ignore },
+ { 0x3b, 0x3b, input_parameter, NULL },
+ { 0x3c, 0x3f, NULL, &input_state_dcs_ignore },
+ { 0x40, 0x7e, input_input, &input_state_dcs_handler },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* dcs_intermediate state table. */
+static const struct input_transition input_state_dcs_intermediate_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0x2f, input_intermediate, NULL },
+ { 0x30, 0x3f, NULL, &input_state_dcs_ignore },
+ { 0x40, 0x7e, input_input, &input_state_dcs_handler },
+ { 0x7f, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* dcs_handler state table. */
+static const struct input_transition input_state_dcs_handler_table[] = {
+ /* No INPUT_STATE_ANYWHERE */
+
+ { 0x00, 0x1a, input_input, NULL },
+ { 0x1b, 0x1b, NULL, &input_state_dcs_escape },
+ { 0x1c, 0xff, input_input, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* dcs_escape state table. */
+static const struct input_transition input_state_dcs_escape_table[] = {
+ /* No INPUT_STATE_ANYWHERE */
+
+ { 0x00, 0x5b, input_input, &input_state_dcs_handler },
+ { 0x5c, 0x5c, input_dcs_dispatch, &input_state_ground },
+ { 0x5d, 0xff, input_input, &input_state_dcs_handler },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* dcs_ignore state table. */
+static const struct input_transition input_state_dcs_ignore_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* osc_string state table. */
+static const struct input_transition input_state_osc_string_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x06, NULL, NULL },
+ { 0x07, 0x07, input_end_bel, &input_state_ground },
+ { 0x08, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0xff, input_input, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* apc_string state table. */
+static const struct input_transition input_state_apc_string_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0xff, input_input, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* rename_string state table. */
+static const struct input_transition input_state_rename_string_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0xff, input_input, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* consume_st state table. */
+static const struct input_transition input_state_consume_st_table[] = {
+ INPUT_STATE_ANYWHERE,
+
+ { 0x00, 0x17, NULL, NULL },
+ { 0x19, 0x19, NULL, NULL },
+ { 0x1c, 0x1f, NULL, NULL },
+ { 0x20, 0xff, NULL, NULL },
+
+ { -1, -1, NULL, NULL }
+};
+
+/* Input table compare. */
+static int
+input_table_compare(const void *key, const void *value)
+{
+ const struct input_ctx *ictx = key;
+ const struct input_table_entry *entry = value;
+
+ if (ictx->ch != entry->ch)
+ return (ictx->ch - entry->ch);
+ return (strcmp(ictx->interm_buf, entry->interm));
+}
+
+/*
+ * Timer - if this expires then have been waiting for a terminator for too
+ * long, so reset to ground.
+ */
+static void
+input_timer_callback(__unused int fd, __unused short events, void *arg)
+{
+ struct input_ctx *ictx = arg;
+
+ log_debug("%s: %s expired" , __func__, ictx->state->name);
+ input_reset(ictx, 0);
+}
+
+/* Start the timer. */
+static void
+input_start_timer(struct input_ctx *ictx)
+{
+ struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };
+
+ event_del(&ictx->timer);
+ event_add(&ictx->timer, &tv);
+}
+
+/* Reset cell state to default. */
+static void
+input_reset_cell(struct input_ctx *ictx)
+{
+ memcpy(&ictx->cell.cell, &grid_default_cell, sizeof ictx->cell.cell);
+ ictx->cell.set = 0;
+ ictx->cell.g0set = ictx->cell.g1set = 0;
+
+ memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell);
+ ictx->old_cx = 0;
+ ictx->old_cy = 0;
+}
+
+/* Save screen state. */
+static void
+input_save_state(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct screen *s = sctx->s;
+
+ memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell);
+ ictx->old_cx = s->cx;
+ ictx->old_cy = s->cy;
+ ictx->old_mode = s->mode;
+}
+
+/* Restore screen state. */
+static void
+input_restore_state(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+
+ memcpy(&ictx->cell, &ictx->old_cell, sizeof ictx->cell);
+ if (ictx->old_mode & MODE_ORIGIN)
+ screen_write_mode_set(sctx, MODE_ORIGIN);
+ else
+ screen_write_mode_clear(sctx, MODE_ORIGIN);
+ screen_write_cursormove(sctx, ictx->old_cx, ictx->old_cy, 0);
+}
+
+/* Initialise input parser. */
+struct input_ctx *
+input_init(struct window_pane *wp, struct bufferevent *bev,
+ struct colour_palette *palette)
+{
+ struct input_ctx *ictx;
+
+ ictx = xcalloc(1, sizeof *ictx);
+ ictx->wp = wp;
+ ictx->event = bev;
+ ictx->palette = palette;
+
+ ictx->input_space = INPUT_BUF_START;
+ ictx->input_buf = xmalloc(INPUT_BUF_START);
+
+ ictx->since_ground = evbuffer_new();
+ if (ictx->since_ground == NULL)
+ fatalx("out of memory");
+
+ evtimer_set(&ictx->timer, input_timer_callback, ictx);
+
+ input_reset(ictx, 0);
+ return (ictx);
+}
+
+/* Destroy input parser. */
+void
+input_free(struct input_ctx *ictx)
+{
+ u_int i;
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ if (ictx->param_list[i].type == INPUT_STRING)
+ free(ictx->param_list[i].str);
+ }
+
+ event_del(&ictx->timer);
+
+ free(ictx->input_buf);
+ evbuffer_free(ictx->since_ground);
+
+ free(ictx);
+}
+
+/* Reset input state and clear screen. */
+void
+input_reset(struct input_ctx *ictx, int clear)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct window_pane *wp = ictx->wp;
+
+ input_reset_cell(ictx);
+
+ if (clear && wp != NULL) {
+ if (TAILQ_EMPTY(&wp->modes))
+ screen_write_start_pane(sctx, wp, &wp->base);
+ else
+ screen_write_start(sctx, &wp->base);
+ screen_write_reset(sctx);
+ screen_write_stop(sctx);
+ }
+
+ input_clear(ictx);
+
+ ictx->last = -1;
+
+ ictx->state = &input_state_ground;
+ ictx->flags = 0;
+}
+
+/* Return pending data. */
+struct evbuffer *
+input_pending(struct input_ctx *ictx)
+{
+ return (ictx->since_ground);
+}
+
+/* Change input state. */
+static void
+input_set_state(struct input_ctx *ictx, const struct input_transition *itr)
+{
+ if (ictx->state->exit != NULL)
+ ictx->state->exit(ictx);
+ ictx->state = itr->state;
+ if (ictx->state->enter != NULL)
+ ictx->state->enter(ictx);
+}
+
+/* Parse data. */
+static void
+input_parse(struct input_ctx *ictx, u_char *buf, size_t len)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ const struct input_state *state = NULL;
+ const struct input_transition *itr = NULL;
+ size_t off = 0;
+
+ /* Parse the input. */
+ while (off < len) {
+ ictx->ch = buf[off++];
+
+ /* Find the transition. */
+ if (ictx->state != state ||
+ itr == NULL ||
+ ictx->ch < itr->first ||
+ ictx->ch > itr->last) {
+ itr = ictx->state->transitions;
+ while (itr->first != -1 && itr->last != -1) {
+ if (ictx->ch >= itr->first &&
+ ictx->ch <= itr->last)
+ break;
+ itr++;
+ }
+ if (itr->first == -1 || itr->last == -1) {
+ /* No transition? Eh? */
+ fatalx("no transition from state");
+ }
+ }
+ state = ictx->state;
+
+ /*
+ * Any state except print stops the current collection. This is
+ * an optimization to avoid checking if the attributes have
+ * changed for every character. It will stop unnecessarily for
+ * sequences that don't make a terminal change, but they should
+ * be the minority.
+ */
+ if (itr->handler != input_print)
+ screen_write_collect_end(sctx);
+
+ /*
+ * Execute the handler, if any. Don't switch state if it
+ * returns non-zero.
+ */
+ if (itr->handler != NULL && itr->handler(ictx) != 0)
+ continue;
+
+ /* And switch state, if necessary. */
+ if (itr->state != NULL)
+ input_set_state(ictx, itr);
+
+ /* If not in ground state, save input. */
+ if (ictx->state != &input_state_ground)
+ evbuffer_add(ictx->since_ground, &ictx->ch, 1);
+ }
+}
+
+/* Parse input from pane. */
+void
+input_parse_pane(struct window_pane *wp)
+{
+ void *new_data;
+ size_t new_size;
+
+ new_data = window_pane_get_new_data(wp, &wp->offset, &new_size);
+ input_parse_buffer(wp, new_data, new_size);
+ window_pane_update_used_data(wp, &wp->offset, new_size);
+}
+
+/* Parse given input. */
+void
+input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len)
+{
+ struct input_ctx *ictx = wp->ictx;
+ struct screen_write_ctx *sctx = &ictx->ctx;
+
+ if (len == 0)
+ return;
+
+ window_update_activity(wp->window);
+ wp->flags |= PANE_CHANGED;
+
+ /* 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);
+ else
+ screen_write_start(sctx, &wp->base);
+
+ log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id,
+ ictx->state->name, len, (int)len, buf);
+
+ input_parse(ictx, buf, len);
+ screen_write_stop(sctx);
+}
+
+/* Parse given input for screen. */
+void
+input_parse_screen(struct input_ctx *ictx, struct screen *s,
+ screen_write_init_ctx_cb cb, void *arg, u_char *buf, size_t len)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+
+ if (len == 0)
+ return;
+
+ screen_write_start_callback(sctx, s, cb, arg);
+ input_parse(ictx, buf, len);
+ screen_write_stop(sctx);
+}
+
+/* Split the parameter list (if any). */
+static int
+input_split(struct input_ctx *ictx)
+{
+ const char *errstr;
+ char *ptr, *out;
+ struct input_param *ip;
+ u_int i;
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ if (ictx->param_list[i].type == INPUT_STRING)
+ free(ictx->param_list[i].str);
+ }
+ ictx->param_list_len = 0;
+
+ if (ictx->param_len == 0)
+ return (0);
+ ip = &ictx->param_list[0];
+
+ ptr = ictx->param_buf;
+ while ((out = strsep(&ptr, ";")) != NULL) {
+ if (*out == '\0')
+ ip->type = INPUT_MISSING;
+ else {
+ if (strchr(out, ':') != NULL) {
+ ip->type = INPUT_STRING;
+ ip->str = xstrdup(out);
+ } else {
+ ip->type = INPUT_NUMBER;
+ ip->num = strtonum(out, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ return (-1);
+ }
+ }
+ ip = &ictx->param_list[++ictx->param_list_len];
+ if (ictx->param_list_len == nitems(ictx->param_list))
+ return (-1);
+ }
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ ip = &ictx->param_list[i];
+ if (ip->type == INPUT_MISSING)
+ log_debug("parameter %u: missing", i);
+ else if (ip->type == INPUT_STRING)
+ log_debug("parameter %u: string %s", i, ip->str);
+ else if (ip->type == INPUT_NUMBER)
+ log_debug("parameter %u: number %d", i, ip->num);
+ }
+
+ return (0);
+}
+
+/* Get an argument or return default value. */
+static int
+input_get(struct input_ctx *ictx, u_int validx, int minval, int defval)
+{
+ struct input_param *ip;
+ int retval;
+
+ if (validx >= ictx->param_list_len)
+ return (defval);
+ ip = &ictx->param_list[validx];
+ if (ip->type == INPUT_MISSING)
+ return (defval);
+ if (ip->type == INPUT_STRING)
+ return (-1);
+ retval = ip->num;
+ if (retval < minval)
+ return (minval);
+ return (retval);
+}
+
+/* Reply to terminal query. */
+static void
+input_reply(struct input_ctx *ictx, const char *fmt, ...)
+{
+ struct bufferevent *bev = ictx->event;
+ va_list ap;
+ char *reply;
+
+ if (bev == NULL)
+ return;
+
+ va_start(ap, fmt);
+ xvasprintf(&reply, fmt, ap);
+ va_end(ap);
+
+ bufferevent_write(bev, reply, strlen(reply));
+ free(reply);
+}
+
+/* Clear saved state. */
+static void
+input_clear(struct input_ctx *ictx)
+{
+ event_del(&ictx->timer);
+
+ *ictx->interm_buf = '\0';
+ ictx->interm_len = 0;
+
+ *ictx->param_buf = '\0';
+ ictx->param_len = 0;
+
+ *ictx->input_buf = '\0';
+ ictx->input_len = 0;
+
+ ictx->input_end = INPUT_END_ST;
+
+ ictx->flags &= ~INPUT_DISCARD;
+}
+
+/* Reset for ground state. */
+static void
+input_ground(struct input_ctx *ictx)
+{
+ event_del(&ictx->timer);
+ evbuffer_drain(ictx->since_ground, EVBUFFER_LENGTH(ictx->since_ground));
+
+ if (ictx->input_space > INPUT_BUF_START) {
+ ictx->input_space = INPUT_BUF_START;
+ ictx->input_buf = xrealloc(ictx->input_buf, INPUT_BUF_START);
+ }
+}
+
+/* Output this character to the screen. */
+static int
+input_print(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ int set;
+
+ ictx->utf8started = 0; /* can't be valid UTF-8 */
+
+ set = ictx->cell.set == 0 ? ictx->cell.g0set : ictx->cell.g1set;
+ if (set == 1)
+ ictx->cell.cell.attr |= GRID_ATTR_CHARSET;
+ else
+ ictx->cell.cell.attr &= ~GRID_ATTR_CHARSET;
+
+ utf8_set(&ictx->cell.cell.data, ictx->ch);
+ screen_write_collect_add(sctx, &ictx->cell.cell);
+ ictx->last = ictx->ch;
+
+ ictx->cell.cell.attr &= ~GRID_ATTR_CHARSET;
+
+ return (0);
+}
+
+/* Collect intermediate string. */
+static int
+input_intermediate(struct input_ctx *ictx)
+{
+ if (ictx->interm_len == (sizeof ictx->interm_buf) - 1)
+ ictx->flags |= INPUT_DISCARD;
+ else {
+ ictx->interm_buf[ictx->interm_len++] = ictx->ch;
+ ictx->interm_buf[ictx->interm_len] = '\0';
+ }
+
+ return (0);
+}
+
+/* Collect parameter string. */
+static int
+input_parameter(struct input_ctx *ictx)
+{
+ if (ictx->param_len == (sizeof ictx->param_buf) - 1)
+ ictx->flags |= INPUT_DISCARD;
+ else {
+ ictx->param_buf[ictx->param_len++] = ictx->ch;
+ ictx->param_buf[ictx->param_len] = '\0';
+ }
+
+ return (0);
+}
+
+/* Collect input string. */
+static int
+input_input(struct input_ctx *ictx)
+{
+ size_t available;
+
+ available = ictx->input_space;
+ while (ictx->input_len + 1 >= available) {
+ available *= 2;
+ if (available > INPUT_BUF_LIMIT) {
+ ictx->flags |= INPUT_DISCARD;
+ return (0);
+ }
+ ictx->input_buf = xrealloc(ictx->input_buf, available);
+ ictx->input_space = available;
+ }
+ ictx->input_buf[ictx->input_len++] = ictx->ch;
+ ictx->input_buf[ictx->input_len] = '\0';
+
+ return (0);
+}
+
+/* Execute C0 control sequence. */
+static int
+input_c0_dispatch(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct window_pane *wp = ictx->wp;
+ struct screen *s = sctx->s;
+
+ ictx->utf8started = 0; /* can't be valid UTF-8 */
+
+ log_debug("%s: '%c'", __func__, ictx->ch);
+
+ switch (ictx->ch) {
+ case '\000': /* NUL */
+ break;
+ case '\007': /* BEL */
+ if (wp != NULL)
+ alerts_queue(wp->window, WINDOW_BELL);
+ break;
+ case '\010': /* BS */
+ screen_write_backspace(sctx);
+ break;
+ case '\011': /* HT */
+ /* Don't tab beyond the end of the line. */
+ if (s->cx >= screen_size_x(s) - 1)
+ break;
+
+ /* Find the next tab point, or use the last column if none. */
+ do {
+ s->cx++;
+ if (bit_test(s->tabs, s->cx))
+ break;
+ } while (s->cx < screen_size_x(s) - 1);
+ break;
+ case '\012': /* LF */
+ case '\013': /* VT */
+ case '\014': /* FF */
+ screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
+ if (s->mode & MODE_CRLF)
+ screen_write_carriagereturn(sctx);
+ break;
+ case '\015': /* CR */
+ screen_write_carriagereturn(sctx);
+ break;
+ case '\016': /* SO */
+ ictx->cell.set = 1;
+ break;
+ case '\017': /* SI */
+ ictx->cell.set = 0;
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+
+ ictx->last = -1;
+ return (0);
+}
+
+/* Execute escape sequence. */
+static int
+input_esc_dispatch(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct screen *s = sctx->s;
+ struct input_table_entry *entry;
+
+ if (ictx->flags & INPUT_DISCARD)
+ return (0);
+ log_debug("%s: '%c', %s", __func__, ictx->ch, ictx->interm_buf);
+
+ entry = bsearch(ictx, input_esc_table, nitems(input_esc_table),
+ sizeof input_esc_table[0], input_table_compare);
+ if (entry == NULL) {
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ return (0);
+ }
+
+ switch (entry->type) {
+ case INPUT_ESC_RIS:
+ colour_palette_clear(ictx->palette);
+ input_reset_cell(ictx);
+ screen_write_reset(sctx);
+ screen_write_fullredraw(sctx);
+ break;
+ case INPUT_ESC_IND:
+ screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
+ break;
+ case INPUT_ESC_NEL:
+ screen_write_carriagereturn(sctx);
+ screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
+ break;
+ case INPUT_ESC_HTS:
+ if (s->cx < screen_size_x(s))
+ bit_set(s->tabs, s->cx);
+ break;
+ case INPUT_ESC_RI:
+ screen_write_reverseindex(sctx, ictx->cell.cell.bg);
+ break;
+ case INPUT_ESC_DECKPAM:
+ screen_write_mode_set(sctx, MODE_KKEYPAD);
+ break;
+ case INPUT_ESC_DECKPNM:
+ screen_write_mode_clear(sctx, MODE_KKEYPAD);
+ break;
+ case INPUT_ESC_DECSC:
+ input_save_state(ictx);
+ break;
+ case INPUT_ESC_DECRC:
+ input_restore_state(ictx);
+ break;
+ case INPUT_ESC_DECALN:
+ screen_write_alignmenttest(sctx);
+ break;
+ case INPUT_ESC_SCSG0_ON:
+ ictx->cell.g0set = 1;
+ break;
+ case INPUT_ESC_SCSG0_OFF:
+ ictx->cell.g0set = 0;
+ break;
+ case INPUT_ESC_SCSG1_ON:
+ ictx->cell.g1set = 1;
+ break;
+ case INPUT_ESC_SCSG1_OFF:
+ ictx->cell.g1set = 0;
+ break;
+ case INPUT_ESC_ST:
+ /* ST terminates OSC but the state transition already did it. */
+ break;
+ }
+
+ ictx->last = -1;
+ return (0);
+}
+
+/* Execute control sequence. */
+static int
+input_csi_dispatch(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct screen *s = sctx->s;
+ struct input_table_entry *entry;
+ int i, n, m;
+ u_int cx, bg = ictx->cell.cell.bg;
+
+ if (ictx->flags & INPUT_DISCARD)
+ return (0);
+
+ log_debug("%s: '%c' \"%s\" \"%s\"",
+ __func__, ictx->ch, ictx->interm_buf, ictx->param_buf);
+
+ if (input_split(ictx) != 0)
+ return (0);
+
+ entry = bsearch(ictx, input_csi_table, nitems(input_csi_table),
+ sizeof input_csi_table[0], input_table_compare);
+ if (entry == NULL) {
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ return (0);
+ }
+
+ switch (entry->type) {
+ case INPUT_CSI_CBT:
+ /* Find the previous tab point, n times. */
+ cx = s->cx;
+ if (cx > screen_size_x(s) - 1)
+ cx = screen_size_x(s) - 1;
+ n = input_get(ictx, 0, 1, 1);
+ if (n == -1)
+ break;
+ while (cx > 0 && n-- > 0) {
+ do
+ cx--;
+ while (cx > 0 && !bit_test(s->tabs, cx));
+ }
+ s->cx = cx;
+ break;
+ case INPUT_CSI_CUB:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_cursorleft(sctx, n);
+ break;
+ case INPUT_CSI_CUD:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_cursordown(sctx, n);
+ break;
+ case INPUT_CSI_CUF:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_cursorright(sctx, n);
+ break;
+ case INPUT_CSI_CUP:
+ n = input_get(ictx, 0, 1, 1);
+ m = input_get(ictx, 1, 1, 1);
+ if (n != -1 && m != -1)
+ screen_write_cursormove(sctx, m - 1, n - 1, 1);
+ break;
+ case INPUT_CSI_MODSET:
+ n = input_get(ictx, 0, 0, 0);
+ m = input_get(ictx, 1, 0, 0);
+ if (options_get_number(global_options, "extended-keys") == 2)
+ break;
+ if (n == 0 || (n == 4 && m == 0))
+ screen_write_mode_clear(sctx, MODE_KEXTENDED);
+ else if (n == 4 && (m == 1 || m == 2))
+ screen_write_mode_set(sctx, MODE_KEXTENDED);
+ break;
+ case INPUT_CSI_MODOFF:
+ n = input_get(ictx, 0, 0, 0);
+ if (n == 4)
+ screen_write_mode_clear(sctx, MODE_KEXTENDED);
+ break;
+ case INPUT_CSI_WINOPS:
+ input_csi_dispatch_winops(ictx);
+ break;
+ case INPUT_CSI_CUU:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_cursorup(sctx, n);
+ break;
+ case INPUT_CSI_CNL:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1) {
+ screen_write_carriagereturn(sctx);
+ screen_write_cursordown(sctx, n);
+ }
+ break;
+ case INPUT_CSI_CPL:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1) {
+ screen_write_carriagereturn(sctx);
+ screen_write_cursorup(sctx, n);
+ }
+ break;
+ case INPUT_CSI_DA:
+ switch (input_get(ictx, 0, 0, 0)) {
+ case -1:
+ break;
+ case 0:
+ input_reply(ictx, "\033[?1;2c");
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ break;
+ case INPUT_CSI_DA_TWO:
+ switch (input_get(ictx, 0, 0, 0)) {
+ case -1:
+ break;
+ case 0:
+ input_reply(ictx, "\033[>84;0;0c");
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ break;
+ case INPUT_CSI_ECH:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_clearcharacter(sctx, n, bg);
+ break;
+ case INPUT_CSI_DCH:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_deletecharacter(sctx, n, bg);
+ break;
+ case INPUT_CSI_DECSTBM:
+ n = input_get(ictx, 0, 1, 1);
+ m = input_get(ictx, 1, 1, screen_size_y(s));
+ if (n != -1 && m != -1)
+ screen_write_scrollregion(sctx, n - 1, m - 1);
+ break;
+ case INPUT_CSI_DL:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_deleteline(sctx, n, bg);
+ break;
+ case INPUT_CSI_DSR:
+ switch (input_get(ictx, 0, 0, 0)) {
+ case -1:
+ break;
+ case 5:
+ input_reply(ictx, "\033[0n");
+ break;
+ case 6:
+ input_reply(ictx, "\033[%u;%uR", s->cy + 1, s->cx + 1);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ break;
+ case INPUT_CSI_ED:
+ switch (input_get(ictx, 0, 0, 0)) {
+ case -1:
+ break;
+ case 0:
+ screen_write_clearendofscreen(sctx, bg);
+ break;
+ case 1:
+ screen_write_clearstartofscreen(sctx, bg);
+ break;
+ case 2:
+ screen_write_clearscreen(sctx, bg);
+ break;
+ case 3:
+ if (input_get(ictx, 1, 0, 0) == 0) {
+ /*
+ * Linux console extension to clear history
+ * (for example before locking the screen).
+ */
+ screen_write_clearhistory(sctx);
+ }
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ break;
+ case INPUT_CSI_EL:
+ switch (input_get(ictx, 0, 0, 0)) {
+ case -1:
+ break;
+ case 0:
+ screen_write_clearendofline(sctx, bg);
+ break;
+ case 1:
+ screen_write_clearstartofline(sctx, bg);
+ break;
+ case 2:
+ screen_write_clearline(sctx, bg);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ break;
+ case INPUT_CSI_HPA:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_cursormove(sctx, n - 1, -1, 1);
+ break;
+ case INPUT_CSI_ICH:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_insertcharacter(sctx, n, bg);
+ break;
+ case INPUT_CSI_IL:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_insertline(sctx, n, bg);
+ break;
+ case INPUT_CSI_REP:
+ n = input_get(ictx, 0, 1, 1);
+ if (n == -1)
+ break;
+
+ m = screen_size_x(s) - s->cx;
+ if (n > m)
+ n = m;
+
+ if (ictx->last == -1)
+ break;
+ ictx->ch = ictx->last;
+
+ for (i = 0; i < n; i++)
+ input_print(ictx);
+ break;
+ case INPUT_CSI_RCP:
+ input_restore_state(ictx);
+ break;
+ case INPUT_CSI_RM:
+ input_csi_dispatch_rm(ictx);
+ break;
+ case INPUT_CSI_RM_PRIVATE:
+ input_csi_dispatch_rm_private(ictx);
+ break;
+ case INPUT_CSI_SCP:
+ input_save_state(ictx);
+ break;
+ case INPUT_CSI_SGR:
+ input_csi_dispatch_sgr(ictx);
+ break;
+ case INPUT_CSI_SM:
+ input_csi_dispatch_sm(ictx);
+ break;
+ case INPUT_CSI_SM_PRIVATE:
+ input_csi_dispatch_sm_private(ictx);
+ break;
+ case INPUT_CSI_SU:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_scrollup(sctx, n, bg);
+ break;
+ case INPUT_CSI_SD:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_scrolldown(sctx, n, bg);
+ break;
+ case INPUT_CSI_TBC:
+ switch (input_get(ictx, 0, 0, 0)) {
+ case -1:
+ break;
+ case 0:
+ if (s->cx < screen_size_x(s))
+ bit_clear(s->tabs, s->cx);
+ break;
+ case 3:
+ bit_nclear(s->tabs, 0, screen_size_x(s) - 1);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ break;
+ case INPUT_CSI_VPA:
+ n = input_get(ictx, 0, 1, 1);
+ if (n != -1)
+ screen_write_cursormove(sctx, -1, n - 1, 1);
+ break;
+ case INPUT_CSI_DECSCUSR:
+ n = input_get(ictx, 0, 0, 0);
+ if (n != -1)
+ screen_set_cursor_style(n, &s->cstyle, &s->mode);
+ break;
+ case INPUT_CSI_XDA:
+ n = input_get(ictx, 0, 0, 0);
+ if (n == 0)
+ input_reply(ictx, "\033P>|tmux %s\033\\", getversion());
+ break;
+
+ }
+
+ ictx->last = -1;
+ return (0);
+}
+
+/* Handle CSI RM. */
+static void
+input_csi_dispatch_rm(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ u_int i;
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ switch (input_get(ictx, i, 0, -1)) {
+ case -1:
+ break;
+ case 4: /* IRM */
+ screen_write_mode_clear(sctx, MODE_INSERT);
+ break;
+ case 34:
+ screen_write_mode_set(sctx, MODE_CURSOR_VERY_VISIBLE);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ }
+}
+
+/* Handle CSI private RM. */
+static void
+input_csi_dispatch_rm_private(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct grid_cell *gc = &ictx->cell.cell;
+ u_int i;
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ switch (input_get(ictx, i, 0, -1)) {
+ case -1:
+ break;
+ case 1: /* DECCKM */
+ screen_write_mode_clear(sctx, MODE_KCURSOR);
+ break;
+ case 3: /* DECCOLM */
+ screen_write_cursormove(sctx, 0, 0, 1);
+ screen_write_clearscreen(sctx, gc->bg);
+ break;
+ case 6: /* DECOM */
+ screen_write_mode_clear(sctx, MODE_ORIGIN);
+ screen_write_cursormove(sctx, 0, 0, 1);
+ break;
+ case 7: /* DECAWM */
+ screen_write_mode_clear(sctx, MODE_WRAP);
+ break;
+ case 12:
+ screen_write_mode_clear(sctx, MODE_CURSOR_BLINKING);
+ screen_write_mode_set(sctx, MODE_CURSOR_BLINKING_SET);
+ break;
+ case 25: /* TCEM */
+ screen_write_mode_clear(sctx, MODE_CURSOR);
+ break;
+ case 1000:
+ case 1001:
+ case 1002:
+ case 1003:
+ screen_write_mode_clear(sctx, ALL_MOUSE_MODES);
+ break;
+ case 1004:
+ screen_write_mode_clear(sctx, MODE_FOCUSON);
+ break;
+ case 1005:
+ screen_write_mode_clear(sctx, MODE_MOUSE_UTF8);
+ break;
+ case 1006:
+ screen_write_mode_clear(sctx, MODE_MOUSE_SGR);
+ break;
+ case 47:
+ case 1047:
+ screen_write_alternateoff(sctx, gc, 0);
+ break;
+ case 1049:
+ screen_write_alternateoff(sctx, gc, 1);
+ break;
+ case 2004:
+ screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ }
+}
+
+/* Handle CSI SM. */
+static void
+input_csi_dispatch_sm(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ u_int i;
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ switch (input_get(ictx, i, 0, -1)) {
+ case -1:
+ break;
+ case 4: /* IRM */
+ screen_write_mode_set(sctx, MODE_INSERT);
+ break;
+ case 34:
+ screen_write_mode_clear(sctx, MODE_CURSOR_VERY_VISIBLE);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ }
+}
+
+/* Handle CSI private SM. */
+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;
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ switch (input_get(ictx, i, 0, -1)) {
+ case -1:
+ break;
+ case 1: /* DECCKM */
+ screen_write_mode_set(sctx, MODE_KCURSOR);
+ break;
+ case 3: /* DECCOLM */
+ screen_write_cursormove(sctx, 0, 0, 1);
+ screen_write_clearscreen(sctx, ictx->cell.cell.bg);
+ break;
+ case 6: /* DECOM */
+ screen_write_mode_set(sctx, MODE_ORIGIN);
+ screen_write_cursormove(sctx, 0, 0, 1);
+ break;
+ case 7: /* DECAWM */
+ screen_write_mode_set(sctx, MODE_WRAP);
+ break;
+ case 12:
+ screen_write_mode_set(sctx, MODE_CURSOR_BLINKING);
+ screen_write_mode_set(sctx, MODE_CURSOR_BLINKING_SET);
+ break;
+ case 25: /* TCEM */
+ screen_write_mode_set(sctx, MODE_CURSOR);
+ break;
+ case 1000:
+ screen_write_mode_clear(sctx, ALL_MOUSE_MODES);
+ screen_write_mode_set(sctx, MODE_MOUSE_STANDARD);
+ break;
+ case 1002:
+ screen_write_mode_clear(sctx, ALL_MOUSE_MODES);
+ screen_write_mode_set(sctx, MODE_MOUSE_BUTTON);
+ break;
+ case 1003:
+ screen_write_mode_clear(sctx, ALL_MOUSE_MODES);
+ 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);
+ break;
+ case 1006:
+ screen_write_mode_set(sctx, MODE_MOUSE_SGR);
+ break;
+ case 47:
+ case 1047:
+ screen_write_alternateon(sctx, gc, 0);
+ break;
+ case 1049:
+ screen_write_alternateon(sctx, gc, 1);
+ break;
+ case 2004:
+ screen_write_mode_set(sctx, MODE_BRACKETPASTE);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ }
+}
+
+/* Handle CSI window operations. */
+static void
+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;
+ u_int x = screen_size_x(s), y = screen_size_y(s);
+ int n, m;
+
+ m = 0;
+ while ((n = input_get(ictx, m, 0, -1)) != -1) {
+ switch (n) {
+ case 1:
+ case 2:
+ case 5:
+ case 6:
+ case 7:
+ case 11:
+ case 13:
+ case 14:
+ case 19:
+ case 20:
+ case 21:
+ case 24:
+ break;
+ case 3:
+ case 4:
+ case 8:
+ m++;
+ if (input_get(ictx, m, 0, -1) == -1)
+ return;
+ /* FALLTHROUGH */
+ case 9:
+ case 10:
+ m++;
+ if (input_get(ictx, m, 0, -1) == -1)
+ return;
+ break;
+ case 22:
+ m++;
+ switch (input_get(ictx, m, 0, -1)) {
+ case -1:
+ return;
+ case 0:
+ case 2:
+ screen_push_title(sctx->s);
+ break;
+ }
+ break;
+ case 23:
+ m++;
+ switch (input_get(ictx, m, 0, -1)) {
+ case -1:
+ return;
+ case 0:
+ case 2:
+ screen_pop_title(sctx->s);
+ if (wp == NULL)
+ break;
+ notify_pane("pane-title-changed", wp);
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ break;
+ }
+ break;
+ case 18:
+ input_reply(ictx, "\033[8;%u;%ut", x, y);
+ break;
+ default:
+ log_debug("%s: unknown '%c'", __func__, ictx->ch);
+ break;
+ }
+ m++;
+ }
+}
+
+/* Helper for 256 colour SGR. */
+static int
+input_csi_dispatch_sgr_256_do(struct input_ctx *ictx, int fgbg, int c)
+{
+ struct grid_cell *gc = &ictx->cell.cell;
+
+ if (c == -1 || c > 255) {
+ if (fgbg == 38)
+ gc->fg = 8;
+ else if (fgbg == 48)
+ gc->bg = 8;
+ } else {
+ if (fgbg == 38)
+ gc->fg = c | COLOUR_FLAG_256;
+ else if (fgbg == 48)
+ gc->bg = c | COLOUR_FLAG_256;
+ else if (fgbg == 58)
+ gc->us = c | COLOUR_FLAG_256;
+ }
+ return (1);
+}
+
+/* Handle CSI SGR for 256 colours. */
+static void
+input_csi_dispatch_sgr_256(struct input_ctx *ictx, int fgbg, u_int *i)
+{
+ int c;
+
+ c = input_get(ictx, (*i) + 1, 0, -1);
+ if (input_csi_dispatch_sgr_256_do(ictx, fgbg, c))
+ (*i)++;
+}
+
+/* Helper for RGB colour SGR. */
+static int
+input_csi_dispatch_sgr_rgb_do(struct input_ctx *ictx, int fgbg, int r, int g,
+ int b)
+{
+ struct grid_cell *gc = &ictx->cell.cell;
+
+ if (r == -1 || r > 255)
+ return (0);
+ if (g == -1 || g > 255)
+ return (0);
+ if (b == -1 || b > 255)
+ return (0);
+
+ if (fgbg == 38)
+ gc->fg = colour_join_rgb(r, g, b);
+ else if (fgbg == 48)
+ gc->bg = colour_join_rgb(r, g, b);
+ else if (fgbg == 58)
+ gc->us = colour_join_rgb(r, g, b);
+ return (1);
+}
+
+/* Handle CSI SGR for RGB colours. */
+static void
+input_csi_dispatch_sgr_rgb(struct input_ctx *ictx, int fgbg, u_int *i)
+{
+ int r, g, b;
+
+ r = input_get(ictx, (*i) + 1, 0, -1);
+ g = input_get(ictx, (*i) + 2, 0, -1);
+ b = input_get(ictx, (*i) + 3, 0, -1);
+ if (input_csi_dispatch_sgr_rgb_do(ictx, fgbg, r, g, b))
+ (*i) += 3;
+}
+
+/* Handle CSI SGR with a ISO parameter. */
+static void
+input_csi_dispatch_sgr_colon(struct input_ctx *ictx, u_int i)
+{
+ struct grid_cell *gc = &ictx->cell.cell;
+ char *s = ictx->param_list[i].str, *copy, *ptr, *out;
+ int p[8];
+ u_int n;
+ const char *errstr;
+
+ for (n = 0; n < nitems(p); n++)
+ p[n] = -1;
+ n = 0;
+
+ ptr = copy = xstrdup(s);
+ while ((out = strsep(&ptr, ":")) != NULL) {
+ if (*out != '\0') {
+ p[n++] = strtonum(out, 0, INT_MAX, &errstr);
+ if (errstr != NULL || n == nitems(p)) {
+ free(copy);
+ return;
+ }
+ } else {
+ n++;
+ if (n == nitems(p)) {
+ free(copy);
+ return;
+ }
+ }
+ log_debug("%s: %u = %d", __func__, n - 1, p[n - 1]);
+ }
+ free(copy);
+
+ if (n == 0)
+ return;
+ if (p[0] == 4) {
+ if (n != 2)
+ return;
+ switch (p[1]) {
+ case 0:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ break;
+ case 1:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE;
+ break;
+ case 2:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE_2;
+ break;
+ case 3:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE_3;
+ break;
+ case 4:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE_4;
+ break;
+ case 5:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE_5;
+ break;
+ }
+ return;
+ }
+ if (n < 2 || (p[0] != 38 && p[0] != 48 && p[0] != 58))
+ return;
+ switch (p[1]) {
+ case 2:
+ if (n < 3)
+ break;
+ if (n == 5)
+ i = 2;
+ else
+ i = 3;
+ if (n < i + 3)
+ break;
+ input_csi_dispatch_sgr_rgb_do(ictx, p[0], p[i], p[i + 1],
+ p[i + 2]);
+ break;
+ case 5:
+ if (n < 3)
+ break;
+ input_csi_dispatch_sgr_256_do(ictx, p[0], p[2]);
+ break;
+ }
+}
+
+/* Handle CSI SGR. */
+static void
+input_csi_dispatch_sgr(struct input_ctx *ictx)
+{
+ struct grid_cell *gc = &ictx->cell.cell;
+ u_int i;
+ int n;
+
+ if (ictx->param_list_len == 0) {
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ return;
+ }
+
+ for (i = 0; i < ictx->param_list_len; i++) {
+ if (ictx->param_list[i].type == INPUT_STRING) {
+ input_csi_dispatch_sgr_colon(ictx, i);
+ continue;
+ }
+ n = input_get(ictx, i, 0, 0);
+ if (n == -1)
+ continue;
+
+ if (n == 38 || n == 48 || n == 58) {
+ i++;
+ switch (input_get(ictx, i, 0, -1)) {
+ case 2:
+ input_csi_dispatch_sgr_rgb(ictx, n, &i);
+ break;
+ case 5:
+ input_csi_dispatch_sgr_256(ictx, n, &i);
+ break;
+ }
+ continue;
+ }
+
+ switch (n) {
+ case 0:
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ break;
+ case 1:
+ gc->attr |= GRID_ATTR_BRIGHT;
+ break;
+ case 2:
+ gc->attr |= GRID_ATTR_DIM;
+ break;
+ case 3:
+ gc->attr |= GRID_ATTR_ITALICS;
+ break;
+ case 4:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE;
+ break;
+ case 5:
+ case 6:
+ gc->attr |= GRID_ATTR_BLINK;
+ break;
+ case 7:
+ gc->attr |= GRID_ATTR_REVERSE;
+ break;
+ case 8:
+ gc->attr |= GRID_ATTR_HIDDEN;
+ break;
+ case 9:
+ gc->attr |= GRID_ATTR_STRIKETHROUGH;
+ break;
+ case 21:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ gc->attr |= GRID_ATTR_UNDERSCORE_2;
+ break;
+ case 22:
+ gc->attr &= ~(GRID_ATTR_BRIGHT|GRID_ATTR_DIM);
+ break;
+ case 23:
+ gc->attr &= ~GRID_ATTR_ITALICS;
+ break;
+ case 24:
+ gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE;
+ break;
+ case 25:
+ gc->attr &= ~GRID_ATTR_BLINK;
+ break;
+ case 27:
+ gc->attr &= ~GRID_ATTR_REVERSE;
+ break;
+ case 28:
+ gc->attr &= ~GRID_ATTR_HIDDEN;
+ break;
+ case 29:
+ gc->attr &= ~GRID_ATTR_STRIKETHROUGH;
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ gc->fg = n - 30;
+ break;
+ case 39:
+ gc->fg = 8;
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ gc->bg = n - 40;
+ break;
+ case 49:
+ gc->bg = 8;
+ break;
+ case 53:
+ gc->attr |= GRID_ATTR_OVERLINE;
+ break;
+ case 55:
+ gc->attr &= ~GRID_ATTR_OVERLINE;
+ break;
+ case 59:
+ gc->us = 0;
+ break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ gc->fg = n;
+ break;
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ case 107:
+ gc->bg = n - 10;
+ break;
+ }
+ }
+}
+
+/* End of input with BEL. */
+static int
+input_end_bel(struct input_ctx *ictx)
+{
+ log_debug("%s", __func__);
+
+ ictx->input_end = INPUT_END_BEL;
+
+ return (0);
+}
+
+/* DCS string started. */
+static void
+input_enter_dcs(struct input_ctx *ictx)
+{
+ log_debug("%s", __func__);
+
+ input_clear(ictx);
+ input_start_timer(ictx);
+ ictx->last = -1;
+}
+
+/* DCS terminator (ST) received. */
+static int
+input_dcs_dispatch(struct input_ctx *ictx)
+{
+ struct window_pane *wp = ictx->wp;
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ u_char *buf = ictx->input_buf;
+ size_t len = ictx->input_len;
+ const char prefix[] = "tmux;";
+ const u_int prefixlen = (sizeof prefix) - 1;
+
+ if (wp == NULL)
+ return (0);
+ if (ictx->flags & INPUT_DISCARD)
+ return (0);
+ if (!options_get_number(ictx->wp->options, "allow-passthrough"))
+ return (0);
+ log_debug("%s: \"%s\"", __func__, buf);
+
+ if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0)
+ screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen);
+
+ return (0);
+}
+
+/* OSC string started. */
+static void
+input_enter_osc(struct input_ctx *ictx)
+{
+ log_debug("%s", __func__);
+
+ input_clear(ictx);
+ input_start_timer(ictx);
+ ictx->last = -1;
+}
+
+/* OSC terminator (ST) received. */
+static void
+input_exit_osc(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct window_pane *wp = ictx->wp;
+ u_char *p = ictx->input_buf;
+ u_int option;
+
+ if (ictx->flags & INPUT_DISCARD)
+ return;
+ if (ictx->input_len < 1 || *p < '0' || *p > '9')
+ return;
+
+ log_debug("%s: \"%s\" (end %s)", __func__, p,
+ ictx->input_end == INPUT_END_ST ? "ST" : "BEL");
+
+ option = 0;
+ while (*p >= '0' && *p <= '9')
+ option = option * 10 + *p++ - '0';
+ if (*p == ';')
+ p++;
+
+ switch (option) {
+ case 0:
+ case 2:
+ if (screen_set_title(sctx->s, p) && wp != NULL) {
+ notify_pane("pane-title-changed", wp);
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ }
+ break;
+ case 4:
+ input_osc_4(ictx, p);
+ break;
+ case 7:
+ if (utf8_isvalid(p)) {
+ screen_set_path(sctx->s, p);
+ if (wp != NULL) {
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ }
+ }
+ break;
+ case 10:
+ input_osc_10(ictx, p);
+ break;
+ case 11:
+ input_osc_11(ictx, p);
+ break;
+ case 12:
+ input_osc_12(ictx, p);
+ break;
+ case 52:
+ input_osc_52(ictx, p);
+ break;
+ case 104:
+ input_osc_104(ictx, p);
+ break;
+ case 110:
+ input_osc_110(ictx, p);
+ break;
+ case 111:
+ input_osc_111(ictx, p);
+ break;
+ case 112:
+ input_osc_112(ictx, p);
+ break;
+ default:
+ log_debug("%s: unknown '%u'", __func__, option);
+ break;
+ }
+}
+
+/* APC string started. */
+static void
+input_enter_apc(struct input_ctx *ictx)
+{
+ log_debug("%s", __func__);
+
+ input_clear(ictx);
+ input_start_timer(ictx);
+ ictx->last = -1;
+}
+
+/* APC terminator (ST) received. */
+static void
+input_exit_apc(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct window_pane *wp = ictx->wp;
+
+ if (ictx->flags & INPUT_DISCARD)
+ return;
+ log_debug("%s: \"%s\"", __func__, ictx->input_buf);
+
+ if (screen_set_title(sctx->s, ictx->input_buf) && wp != NULL) {
+ notify_pane("pane-title-changed", wp);
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ }
+}
+
+/* Rename string started. */
+static void
+input_enter_rename(struct input_ctx *ictx)
+{
+ log_debug("%s", __func__);
+
+ input_clear(ictx);
+ input_start_timer(ictx);
+ ictx->last = -1;
+}
+
+/* Rename terminator (ST) received. */
+static void
+input_exit_rename(struct input_ctx *ictx)
+{
+ struct window_pane *wp = ictx->wp;
+ struct window *w;
+ struct options_entry *o;
+
+ if (wp == NULL)
+ return;
+ if (ictx->flags & INPUT_DISCARD)
+ return;
+ if (!options_get_number(ictx->wp->options, "allow-rename"))
+ return;
+ log_debug("%s: \"%s\"", __func__, ictx->input_buf);
+
+ if (!utf8_isvalid(ictx->input_buf))
+ return;
+ w = wp->window;
+
+ if (ictx->input_len == 0) {
+ o = options_get_only(w->options, "automatic-rename");
+ if (o != NULL)
+ options_remove_or_default(o, -1, NULL);
+ if (!options_get_number(w->options, "automatic-rename"))
+ window_set_name(w, "");
+ } else {
+ options_set_number(w->options, "automatic-rename", 0);
+ window_set_name(w, ictx->input_buf);
+ }
+ server_redraw_window_borders(w);
+ server_status_window(w);
+}
+
+/* Open UTF-8 character. */
+static int
+input_top_bit_set(struct input_ctx *ictx)
+{
+ struct screen_write_ctx *sctx = &ictx->ctx;
+ struct utf8_data *ud = &ictx->utf8data;
+
+ ictx->last = -1;
+
+ if (!ictx->utf8started) {
+ if (utf8_open(ud, ictx->ch) != UTF8_MORE)
+ return (0);
+ ictx->utf8started = 1;
+ return (0);
+ }
+
+ switch (utf8_append(ud, ictx->ch)) {
+ case UTF8_MORE:
+ return (0);
+ case UTF8_ERROR:
+ ictx->utf8started = 0;
+ return (0);
+ case UTF8_DONE:
+ break;
+ }
+ ictx->utf8started = 0;
+
+ log_debug("%s %hhu '%*s' (width %hhu)", __func__, ud->size,
+ (int)ud->size, ud->data, ud->width);
+
+ utf8_copy(&ictx->cell.cell.data, ud);
+ screen_write_collect_add(sctx, &ictx->cell.cell);
+
+ 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)
+{
+ u_char r, g, b;
+ const char *end;
+
+ if (c != -1)
+ c = colour_force_rgb(c);
+ if (c == -1)
+ return;
+ colour_split_rgb(c, &r, &g, &b);
+
+ if (ictx->input_end == INPUT_END_BEL)
+ end = "\007";
+ else
+ end = "\033\\";
+ input_reply(ictx, "\033]%u;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s",
+ n, r, r, g, g, b, b, end);
+}
+
+/* Handle the OSC 4 sequence for setting (multiple) palette entries. */
+static void
+input_osc_4(struct input_ctx *ictx, const char *p)
+{
+ char *copy, *s, *next = NULL;
+ long idx;
+ int c, bad = 0, redraw = 0;
+
+ copy = s = xstrdup(p);
+ while (s != NULL && *s != '\0') {
+ idx = strtol(s, &next, 10);
+ if (*next++ != ';') {
+ bad = 1;
+ break;
+ }
+ if (idx < 0 || idx >= 256) {
+ bad = 1;
+ break;
+ }
+
+ s = strsep(&next, ";");
+ if (strcmp(s, "?") == 0) {
+ c = colour_palette_get(ictx->palette, idx);
+ if (c != -1)
+ input_osc_colour_reply(ictx, 4, c);
+ continue;
+ }
+ if ((c = input_osc_parse_colour(s)) == -1) {
+ s = next;
+ continue;
+ }
+ if (colour_palette_set(ictx->palette, idx, c))
+ redraw = 1;
+ s = next;
+ }
+ if (bad)
+ log_debug("bad OSC 4: %s", p);
+ if (redraw)
+ screen_write_fullredraw(&ictx->ctx);
+ free(copy);
+}
+
+/* Handle the OSC 10 sequence for setting and querying foreground colour. */
+static void
+input_osc_10(struct input_ctx *ictx, const char *p)
+{
+ struct window_pane *wp = ictx->wp;
+ struct grid_cell defaults;
+ int c;
+
+ if (strcmp(p, "?") == 0) {
+ if (wp != NULL) {
+ tty_default_colours(&defaults, wp);
+ input_osc_colour_reply(ictx, 10, defaults.fg);
+ }
+ return;
+ }
+
+ if ((c = input_osc_parse_colour(p)) == -1) {
+ log_debug("bad OSC 10: %s", p);
+ return;
+ }
+ if (ictx->palette != NULL) {
+ ictx->palette->fg = c;
+ if (wp != NULL)
+ wp->flags |= PANE_STYLECHANGED;
+ screen_write_fullredraw(&ictx->ctx);
+ }
+}
+
+/* Handle the OSC 110 sequence for resetting foreground colour. */
+static void
+input_osc_110(struct input_ctx *ictx, const char *p)
+{
+ struct window_pane *wp = ictx->wp;
+
+ if (*p != '\0')
+ return;
+ if (ictx->palette != NULL) {
+ ictx->palette->fg = 8;
+ if (wp != NULL)
+ wp->flags |= PANE_STYLECHANGED;
+ screen_write_fullredraw(&ictx->ctx);
+ }
+}
+
+/* Handle the OSC 11 sequence for setting and querying background colour. */
+static void
+input_osc_11(struct input_ctx *ictx, const char *p)
+{
+ struct window_pane *wp = ictx->wp;
+ struct grid_cell defaults;
+ int c;
+
+ if (strcmp(p, "?") == 0) {
+ if (wp != NULL) {
+ tty_default_colours(&defaults, wp);
+ input_osc_colour_reply(ictx, 11, defaults.bg);
+ }
+ return;
+ }
+
+ if ((c = input_osc_parse_colour(p)) == -1) {
+ log_debug("bad OSC 11: %s", p);
+ return;
+ }
+ if (ictx->palette != NULL) {
+ ictx->palette->bg = c;
+ if (wp != NULL)
+ wp->flags |= PANE_STYLECHANGED;
+ screen_write_fullredraw(&ictx->ctx);
+ }
+}
+
+/* Handle the OSC 111 sequence for resetting background colour. */
+static void
+input_osc_111(struct input_ctx *ictx, const char *p)
+{
+ struct window_pane *wp = ictx->wp;
+
+ if (*p != '\0')
+ return;
+ if (ictx->palette != NULL) {
+ ictx->palette->bg = 8;
+ if (wp != NULL)
+ wp->flags |= PANE_STYLECHANGED;
+ screen_write_fullredraw(&ictx->ctx);
+ }
+}
+
+/* Handle the OSC 12 sequence for setting and querying cursor colour. */
+static void
+input_osc_12(struct input_ctx *ictx, const char *p)
+{
+ struct window_pane *wp = ictx->wp;
+ int c;
+
+ if (strcmp(p, "?") == 0) {
+ if (wp != NULL) {
+ c = ictx->ctx.s->ccolour;
+ if (c == -1)
+ c = ictx->ctx.s->default_ccolour;
+ input_osc_colour_reply(ictx, 12, c);
+ }
+ return;
+ }
+
+ if ((c = input_osc_parse_colour(p)) == -1) {
+ log_debug("bad OSC 12: %s", p);
+ return;
+ }
+ screen_set_cursor_colour(ictx->ctx.s, c);
+}
+
+/* Handle the OSC 112 sequence for resetting cursor colour. */
+static void
+input_osc_112(struct input_ctx *ictx, const char *p)
+{
+ if (*p == '\0') /* no arguments allowed */
+ screen_set_cursor_colour(ictx->ctx.s, -1);
+}
+
+
+/* Handle the OSC 52 sequence for setting the clipboard. */
+static void
+input_osc_52(struct input_ctx *ictx, const char *p)
+{
+ struct window_pane *wp = ictx->wp;
+ char *end;
+ const char *buf = NULL;
+ size_t len = 0;
+ u_char *out;
+ int outlen, state;
+ struct screen_write_ctx ctx;
+ struct paste_buffer *pb;
+
+ if (wp == NULL)
+ return;
+ state = options_get_number(global_options, "set-clipboard");
+ if (state != 2)
+ return;
+
+ if ((end = strchr(p, ';')) == NULL)
+ return;
+ end++;
+ if (*end == '\0')
+ return;
+ log_debug("%s: %s", __func__, end);
+
+ if (strcmp(end, "?") == 0) {
+ if ((pb = paste_get_top(NULL)) != NULL)
+ buf = paste_buffer_data(pb, &len);
+ if (ictx->input_end == INPUT_END_BEL)
+ input_reply_clipboard(ictx->event, buf, len, "\007");
+ else
+ input_reply_clipboard(ictx->event, buf, len, "\033\\");
+ return;
+ }
+
+ len = (strlen(end) / 4) * 3;
+ if (len == 0)
+ return;
+
+ out = xmalloc(len);
+ if ((outlen = b64_pton(end, out, len)) == -1) {
+ free(out);
+ return;
+ }
+
+ screen_write_start_pane(&ctx, wp, NULL);
+ screen_write_setselection(&ctx, out, outlen);
+ screen_write_stop(&ctx);
+ notify_pane("pane-set-clipboard", wp);
+
+ paste_add(NULL, out, outlen);
+}
+
+/* Handle the OSC 104 sequence for unsetting (multiple) palette entries. */
+static void
+input_osc_104(struct input_ctx *ictx, const char *p)
+{
+ char *copy, *s;
+ long idx;
+ int bad = 0, redraw = 0;
+
+ if (*p == '\0') {
+ colour_palette_clear(ictx->palette);
+ screen_write_fullredraw(&ictx->ctx);
+ return;
+ }
+
+ copy = s = xstrdup(p);
+ while (*s != '\0') {
+ idx = strtol(s, &s, 10);
+ if (*s != '\0' && *s != ';') {
+ bad = 1;
+ break;
+ }
+ if (idx < 0 || idx >= 256) {
+ bad = 1;
+ break;
+ }
+ if (colour_palette_set(ictx->palette, idx, -1))
+ redraw = 1;
+ if (*s == ';')
+ s++;
+ }
+ if (bad)
+ log_debug("bad OSC 104: %s", p);
+ if (redraw)
+ screen_write_fullredraw(&ictx->ctx);
+ free(copy);
+}
+
+void
+input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len,
+ const char *end)
+{
+ char *out = NULL;
+ size_t outlen = 0;
+
+ if (buf != NULL && len != 0) {
+ outlen = 4 * ((len + 2) / 3) + 1;
+ out = xmalloc(outlen);
+ if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) {
+ free(out);
+ return;
+ }
+ }
+
+ bufferevent_write(bev, "\033]52;;", 6);
+ if (outlen != 0)
+ bufferevent_write(bev, out, outlen);
+ bufferevent_write(bev, end, strlen(end));
+ free(out);
+}
diff --git a/job.c b/job.c
new file mode 100644
index 0000000..d2d9adb
--- /dev/null
+++ b/job.c
@@ -0,0 +1,415 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Job scheduling. Run queued commands in the background and record their
+ * output.
+ */
+
+static void job_read_callback(struct bufferevent *, void *);
+static void job_write_callback(struct bufferevent *, void *);
+static void job_error_callback(struct bufferevent *, short, void *);
+
+/* A single job. */
+struct job {
+ enum {
+ JOB_RUNNING,
+ JOB_DEAD,
+ JOB_CLOSED
+ } state;
+
+ int flags;
+
+ char *cmd;
+ pid_t pid;
+ char tty[TTY_NAME_MAX];
+ int status;
+
+ int fd;
+ struct bufferevent *event;
+
+ job_update_cb updatecb;
+ job_complete_cb completecb;
+ job_free_cb freecb;
+ void *data;
+
+ LIST_ENTRY(job) entry;
+};
+
+/* All jobs list. */
+static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs);
+
+/* Start a job running. */
+struct job *
+job_run(const char *cmd, int argc, char **argv, struct environ *e, struct session *s,
+ const char *cwd, job_update_cb updatecb, job_complete_cb completecb,
+ job_free_cb freecb, void *data, int flags, int sx, int sy)
+{
+ struct job *job;
+ struct environ *env;
+ pid_t pid;
+ int nullfd, out[2], master;
+ const char *home;
+ sigset_t set, oldset;
+ struct winsize ws;
+ char **argvp, tty[TTY_NAME_MAX];
+
+ /*
+ * Do not set TERM during .tmux.conf, it is nice to be able to use
+ * if-shell to decide on default-terminal based on outside TERM.
+ */
+ env = environ_for_session(s, !cfg_finished);
+ if (e != NULL)
+ environ_copy(e, env);
+
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oldset);
+
+ if (flags & JOB_PTY) {
+ memset(&ws, 0, sizeof ws);
+ ws.ws_col = sx;
+ ws.ws_row = sy;
+ pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws);
+ } else {
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0)
+ goto fail;
+ pid = fork();
+ }
+ if (cmd == NULL) {
+ cmd_log_argv(argc, argv, "%s:", __func__);
+ log_debug("%s: cwd=%s", __func__, cwd == NULL ? "" : cwd);
+ } else {
+ log_debug("%s: cmd=%s, cwd=%s", __func__, cmd,
+ cwd == NULL ? "" : cwd);
+ }
+
+ switch (pid) {
+ case -1:
+ if (~flags & JOB_PTY) {
+ close(out[0]);
+ close(out[1]);
+ }
+ goto fail;
+ case 0:
+ proc_clear_signals(server_proc, 1);
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+
+ if ((cwd == NULL || chdir(cwd) != 0) &&
+ ((home = find_home()) == NULL || chdir(home) != 0) &&
+ chdir("/") != 0)
+ fatal("chdir failed");
+
+ environ_push(env);
+ environ_free(env);
+
+ if (~flags & JOB_PTY) {
+ if (dup2(out[1], STDIN_FILENO) == -1)
+ fatal("dup2 failed");
+ if (dup2(out[1], STDOUT_FILENO) == -1)
+ fatal("dup2 failed");
+ if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO)
+ close(out[1]);
+ close(out[0]);
+
+ nullfd = open(_PATH_DEVNULL, O_RDWR);
+ if (nullfd == -1)
+ fatal("open failed");
+ if (dup2(nullfd, STDERR_FILENO) == -1)
+ fatal("dup2 failed");
+ if (nullfd != STDERR_FILENO)
+ close(nullfd);
+ }
+ closefrom(STDERR_FILENO + 1);
+
+ if (cmd != NULL) {
+ execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
+ fatal("execl failed");
+ } else {
+ argvp = cmd_copy_argv(argc, argv);
+ execvp(argvp[0], argvp);
+ fatal("execvp failed");
+ }
+ }
+
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ environ_free(env);
+
+ job = xmalloc(sizeof *job);
+ job->state = JOB_RUNNING;
+ job->flags = flags;
+
+ if (cmd != NULL)
+ job->cmd = xstrdup(cmd);
+ else
+ job->cmd = cmd_stringify_argv(argc, argv);
+ job->pid = pid;
+ strlcpy(job->tty, tty, sizeof job->tty);
+ job->status = 0;
+
+ LIST_INSERT_HEAD(&all_jobs, job, entry);
+
+ job->updatecb = updatecb;
+ job->completecb = completecb;
+ job->freecb = freecb;
+ job->data = data;
+
+ if (~flags & JOB_PTY) {
+ close(out[1]);
+ job->fd = out[0];
+ } else
+ job->fd = master;
+ setblocking(job->fd, 0);
+
+ job->event = bufferevent_new(job->fd, job_read_callback,
+ job_write_callback, job_error_callback, job);
+ if (job->event == NULL)
+ fatalx("out of memory");
+ bufferevent_enable(job->event, EV_READ|EV_WRITE);
+
+ log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid);
+ return (job);
+
+fail:
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ environ_free(env);
+ return (NULL);
+}
+
+/* Take job's file descriptor and free the job. */
+int
+job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen)
+{
+ int fd = job->fd;
+
+ log_debug("transfer job %p: %s", job, job->cmd);
+
+ if (pid != NULL)
+ *pid = job->pid;
+ if (tty != NULL)
+ strlcpy(tty, job->tty, ttylen);
+
+ LIST_REMOVE(job, entry);
+ free(job->cmd);
+
+ if (job->freecb != NULL && job->data != NULL)
+ job->freecb(job->data);
+
+ if (job->event != NULL)
+ bufferevent_free(job->event);
+
+ free(job);
+ return (fd);
+}
+
+/* Kill and free an individual job. */
+void
+job_free(struct job *job)
+{
+ log_debug("free job %p: %s", job, job->cmd);
+
+ LIST_REMOVE(job, entry);
+ free(job->cmd);
+
+ if (job->freecb != NULL && job->data != NULL)
+ job->freecb(job->data);
+
+ if (job->pid != -1)
+ kill(job->pid, SIGTERM);
+ if (job->event != NULL)
+ bufferevent_free(job->event);
+ if (job->fd != -1)
+ close(job->fd);
+
+ free(job);
+}
+
+/* Resize job. */
+void
+job_resize(struct job *job, u_int sx, u_int sy)
+{
+ struct winsize ws;
+
+ if (job->fd == -1 || (~job->flags & JOB_PTY))
+ return;
+
+ log_debug("resize job %p: %ux%u", job, sx, sy);
+
+ memset(&ws, 0, sizeof ws);
+ ws.ws_col = sx;
+ ws.ws_row = sy;
+ if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1)
+ fatal("ioctl failed");
+}
+
+/* Job buffer read callback. */
+static void
+job_read_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct job *job = data;
+
+ if (job->updatecb != NULL)
+ job->updatecb(job);
+}
+
+/*
+ * Job buffer write callback. Fired when the buffer falls below watermark
+ * (default is empty). If all the data has been written, disable the write
+ * event.
+ */
+static void
+job_write_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct job *job = data;
+ size_t len = EVBUFFER_LENGTH(EVBUFFER_OUTPUT(job->event));
+
+ log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd,
+ (long) job->pid, len);
+
+ if (len == 0 && (~job->flags & JOB_KEEPWRITE)) {
+ shutdown(job->fd, SHUT_WR);
+ bufferevent_disable(job->event, EV_WRITE);
+ }
+}
+
+/* Job buffer error callback. */
+static void
+job_error_callback(__unused struct bufferevent *bufev, __unused short events,
+ void *data)
+{
+ struct job *job = data;
+
+ log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
+
+ if (job->state == JOB_DEAD) {
+ if (job->completecb != NULL)
+ job->completecb(job);
+ job_free(job);
+ } else {
+ bufferevent_disable(job->event, EV_READ);
+ job->state = JOB_CLOSED;
+ }
+}
+
+/* Job died (waitpid() returned its pid). */
+void
+job_check_died(pid_t pid, int status)
+{
+ struct job *job;
+
+ LIST_FOREACH(job, &all_jobs, entry) {
+ if (pid == job->pid)
+ break;
+ }
+ if (job == NULL)
+ return;
+ if (WIFSTOPPED(status)) {
+ if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
+ return;
+ killpg(job->pid, SIGCONT);
+ return;
+ }
+ log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid);
+
+ job->status = status;
+
+ if (job->state == JOB_CLOSED) {
+ if (job->completecb != NULL)
+ job->completecb(job);
+ job_free(job);
+ } else {
+ job->pid = -1;
+ job->state = JOB_DEAD;
+ }
+}
+
+/* Get job status. */
+int
+job_get_status(struct job *job)
+{
+ return (job->status);
+}
+
+/* Get job data. */
+void *
+job_get_data(struct job *job)
+{
+ return (job->data);
+}
+
+/* Get job event. */
+struct bufferevent *
+job_get_event(struct job *job)
+{
+ return (job->event);
+}
+
+/* Kill all jobs. */
+void
+job_kill_all(void)
+{
+ struct job *job;
+
+ LIST_FOREACH(job, &all_jobs, entry) {
+ if (job->pid != -1)
+ kill(job->pid, SIGTERM);
+ }
+}
+
+/* Are any jobs still running? */
+int
+job_still_running(void)
+{
+ struct job *job;
+
+ LIST_FOREACH(job, &all_jobs, entry) {
+ if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING)
+ return (1);
+ }
+ return (0);
+}
+
+/* Print job summary. */
+void
+job_print_summary(struct cmdq_item *item, int blank)
+{
+ struct job *job;
+ u_int n = 0;
+
+ LIST_FOREACH(job, &all_jobs, entry) {
+ if (blank) {
+ cmdq_print(item, "%s", "");
+ blank = 0;
+ }
+ cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]",
+ n, job->cmd, job->fd, (long)job->pid, job->status);
+ n++;
+ }
+}
diff --git a/key-bindings.c b/key-bindings.c
new file mode 100644
index 0000000..9517196
--- /dev/null
+++ b/key-bindings.c
@@ -0,0 +1,682 @@
+/* $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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+#define DEFAULT_SESSION_MENU \
+ " 'Next' 'n' {switch-client -n}" \
+ " 'Previous' 'p' {switch-client -p}" \
+ " ''" \
+ " 'Renumber' 'N' {move-window -r}" \
+ " 'Rename' 'n' {command-prompt -I \"#S\" {rename-session -- '%%'}}" \
+ " ''" \
+ " 'New Session' 's' {new-session}" \
+ " 'New Window' 'w' {new-window}"
+#define DEFAULT_WINDOW_MENU \
+ " '#{?#{>:#{session_windows},1},,-}Swap Left' 'l' {swap-window -t:-1}" \
+ " '#{?#{>:#{session_windows},1},,-}Swap Right' 'r' {swap-window -t:+1}" \
+ " '#{?pane_marked_set,,-}Swap Marked' 's' {swap-window}" \
+ " ''" \
+ " 'Kill' 'X' {kill-window}" \
+ " 'Respawn' 'R' {respawn-window -k}" \
+ " '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \
+ " 'Rename' 'n' {command-prompt -FI \"#W\" {rename-window -t '#{window_id}' -- '%%'}}" \
+ " ''" \
+ " 'New After' 'w' {new-window -a}" \
+ " 'New At End' 'W' {new-window}"
+#define DEFAULT_PANE_MENU \
+ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \
+ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \
+ " ''" \
+ " '#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}' 'C-r' {if -F '#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}' 'copy-mode -t='; send -Xt= search-backward \"#{q:mouse_word}\"}" \
+ " '#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}' 'C-y' {copy-mode -q; send-keys -l -- \"#{q:mouse_word}\"}" \
+ " '#{?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}\"}" \
+ " ''" \
+ " 'Horizontal Split' 'h' {split-window -h}" \
+ " 'Vertical Split' 'v' {split-window -v}" \
+ " ''" \
+ " '#{?#{>:#{window_panes},1},,-}Swap Up' 'u' {swap-pane -U}" \
+ " '#{?#{>:#{window_panes},1},,-}Swap Down' 'd' {swap-pane -D}" \
+ " '#{?pane_marked_set,,-}Swap Marked' 's' {swap-pane}" \
+ " ''" \
+ " 'Kill' 'X' {kill-pane}" \
+ " 'Respawn' 'R' {respawn-pane -k}" \
+ " '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \
+ " '#{?#{>:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}' 'z' {resize-pane -Z}"
+
+static int key_bindings_cmp(struct key_binding *, struct key_binding *);
+RB_GENERATE_STATIC(key_bindings, key_binding, entry, key_bindings_cmp);
+static int key_table_cmp(struct key_table *, struct key_table *);
+RB_GENERATE_STATIC(key_tables, key_table, entry, key_table_cmp);
+static struct key_tables key_tables = RB_INITIALIZER(&key_tables);
+
+static int
+key_table_cmp(struct key_table *table1, struct key_table *table2)
+{
+ return (strcmp(table1->name, table2->name));
+}
+
+static int
+key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2)
+{
+ if (bd1->key < bd2->key)
+ return (-1);
+ if (bd1->key > bd2->key)
+ return (1);
+ return (0);
+}
+
+static void
+key_bindings_free(struct key_binding *bd)
+{
+ cmd_list_free(bd->cmdlist);
+ free((void *)bd->note);
+ free(bd);
+}
+
+struct key_table *
+key_bindings_get_table(const char *name, int create)
+{
+ struct key_table table_find, *table;
+
+ table_find.name = name;
+ table = RB_FIND(key_tables, &key_tables, &table_find);
+ if (table != NULL || !create)
+ return (table);
+
+ table = xmalloc(sizeof *table);
+ table->name = xstrdup(name);
+ RB_INIT(&table->key_bindings);
+ RB_INIT(&table->default_key_bindings);
+
+ table->references = 1; /* one reference in key_tables */
+ RB_INSERT(key_tables, &key_tables, table);
+
+ return (table);
+}
+
+struct key_table *
+key_bindings_first_table(void)
+{
+ return (RB_MIN(key_tables, &key_tables));
+}
+
+struct key_table *
+key_bindings_next_table(struct key_table *table)
+{
+ return (RB_NEXT(key_tables, &key_tables, table));
+}
+
+void
+key_bindings_unref_table(struct key_table *table)
+{
+ struct key_binding *bd;
+ struct key_binding *bd1;
+
+ if (--table->references != 0)
+ return;
+
+ RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) {
+ RB_REMOVE(key_bindings, &table->key_bindings, bd);
+ key_bindings_free(bd);
+ }
+ RB_FOREACH_SAFE(bd, key_bindings, &table->default_key_bindings, bd1) {
+ RB_REMOVE(key_bindings, &table->default_key_bindings, bd);
+ key_bindings_free(bd);
+ }
+
+ free((void *)table->name);
+ free(table);
+}
+
+struct key_binding *
+key_bindings_get(struct key_table *table, key_code key)
+{
+ struct key_binding bd;
+
+ bd.key = key;
+ return (RB_FIND(key_bindings, &table->key_bindings, &bd));
+}
+
+struct key_binding *
+key_bindings_get_default(struct key_table *table, key_code key)
+{
+ struct key_binding bd;
+
+ bd.key = key;
+ return (RB_FIND(key_bindings, &table->default_key_bindings, &bd));
+}
+
+struct key_binding *
+key_bindings_first(struct key_table *table)
+{
+ return (RB_MIN(key_bindings, &table->key_bindings));
+}
+
+struct key_binding *
+key_bindings_next(__unused struct key_table *table, struct key_binding *bd)
+{
+ return (RB_NEXT(key_bindings, &table->key_bindings, bd));
+}
+
+void
+key_bindings_add(const char *name, key_code key, const char *note, int repeat,
+ struct cmd_list *cmdlist)
+{
+ struct key_table *table;
+ struct key_binding *bd;
+ char *s;
+
+ table = key_bindings_get_table(name, 1);
+
+ bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
+ if (cmdlist == NULL) {
+ if (bd != NULL) {
+ free((void *)bd->note);
+ if (note != NULL)
+ bd->note = xstrdup(note);
+ else
+ bd->note = NULL;
+ }
+ return;
+ }
+ if (bd != NULL) {
+ RB_REMOVE(key_bindings, &table->key_bindings, bd);
+ key_bindings_free(bd);
+ }
+
+ bd = xcalloc(1, sizeof *bd);
+ bd->key = (key & ~KEYC_MASK_FLAGS);
+ if (note != NULL)
+ bd->note = xstrdup(note);
+ RB_INSERT(key_bindings, &table->key_bindings, bd);
+
+ if (repeat)
+ bd->flags |= KEY_BINDING_REPEAT;
+ bd->cmdlist = cmdlist;
+
+ s = cmd_list_print(bd->cmdlist, 0);
+ log_debug("%s: %#llx %s = %s", __func__, bd->key,
+ key_string_lookup_key(bd->key, 1), s);
+ free(s);
+}
+
+void
+key_bindings_remove(const char *name, key_code key)
+{
+ struct key_table *table;
+ struct key_binding *bd;
+
+ table = key_bindings_get_table(name, 0);
+ if (table == NULL)
+ return;
+
+ bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
+ if (bd == NULL)
+ return;
+
+ log_debug("%s: %#llx %s", __func__, bd->key,
+ key_string_lookup_key(bd->key, 1));
+
+ RB_REMOVE(key_bindings, &table->key_bindings, bd);
+ key_bindings_free(bd);
+
+ if (RB_EMPTY(&table->key_bindings) &&
+ RB_EMPTY(&table->default_key_bindings)) {
+ RB_REMOVE(key_tables, &key_tables, table);
+ key_bindings_unref_table(table);
+ }
+}
+
+void
+key_bindings_reset(const char *name, key_code key)
+{
+ struct key_table *table;
+ struct key_binding *bd, *dd;
+
+ table = key_bindings_get_table(name, 0);
+ if (table == NULL)
+ return;
+
+ bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
+ if (bd == NULL)
+ return;
+
+ dd = key_bindings_get_default(table, bd->key);
+ if (dd == NULL) {
+ key_bindings_remove(name, bd->key);
+ return;
+ }
+
+ cmd_list_free(bd->cmdlist);
+ bd->cmdlist = dd->cmdlist;
+ bd->cmdlist->references++;
+
+ free((void *)bd->note);
+ if (dd->note != NULL)
+ bd->note = xstrdup(dd->note);
+ else
+ bd->note = NULL;
+ bd->flags = dd->flags;
+}
+
+void
+key_bindings_remove_table(const char *name)
+{
+ struct key_table *table;
+ struct client *c;
+
+ table = key_bindings_get_table(name, 0);
+ if (table != NULL) {
+ RB_REMOVE(key_tables, &key_tables, table);
+ key_bindings_unref_table(table);
+ }
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->keytable == table)
+ server_client_set_key_table(c, NULL);
+ }
+}
+
+void
+key_bindings_reset_table(const char *name)
+{
+ struct key_table *table;
+ struct key_binding *bd, *bd1;
+
+ table = key_bindings_get_table(name, 0);
+ if (table == NULL)
+ return;
+ if (RB_EMPTY(&table->default_key_bindings)) {
+ key_bindings_remove_table(name);
+ return;
+ }
+ RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1)
+ key_bindings_reset(name, bd->key);
+}
+
+static enum cmd_retval
+key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data)
+{
+ struct key_table *table;
+ struct key_binding *bd, *new_bd;
+
+ RB_FOREACH(table, key_tables, &key_tables) {
+ RB_FOREACH(bd, key_bindings, &table->key_bindings) {
+ new_bd = xcalloc(1, sizeof *bd);
+ new_bd->key = bd->key;
+ if (bd->note != NULL)
+ new_bd->note = xstrdup(bd->note);
+ new_bd->flags = bd->flags;
+ new_bd->cmdlist = bd->cmdlist;
+ new_bd->cmdlist->references++;
+ RB_INSERT(key_bindings, &table->default_key_bindings,
+ new_bd);
+ }
+ }
+ return (CMD_RETURN_NORMAL);
+}
+
+void
+key_bindings_init(void)
+{
+ static const char *defaults[] = {
+ /* Prefix keys. */
+ "bind -N 'Send the prefix key' C-b { send-prefix }",
+ "bind -N 'Rotate through the panes' C-o { rotate-window }",
+ "bind -N 'Suspend the current client' C-z { suspend-client }",
+ "bind -N 'Select next layout' Space { next-layout }",
+ "bind -N 'Break pane to a new window' ! { break-pane }",
+ "bind -N 'Split window vertically' '\"' { split-window }",
+ "bind -N 'List all paste buffers' '#' { list-buffers }",
+ "bind -N 'Rename current session' '$' { command-prompt -I'#S' { rename-session -- '%%' } }",
+ "bind -N 'Split window horizontally' % { split-window -h }",
+ "bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }",
+ "bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }",
+ "bind -N 'Switch to previous client' ( { switch-client -p }",
+ "bind -N 'Switch to next client' ) { switch-client -n }",
+ "bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }",
+ "bind -N 'Delete the most recent paste buffer' - { delete-buffer }",
+ "bind -N 'Move the current window' . { command-prompt -T target { move-window -t '%%' } }",
+ "bind -N 'Describe key binding' '/' { command-prompt -kpkey { list-keys -1N '%%' } }",
+ "bind -N 'Select window 0' 0 { select-window -t:=0 }",
+ "bind -N 'Select window 1' 1 { select-window -t:=1 }",
+ "bind -N 'Select window 2' 2 { select-window -t:=2 }",
+ "bind -N 'Select window 3' 3 { select-window -t:=3 }",
+ "bind -N 'Select window 4' 4 { select-window -t:=4 }",
+ "bind -N 'Select window 5' 5 { select-window -t:=5 }",
+ "bind -N 'Select window 6' 6 { select-window -t:=6 }",
+ "bind -N 'Select window 7' 7 { select-window -t:=7 }",
+ "bind -N 'Select window 8' 8 { select-window -t:=8 }",
+ "bind -N 'Select window 9' 9 { select-window -t:=9 }",
+ "bind -N 'Prompt for a command' : { command-prompt }",
+ "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 '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 }",
+ "bind -N 'Enter copy mode' [ { copy-mode }",
+ "bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }",
+ "bind -N 'Create a new window' c { new-window }",
+ "bind -N 'Detach the current client' d { detach-client }",
+ "bind -N 'Search for a pane' f { command-prompt { find-window -Z -- '%%' } }",
+ "bind -N 'Display window information' i { display-message }",
+ "bind -N 'Select the previously current window' l { last-window }",
+ "bind -N 'Toggle the marked pane' m { select-pane -m }",
+ "bind -N 'Select the next window' n { next-window }",
+ "bind -N 'Select the next pane' o { select-pane -t:.+ }",
+ "bind -N 'Customize options' C { customize-mode -Z }",
+ "bind -N 'Select the previous window' p { previous-window }",
+ "bind -N 'Display pane numbers' q { display-panes }",
+ "bind -N 'Redraw the current client' r { refresh-client }",
+ "bind -N 'Choose a session from a list' s { choose-tree -Zs }",
+ "bind -N 'Show a clock' t { clock-mode }",
+ "bind -N 'Choose a window from a list' w { choose-tree -Zw }",
+ "bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }",
+ "bind -N 'Zoom the active pane' z { resize-pane -Z }",
+ "bind -N 'Swap the active pane with the pane above' '{' { swap-pane -U }",
+ "bind -N 'Swap the active pane with the pane below' '}' { swap-pane -D }",
+ "bind -N 'Show messages' '~' { show-messages }",
+ "bind -N 'Enter copy mode and scroll up' PPage { copy-mode -u }",
+ "bind -N 'Select the pane above the active pane' -r Up { select-pane -U }",
+ "bind -N 'Select the pane below the active pane' -r Down { select-pane -D }",
+ "bind -N 'Select the pane to the left of the active pane' -r Left { select-pane -L }",
+ "bind -N 'Select the pane to the right of the active pane' -r Right { select-pane -R }",
+ "bind -N 'Set the even-horizontal layout' M-1 { select-layout even-horizontal }",
+ "bind -N 'Set the even-vertical layout' M-2 { select-layout even-vertical }",
+ "bind -N 'Set the main-horizontal layout' M-3 { select-layout main-horizontal }",
+ "bind -N 'Set the main-vertical layout' M-4 { select-layout main-vertical }",
+ "bind -N 'Select the tiled layout' M-5 { select-layout tiled }",
+ "bind -N 'Select the next window with an alert' M-n { next-window -a }",
+ "bind -N 'Rotate through the panes in reverse' M-o { rotate-window -D }",
+ "bind -N 'Select the previous window with an alert' M-p { previous-window -a }",
+ "bind -N 'Move the visible part of the window up' -r S-Up { refresh-client -U 10 }",
+ "bind -N 'Move the visible part of the window down' -r S-Down { refresh-client -D 10 }",
+ "bind -N 'Move the visible part of the window left' -r S-Left { refresh-client -L 10 }",
+ "bind -N 'Move the visible part of the window right' -r S-Right { refresh-client -R 10 }",
+ "bind -N 'Reset so the visible part of the window follows the cursor' -r DC { refresh-client -c }",
+ "bind -N 'Resize the pane up by 5' -r M-Up { resize-pane -U 5 }",
+ "bind -N 'Resize the pane down by 5' -r M-Down { resize-pane -D 5 }",
+ "bind -N 'Resize the pane left by 5' -r M-Left { resize-pane -L 5 }",
+ "bind -N 'Resize the pane right by 5' -r M-Right { resize-pane -R 5 }",
+ "bind -N 'Resize the pane up' -r C-Up { resize-pane -U }",
+ "bind -N 'Resize the pane down' -r C-Down { resize-pane -D }",
+ "bind -N 'Resize the pane left' -r C-Left { resize-pane -L }",
+ "bind -N 'Resize the pane right' -r C-Right { resize-pane -R }",
+
+ /* Menu keys */
+ "bind < { display-menu -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU " }",
+ "bind > { display-menu -xP -yP -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }",
+
+ /* Mouse button 1 down on pane. */
+ "bind -n MouseDown1Pane { select-pane -t=; send -M }",
+
+ /* Mouse button 1 drag on pane. */
+ "bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }",
+
+ /* Mouse wheel up on pane. */
+ "bind -n WheelUpPane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -e } }",
+
+ /* Mouse button 2 down on pane. */
+ "bind -n MouseDown2Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { paste -p } }",
+
+ /* Mouse button 1 double click on pane. */
+ "bind -n DoubleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-word; run -d0.3; send -X copy-pipe-and-cancel } }",
+
+ /* Mouse button 1 triple click on pane. */
+ "bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }",
+
+ /* Mouse button 1 drag on border. */
+ "bind -n MouseDrag1Border { resize-pane -M }",
+
+ /* Mouse button 1 down on status line. */
+ "bind -n MouseDown1Status { select-window -t= }",
+
+ /* Mouse wheel down on status line. */
+ "bind -n WheelDownStatus { next-window }",
+
+ /* Mouse wheel up on status line. */
+ "bind -n WheelUpStatus { previous-window }",
+
+ /* Mouse button 3 down on status left. */
+ "bind -n 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 "}",
+
+ /* 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 " } }",
+ "bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }",
+
+ /* Copy mode (emacs) keys. */
+ "bind -Tcopy-mode C-Space { send -X begin-selection }",
+ "bind -Tcopy-mode C-a { send -X start-of-line }",
+ "bind -Tcopy-mode C-c { send -X cancel }",
+ "bind -Tcopy-mode C-e { send -X end-of-line }",
+ "bind -Tcopy-mode C-f { send -X cursor-right }",
+ "bind -Tcopy-mode C-b { send -X cursor-left }",
+ "bind -Tcopy-mode C-g { send -X clear-selection }",
+ "bind -Tcopy-mode C-k { send -X copy-pipe-end-of-line-and-cancel }",
+ "bind -Tcopy-mode C-n { send -X cursor-down }",
+ "bind -Tcopy-mode C-p { send -X cursor-up }",
+ "bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental '%%' } }",
+ "bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental '%%' } }",
+ "bind -Tcopy-mode C-v { send -X page-down }",
+ "bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode Escape { send -X cancel }",
+ "bind -Tcopy-mode Space { send -X page-down }",
+ "bind -Tcopy-mode , { send -X jump-reverse }",
+ "bind -Tcopy-mode \\; { send -X jump-again }",
+ "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }",
+ "bind -Tcopy-mode N { send -X search-reverse }",
+ "bind -Tcopy-mode P { send -X toggle-position }",
+ "bind -Tcopy-mode R { send -X rectangle-toggle }",
+ "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }",
+ "bind -Tcopy-mode X { send -X set-mark }",
+ "bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward '%%' } }",
+ "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line '%%' } }",
+ "bind -Tcopy-mode n { send -X search-again }",
+ "bind -Tcopy-mode q { send -X cancel }",
+ "bind -Tcopy-mode r { send -X refresh-from-pane }",
+ "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward '%%' } }",
+ "bind -Tcopy-mode Home { send -X start-of-line }",
+ "bind -Tcopy-mode End { send -X end-of-line }",
+ "bind -Tcopy-mode MouseDown1Pane select-pane",
+ "bind -Tcopy-mode MouseDrag1Pane { select-pane; send -X begin-selection }",
+ "bind -Tcopy-mode MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode WheelUpPane { select-pane; send -N5 -X scroll-up }",
+ "bind -Tcopy-mode WheelDownPane { select-pane; send -N5 -X scroll-down }",
+ "bind -Tcopy-mode DoubleClick1Pane { select-pane; send -X select-word; run -d0.3; send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode TripleClick1Pane { select-pane; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode NPage { send -X page-down }",
+ "bind -Tcopy-mode PPage { send -X page-up }",
+ "bind -Tcopy-mode Up { send -X cursor-up }",
+ "bind -Tcopy-mode Down { send -X cursor-down }",
+ "bind -Tcopy-mode Left { send -X cursor-left }",
+ "bind -Tcopy-mode Right { send -X cursor-right }",
+ "bind -Tcopy-mode M-1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }",
+ "bind -Tcopy-mode M-2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }",
+ "bind -Tcopy-mode M-3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }",
+ "bind -Tcopy-mode M-4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }",
+ "bind -Tcopy-mode M-5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }",
+ "bind -Tcopy-mode M-6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }",
+ "bind -Tcopy-mode M-7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }",
+ "bind -Tcopy-mode M-8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }",
+ "bind -Tcopy-mode M-9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }",
+ "bind -Tcopy-mode M-< { send -X history-top }",
+ "bind -Tcopy-mode M-> { send -X history-bottom }",
+ "bind -Tcopy-mode M-R { send -X top-line }",
+ "bind -Tcopy-mode M-b { send -X previous-word }",
+ "bind -Tcopy-mode C-M-b { send -X previous-matching-bracket }",
+ "bind -Tcopy-mode M-f { send -X next-word-end }",
+ "bind -Tcopy-mode C-M-f { send -X next-matching-bracket }",
+ "bind -Tcopy-mode M-m { send -X back-to-indentation }",
+ "bind -Tcopy-mode M-r { send -X middle-line }",
+ "bind -Tcopy-mode M-v { send -X page-up }",
+ "bind -Tcopy-mode M-w { send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode M-x { send -X jump-to-mark }",
+ "bind -Tcopy-mode 'M-{' { send -X previous-paragraph }",
+ "bind -Tcopy-mode 'M-}' { send -X next-paragraph }",
+ "bind -Tcopy-mode M-Up { send -X halfpage-up }",
+ "bind -Tcopy-mode M-Down { send -X halfpage-down }",
+ "bind -Tcopy-mode C-Up { send -X scroll-up }",
+ "bind -Tcopy-mode C-Down { send -X scroll-down }",
+
+ /* Copy mode (vi) keys. */
+ "bind -Tcopy-mode-vi '#' { send -FX search-backward '#{copy_cursor_word}' }",
+ "bind -Tcopy-mode-vi * { send -FX search-forward '#{copy_cursor_word}' }",
+ "bind -Tcopy-mode-vi C-c { send -X cancel }",
+ "bind -Tcopy-mode-vi C-d { send -X halfpage-down }",
+ "bind -Tcopy-mode-vi C-e { send -X scroll-down }",
+ "bind -Tcopy-mode-vi C-b { send -X page-up }",
+ "bind -Tcopy-mode-vi C-f { send -X page-down }",
+ "bind -Tcopy-mode-vi C-h { send -X cursor-left }",
+ "bind -Tcopy-mode-vi C-j { send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode-vi Enter { send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode-vi C-u { send -X halfpage-up }",
+ "bind -Tcopy-mode-vi C-v { send -X rectangle-toggle }",
+ "bind -Tcopy-mode-vi C-y { send -X scroll-up }",
+ "bind -Tcopy-mode-vi Escape { send -X clear-selection }",
+ "bind -Tcopy-mode-vi Space { send -X begin-selection }",
+ "bind -Tcopy-mode-vi '$' { send -X end-of-line }",
+ "bind -Tcopy-mode-vi , { send -X jump-reverse }",
+ "bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward '%%' } }",
+ "bind -Tcopy-mode-vi 0 { send -X start-of-line }",
+ "bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }",
+ "bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line '%%' } }",
+ "bind -Tcopy-mode-vi \\; { send -X jump-again }",
+ "bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward '%%' } }",
+ "bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }",
+ "bind -Tcopy-mode-vi B { send -X previous-space }",
+ "bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }",
+ "bind -Tcopy-mode-vi E { send -X next-space-end }",
+ "bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }",
+ "bind -Tcopy-mode-vi G { send -X history-bottom }",
+ "bind -Tcopy-mode-vi H { send -X top-line }",
+ "bind -Tcopy-mode-vi J { send -X scroll-down }",
+ "bind -Tcopy-mode-vi K { send -X scroll-up }",
+ "bind -Tcopy-mode-vi L { send -X bottom-line }",
+ "bind -Tcopy-mode-vi M { send -X middle-line }",
+ "bind -Tcopy-mode-vi N { send -X search-reverse }",
+ "bind -Tcopy-mode-vi P { send -X toggle-position }",
+ "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }",
+ "bind -Tcopy-mode-vi V { send -X select-line }",
+ "bind -Tcopy-mode-vi W { send -X next-space }",
+ "bind -Tcopy-mode-vi X { send -X set-mark }",
+ "bind -Tcopy-mode-vi ^ { send -X back-to-indentation }",
+ "bind -Tcopy-mode-vi b { send -X previous-word }",
+ "bind -Tcopy-mode-vi e { send -X next-word-end }",
+ "bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward '%%' } }",
+ "bind -Tcopy-mode-vi g { send -X history-top }",
+ "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 l { send -X cursor-right }",
+ "bind -Tcopy-mode-vi n { send -X search-again }",
+ "bind -Tcopy-mode-vi o { send -X other-end }",
+ "bind -Tcopy-mode-vi q { send -X cancel }",
+ "bind -Tcopy-mode-vi r { send -X refresh-from-pane }",
+ "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward '%%' } }",
+ "bind -Tcopy-mode-vi v { send -X rectangle-toggle }",
+ "bind -Tcopy-mode-vi w { send -X next-word }",
+ "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 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 }",
+ "bind -Tcopy-mode-vi WheelUpPane { select-pane; send -N5 -X scroll-up }",
+ "bind -Tcopy-mode-vi WheelDownPane { select-pane; send -N5 -X scroll-down }",
+ "bind -Tcopy-mode-vi DoubleClick1Pane { select-pane; send -X select-word; run -d0.3; send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode-vi TripleClick1Pane { select-pane; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel }",
+ "bind -Tcopy-mode-vi BSpace { send -X cursor-left }",
+ "bind -Tcopy-mode-vi NPage { send -X page-down }",
+ "bind -Tcopy-mode-vi PPage { send -X page-up }",
+ "bind -Tcopy-mode-vi Up { send -X cursor-up }",
+ "bind -Tcopy-mode-vi Down { send -X cursor-down }",
+ "bind -Tcopy-mode-vi Left { send -X cursor-left }",
+ "bind -Tcopy-mode-vi Right { send -X cursor-right }",
+ "bind -Tcopy-mode-vi M-x { send -X jump-to-mark }",
+ "bind -Tcopy-mode-vi C-Up { send -X scroll-up }",
+ "bind -Tcopy-mode-vi C-Down { send -X scroll-down }",
+ };
+ u_int i;
+ struct cmd_parse_result *pr;
+
+ for (i = 0; i < nitems(defaults); i++) {
+ pr = cmd_parse_from_string(defaults[i], NULL);
+ if (pr->status != CMD_PARSE_SUCCESS) {
+ log_debug("%s", pr->error);
+ fatalx("bad default key: %s", defaults[i]);
+ }
+ cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL));
+ cmd_list_free(pr->cmdlist);
+ }
+ cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL));
+}
+
+static enum cmd_retval
+key_bindings_read_only(struct cmdq_item *item, __unused void *data)
+{
+ cmdq_error(item, "client is read-only");
+ return (CMD_RETURN_ERROR);
+}
+
+struct cmdq_item *
+key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item,
+ struct client *c, struct key_event *event, struct cmd_find_state *fs)
+{
+ struct cmdq_item *new_item;
+ struct cmdq_state *new_state;
+ int readonly, flags = 0;
+
+ if (c == NULL || (~c->flags & CLIENT_READONLY))
+ readonly = 1;
+ else
+ readonly = cmd_list_all_have(bd->cmdlist, CMD_READONLY);
+ if (!readonly)
+ new_item = cmdq_get_callback(key_bindings_read_only, NULL);
+ else {
+ if (bd->flags & KEY_BINDING_REPEAT)
+ flags |= CMDQ_STATE_REPEAT;
+ new_state = cmdq_new_state(fs, event, flags);
+ new_item = cmdq_get_command(bd->cmdlist, new_state);
+ cmdq_free_state(new_state);
+ }
+ if (item != NULL)
+ new_item = cmdq_insert_after(item, new_item);
+ else
+ new_item = cmdq_append(c, new_item);
+ return (new_item);
+}
diff --git a/key-string.c b/key-string.c
new file mode 100644
index 0000000..0ca9130
--- /dev/null
+++ b/key-string.c
@@ -0,0 +1,466 @@
+/* $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 <wchar.h>
+
+#include "tmux.h"
+
+static key_code key_string_search_table(const char *);
+static key_code key_string_get_modifiers(const char **);
+
+static const struct {
+ const char *string;
+ key_code key;
+} key_string_table[] = {
+ /* Function keys. */
+ { "F1", KEYC_F1|KEYC_IMPLIED_META },
+ { "F2", KEYC_F2|KEYC_IMPLIED_META },
+ { "F3", KEYC_F3|KEYC_IMPLIED_META },
+ { "F4", KEYC_F4|KEYC_IMPLIED_META },
+ { "F5", KEYC_F5|KEYC_IMPLIED_META },
+ { "F6", KEYC_F6|KEYC_IMPLIED_META },
+ { "F7", KEYC_F7|KEYC_IMPLIED_META },
+ { "F8", KEYC_F8|KEYC_IMPLIED_META },
+ { "F9", KEYC_F9|KEYC_IMPLIED_META },
+ { "F10", KEYC_F10|KEYC_IMPLIED_META },
+ { "F11", KEYC_F11|KEYC_IMPLIED_META },
+ { "F12", KEYC_F12|KEYC_IMPLIED_META },
+ { "IC", KEYC_IC|KEYC_IMPLIED_META },
+ { "Insert", KEYC_IC|KEYC_IMPLIED_META },
+ { "DC", KEYC_DC|KEYC_IMPLIED_META },
+ { "Delete", KEYC_DC|KEYC_IMPLIED_META },
+ { "Home", KEYC_HOME|KEYC_IMPLIED_META },
+ { "End", KEYC_END|KEYC_IMPLIED_META },
+ { "NPage", KEYC_NPAGE|KEYC_IMPLIED_META },
+ { "PageDown", KEYC_NPAGE|KEYC_IMPLIED_META },
+ { "PgDn", KEYC_NPAGE|KEYC_IMPLIED_META },
+ { "PPage", KEYC_PPAGE|KEYC_IMPLIED_META },
+ { "PageUp", KEYC_PPAGE|KEYC_IMPLIED_META },
+ { "PgUp", KEYC_PPAGE|KEYC_IMPLIED_META },
+ { "Tab", '\011' },
+ { "BTab", KEYC_BTAB },
+ { "Space", ' ' },
+ { "BSpace", KEYC_BSPACE },
+ { "Enter", '\r' },
+ { "Escape", '\033' },
+
+ /* Arrow keys. */
+ { "Up", KEYC_UP|KEYC_CURSOR|KEYC_IMPLIED_META },
+ { "Down", KEYC_DOWN|KEYC_CURSOR|KEYC_IMPLIED_META },
+ { "Left", KEYC_LEFT|KEYC_CURSOR|KEYC_IMPLIED_META },
+ { "Right", KEYC_RIGHT|KEYC_CURSOR|KEYC_IMPLIED_META },
+
+ /* Numeric keypad. */
+ { "KP/", KEYC_KP_SLASH|KEYC_KEYPAD },
+ { "KP*", KEYC_KP_STAR|KEYC_KEYPAD },
+ { "KP-", KEYC_KP_MINUS|KEYC_KEYPAD },
+ { "KP7", KEYC_KP_SEVEN|KEYC_KEYPAD },
+ { "KP8", KEYC_KP_EIGHT|KEYC_KEYPAD },
+ { "KP9", KEYC_KP_NINE|KEYC_KEYPAD },
+ { "KP+", KEYC_KP_PLUS|KEYC_KEYPAD },
+ { "KP4", KEYC_KP_FOUR|KEYC_KEYPAD },
+ { "KP5", KEYC_KP_FIVE|KEYC_KEYPAD },
+ { "KP6", KEYC_KP_SIX|KEYC_KEYPAD },
+ { "KP1", KEYC_KP_ONE|KEYC_KEYPAD },
+ { "KP2", KEYC_KP_TWO|KEYC_KEYPAD },
+ { "KP3", KEYC_KP_THREE|KEYC_KEYPAD },
+ { "KPEnter", KEYC_KP_ENTER|KEYC_KEYPAD },
+ { "KP0", KEYC_KP_ZERO|KEYC_KEYPAD },
+ { "KP.", KEYC_KP_PERIOD|KEYC_KEYPAD },
+
+ /* Mouse keys. */
+ KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1),
+ KEYC_MOUSE_STRING(MOUSEDOWN2, MouseDown2),
+ KEYC_MOUSE_STRING(MOUSEDOWN3, MouseDown3),
+ KEYC_MOUSE_STRING(MOUSEDOWN6, MouseDown6),
+ KEYC_MOUSE_STRING(MOUSEDOWN7, MouseDown7),
+ KEYC_MOUSE_STRING(MOUSEDOWN8, MouseDown8),
+ KEYC_MOUSE_STRING(MOUSEDOWN9, MouseDown9),
+ KEYC_MOUSE_STRING(MOUSEDOWN10, MouseDown10),
+ KEYC_MOUSE_STRING(MOUSEDOWN11, MouseDown11),
+ KEYC_MOUSE_STRING(MOUSEUP1, MouseUp1),
+ KEYC_MOUSE_STRING(MOUSEUP2, MouseUp2),
+ KEYC_MOUSE_STRING(MOUSEUP3, MouseUp3),
+ KEYC_MOUSE_STRING(MOUSEUP6, MouseUp6),
+ KEYC_MOUSE_STRING(MOUSEUP7, MouseUp7),
+ KEYC_MOUSE_STRING(MOUSEUP8, MouseUp8),
+ KEYC_MOUSE_STRING(MOUSEUP9, MouseUp9),
+ KEYC_MOUSE_STRING(MOUSEUP10, MouseUp10),
+ KEYC_MOUSE_STRING(MOUSEUP11, MouseUp11),
+ KEYC_MOUSE_STRING(MOUSEDRAG1, MouseDrag1),
+ KEYC_MOUSE_STRING(MOUSEDRAG2, MouseDrag2),
+ KEYC_MOUSE_STRING(MOUSEDRAG3, MouseDrag3),
+ KEYC_MOUSE_STRING(MOUSEDRAG6, MouseDrag6),
+ KEYC_MOUSE_STRING(MOUSEDRAG7, MouseDrag7),
+ KEYC_MOUSE_STRING(MOUSEDRAG8, MouseDrag8),
+ KEYC_MOUSE_STRING(MOUSEDRAG9, MouseDrag9),
+ KEYC_MOUSE_STRING(MOUSEDRAG10, MouseDrag10),
+ KEYC_MOUSE_STRING(MOUSEDRAG11, MouseDrag11),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND1, MouseDragEnd1),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND2, MouseDragEnd2),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND3, MouseDragEnd3),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND6, MouseDragEnd6),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND7, MouseDragEnd7),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND8, MouseDragEnd8),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND9, MouseDragEnd9),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND10, MouseDragEnd10),
+ KEYC_MOUSE_STRING(MOUSEDRAGEND11, MouseDragEnd11),
+ KEYC_MOUSE_STRING(WHEELUP, WheelUp),
+ KEYC_MOUSE_STRING(WHEELDOWN, WheelDown),
+ KEYC_MOUSE_STRING(SECONDCLICK1, SecondClick1),
+ KEYC_MOUSE_STRING(SECONDCLICK2, SecondClick2),
+ KEYC_MOUSE_STRING(SECONDCLICK3, SecondClick3),
+ KEYC_MOUSE_STRING(SECONDCLICK6, SecondClick6),
+ KEYC_MOUSE_STRING(SECONDCLICK7, SecondClick7),
+ KEYC_MOUSE_STRING(SECONDCLICK8, SecondClick8),
+ KEYC_MOUSE_STRING(SECONDCLICK9, SecondClick9),
+ KEYC_MOUSE_STRING(SECONDCLICK10, SecondClick10),
+ KEYC_MOUSE_STRING(SECONDCLICK11, SecondClick11),
+ KEYC_MOUSE_STRING(DOUBLECLICK1, DoubleClick1),
+ KEYC_MOUSE_STRING(DOUBLECLICK2, DoubleClick2),
+ KEYC_MOUSE_STRING(DOUBLECLICK3, DoubleClick3),
+ KEYC_MOUSE_STRING(DOUBLECLICK6, DoubleClick6),
+ KEYC_MOUSE_STRING(DOUBLECLICK7, DoubleClick7),
+ KEYC_MOUSE_STRING(DOUBLECLICK8, DoubleClick8),
+ KEYC_MOUSE_STRING(DOUBLECLICK9, DoubleClick9),
+ KEYC_MOUSE_STRING(DOUBLECLICK10, DoubleClick10),
+ KEYC_MOUSE_STRING(DOUBLECLICK11, DoubleClick11),
+ KEYC_MOUSE_STRING(TRIPLECLICK1, TripleClick1),
+ KEYC_MOUSE_STRING(TRIPLECLICK2, TripleClick2),
+ KEYC_MOUSE_STRING(TRIPLECLICK3, TripleClick3),
+ KEYC_MOUSE_STRING(TRIPLECLICK6, TripleClick6),
+ KEYC_MOUSE_STRING(TRIPLECLICK7, TripleClick7),
+ KEYC_MOUSE_STRING(TRIPLECLICK8, TripleClick8),
+ KEYC_MOUSE_STRING(TRIPLECLICK9, TripleClick9),
+ KEYC_MOUSE_STRING(TRIPLECLICK10, TripleClick10),
+ KEYC_MOUSE_STRING(TRIPLECLICK11, TripleClick11)
+};
+
+/* Find key string in table. */
+static key_code
+key_string_search_table(const char *string)
+{
+ u_int i, user;
+
+ for (i = 0; i < nitems(key_string_table); i++) {
+ if (strcasecmp(string, key_string_table[i].string) == 0)
+ return (key_string_table[i].key);
+ }
+
+ if (sscanf(string, "User%u", &user) == 1 && user < KEYC_NUSER)
+ return (KEYC_USER + user);
+
+ return (KEYC_UNKNOWN);
+}
+
+/* Find modifiers. */
+static key_code
+key_string_get_modifiers(const char **string)
+{
+ key_code modifiers;
+
+ modifiers = 0;
+ while (((*string)[0] != '\0') && (*string)[1] == '-') {
+ switch ((*string)[0]) {
+ case 'C':
+ case 'c':
+ modifiers |= KEYC_CTRL;
+ break;
+ case 'M':
+ case 'm':
+ modifiers |= KEYC_META;
+ break;
+ case 'S':
+ case 's':
+ modifiers |= KEYC_SHIFT;
+ break;
+ default:
+ *string = NULL;
+ return (0);
+ }
+ *string += 2;
+ }
+ return (modifiers);
+}
+
+/* Lookup a string and convert to a key value. */
+key_code
+key_string_lookup_string(const char *string)
+{
+ static const char *other = "!#()+,-.0123456789:;<=>'\r\t\177`/";
+ key_code key, modifiers;
+ u_int u, i;
+ struct utf8_data ud, *udp;
+ enum utf8_state more;
+ utf8_char uc;
+ char m[MB_LEN_MAX + 1];
+ int mlen;
+
+ /* Is this no key or any key? */
+ if (strcasecmp(string, "None") == 0)
+ return (KEYC_NONE);
+ if (strcasecmp(string, "Any") == 0)
+ return (KEYC_ANY);
+
+ /* Is this a hexadecimal value? */
+ if (string[0] == '0' && string[1] == 'x') {
+ if (sscanf(string + 2, "%x", &u) != 1)
+ return (KEYC_UNKNOWN);
+ if (u < 32)
+ return (u);
+ mlen = wctomb(m, u);
+ if (mlen <= 0 || mlen > MB_LEN_MAX)
+ return (KEYC_UNKNOWN);
+ m[mlen] = '\0';
+
+ udp = utf8_fromcstr(m);
+ if (udp == NULL ||
+ udp[0].size == 0 ||
+ udp[1].size != 0 ||
+ utf8_from_data(&udp[0], &uc) != UTF8_DONE) {
+ free(udp);
+ return (KEYC_UNKNOWN);
+ }
+ free(udp);
+ return (uc);
+ }
+
+ /* Check for modifiers. */
+ modifiers = 0;
+ if (string[0] == '^' && string[1] != '\0') {
+ modifiers |= KEYC_CTRL;
+ string++;
+ }
+ modifiers |= key_string_get_modifiers(&string);
+ if (string == NULL || string[0] == '\0')
+ return (KEYC_UNKNOWN);
+
+ /* Is this a standard ASCII key? */
+ if (string[1] == '\0' && (u_char)string[0] <= 127) {
+ key = (u_char)string[0];
+ if (key < 32)
+ return (KEYC_UNKNOWN);
+ } else {
+ /* Try as a UTF-8 key. */
+ if ((more = utf8_open(&ud, (u_char)*string)) == UTF8_MORE) {
+ if (strlen(string) != ud.size)
+ return (KEYC_UNKNOWN);
+ for (i = 1; i < ud.size; i++)
+ more = utf8_append(&ud, (u_char)string[i]);
+ if (more != UTF8_DONE)
+ return (KEYC_UNKNOWN);
+ if (utf8_from_data(&ud, &uc) != UTF8_DONE)
+ return (KEYC_UNKNOWN);
+ return (uc|modifiers);
+ }
+
+ /* Otherwise look the key up in the table. */
+ key = key_string_search_table(string);
+ if (key == KEYC_UNKNOWN)
+ return (KEYC_UNKNOWN);
+ if (~modifiers & KEYC_META)
+ key &= ~KEYC_IMPLIED_META;
+ }
+
+ /* Convert the standard control keys. */
+ if (key <= 127 &&
+ (modifiers & KEYC_CTRL) &&
+ strchr(other, key) == NULL &&
+ key != 9 &&
+ key != 13 &&
+ key != 27) {
+ if (key >= 97 && key <= 122)
+ key -= 96;
+ else if (key >= 64 && key <= 95)
+ key -= 64;
+ else if (key == 32)
+ key = 0;
+ else if (key == 63)
+ key = 127;
+ else
+ return (KEYC_UNKNOWN);
+ modifiers &= ~KEYC_CTRL;
+ }
+
+ return (key|modifiers);
+}
+
+/* Convert a key code into string format, with prefix if necessary. */
+const char *
+key_string_lookup_key(key_code key, int with_flags)
+{
+ key_code saved = key;
+ static char out[64];
+ char tmp[8];
+ const char *s;
+ u_int i;
+ struct utf8_data ud;
+ size_t off;
+
+ *out = '\0';
+
+ /* Literal keys are themselves. */
+ if (key & KEYC_LITERAL) {
+ snprintf(out, sizeof out, "%c", (int)(key & 0xff));
+ goto out;
+ }
+
+ /* Display C-@ as C-Space. */
+ if ((key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) == 0)
+ key = ' '|KEYC_CTRL;
+
+ /* Fill in the modifiers. */
+ if (key & KEYC_CTRL)
+ strlcat(out, "C-", sizeof out);
+ if (key & KEYC_META)
+ strlcat(out, "M-", sizeof out);
+ if (key & KEYC_SHIFT)
+ strlcat(out, "S-", sizeof out);
+ key &= KEYC_MASK_KEY;
+
+ /* Handle no key. */
+ if (key == KEYC_NONE) {
+ s = "None";
+ goto append;
+ }
+
+ /* Handle special keys. */
+ if (key == KEYC_UNKNOWN) {
+ s = "Unknown";
+ goto append;
+ }
+ if (key == KEYC_ANY) {
+ s = "Any";
+ goto append;
+ }
+ if (key == KEYC_FOCUS_IN) {
+ s = "FocusIn";
+ goto append;
+ }
+ if (key == KEYC_FOCUS_OUT) {
+ s = "FocusOut";
+ goto append;
+ }
+ if (key == KEYC_PASTE_START) {
+ s = "PasteStart";
+ goto append;
+ }
+ if (key == KEYC_PASTE_END) {
+ s = "PasteEnd";
+ goto append;
+ }
+ if (key == KEYC_MOUSE) {
+ s = "Mouse";
+ goto append;
+ }
+ if (key == KEYC_DRAGGING) {
+ s = "Dragging";
+ goto append;
+ }
+ if (key == KEYC_MOUSEMOVE_PANE) {
+ s = "MouseMovePane";
+ goto append;
+ }
+ if (key == KEYC_MOUSEMOVE_STATUS) {
+ s = "MouseMoveStatus";
+ goto append;
+ }
+ if (key == KEYC_MOUSEMOVE_STATUS_LEFT) {
+ s = "MouseMoveStatusLeft";
+ goto append;
+ }
+ if (key == KEYC_MOUSEMOVE_STATUS_RIGHT) {
+ s = "MouseMoveStatusRight";
+ goto append;
+ }
+ if (key == KEYC_MOUSEMOVE_BORDER) {
+ s = "MouseMoveBorder";
+ goto append;
+ }
+ if (key >= KEYC_USER && key < KEYC_USER + KEYC_NUSER) {
+ snprintf(tmp, sizeof tmp, "User%u", (u_int)(key - KEYC_USER));
+ strlcat(out, tmp, sizeof out);
+ goto out;
+ }
+
+ /* Try the key against the string table. */
+ for (i = 0; i < nitems(key_string_table); i++) {
+ if (key == (key_string_table[i].key & KEYC_MASK_KEY))
+ break;
+ }
+ if (i != nitems(key_string_table)) {
+ strlcat(out, key_string_table[i].string, sizeof out);
+ goto out;
+ }
+
+ /* Is this a Unicode key? */
+ if (KEYC_IS_UNICODE(key)) {
+ utf8_to_data(key, &ud);
+ off = strlen(out);
+ memcpy(out + off, ud.data, ud.size);
+ out[off + ud.size] = '\0';
+ goto out;
+ }
+
+ /* Invalid keys are errors. */
+ if (key > 255) {
+ snprintf(out, sizeof out, "Invalid#%llx", saved);
+ goto out;
+ }
+
+ /* Check for standard or control key. */
+ if (key <= 32) {
+ if (key == 0 || key > 26)
+ xsnprintf(tmp, sizeof tmp, "C-%c", (int)(64 + key));
+ else
+ xsnprintf(tmp, sizeof tmp, "C-%c", (int)(96 + key));
+ } else if (key >= 32 && key <= 126) {
+ tmp[0] = key;
+ tmp[1] = '\0';
+ } else if (key == 127)
+ xsnprintf(tmp, sizeof tmp, "C-?");
+ else if (key >= 128)
+ xsnprintf(tmp, sizeof tmp, "\\%llo", key);
+
+ strlcat(out, tmp, sizeof out);
+ goto out;
+
+append:
+ strlcat(out, s, sizeof out);
+
+out:
+ if (with_flags && (saved & KEYC_MASK_FLAGS) != 0) {
+ strlcat(out, "[", sizeof out);
+ if (saved & KEYC_LITERAL)
+ strlcat(out, "L", sizeof out);
+ if (saved & KEYC_KEYPAD)
+ strlcat(out, "K", sizeof out);
+ if (saved & KEYC_CURSOR)
+ strlcat(out, "C", sizeof out);
+ if (saved & KEYC_IMPLIED_META)
+ strlcat(out, "I", sizeof out);
+ if (saved & KEYC_BUILD_MODIFIERS)
+ strlcat(out, "B", sizeof out);
+ strlcat(out, "]", sizeof out);
+ }
+ return (out);
+}
diff --git a/layout-custom.c b/layout-custom.c
new file mode 100644
index 0000000..932b30e
--- /dev/null
+++ b/layout-custom.c
@@ -0,0 +1,368 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2010 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 <ctype.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static struct layout_cell *layout_find_bottomright(struct layout_cell *);
+static u_short layout_checksum(const char *);
+static int layout_append(struct layout_cell *, char *,
+ size_t);
+static struct layout_cell *layout_construct(struct layout_cell *,
+ const char **);
+static void layout_assign(struct window_pane **,
+ struct layout_cell *);
+
+/* Find the bottom-right cell. */
+static struct layout_cell *
+layout_find_bottomright(struct layout_cell *lc)
+{
+ if (lc->type == LAYOUT_WINDOWPANE)
+ return (lc);
+ lc = TAILQ_LAST(&lc->cells, layout_cells);
+ return (layout_find_bottomright(lc));
+}
+
+/* Calculate layout checksum. */
+static u_short
+layout_checksum(const char *layout)
+{
+ u_short csum;
+
+ csum = 0;
+ for (; *layout != '\0'; layout++) {
+ csum = (csum >> 1) + ((csum & 1) << 15);
+ csum += *layout;
+ }
+ return (csum);
+}
+
+/* Dump layout as a string. */
+char *
+layout_dump(struct layout_cell *root)
+{
+ char layout[8192], *out;
+
+ *layout = '\0';
+ if (layout_append(root, layout, sizeof layout) != 0)
+ return (NULL);
+
+ xasprintf(&out, "%04hx,%s", layout_checksum(layout), layout);
+ return (out);
+}
+
+/* Append information for a single cell. */
+static int
+layout_append(struct layout_cell *lc, char *buf, size_t len)
+{
+ struct layout_cell *lcchild;
+ char tmp[64];
+ size_t tmplen;
+ const char *brackets = "][";
+
+ if (len == 0)
+ return (-1);
+
+ if (lc->wp != NULL) {
+ tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u,%u",
+ lc->sx, lc->sy, lc->xoff, lc->yoff, lc->wp->id);
+ } else {
+ tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u",
+ lc->sx, lc->sy, lc->xoff, lc->yoff);
+ }
+ if (tmplen > (sizeof tmp) - 1)
+ return (-1);
+ if (strlcat(buf, tmp, len) >= len)
+ return (-1);
+
+ switch (lc->type) {
+ case LAYOUT_LEFTRIGHT:
+ brackets = "}{";
+ /* FALLTHROUGH */
+ case LAYOUT_TOPBOTTOM:
+ if (strlcat(buf, &brackets[1], len) >= len)
+ return (-1);
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (layout_append(lcchild, buf, len) != 0)
+ return (-1);
+ if (strlcat(buf, ",", len) >= len)
+ return (-1);
+ }
+ buf[strlen(buf) - 1] = brackets[0];
+ break;
+ case LAYOUT_WINDOWPANE:
+ break;
+ }
+
+ return (0);
+}
+
+/* Check layout sizes fit. */
+static int
+layout_check(struct layout_cell *lc)
+{
+ struct layout_cell *lcchild;
+ u_int n = 0;
+
+ switch (lc->type) {
+ case LAYOUT_WINDOWPANE:
+ break;
+ case LAYOUT_LEFTRIGHT:
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (lcchild->sy != lc->sy)
+ return (0);
+ if (!layout_check(lcchild))
+ return (0);
+ n += lcchild->sx + 1;
+ }
+ if (n - 1 != lc->sx)
+ return (0);
+ break;
+ case LAYOUT_TOPBOTTOM:
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (lcchild->sx != lc->sx)
+ return (0);
+ if (!layout_check(lcchild))
+ return (0);
+ n += lcchild->sy + 1;
+ }
+ if (n - 1 != lc->sy)
+ return (0);
+ break;
+ }
+ return (1);
+}
+
+/* Parse a layout string and arrange window as layout. */
+int
+layout_parse(struct window *w, const char *layout, char **cause)
+{
+ struct layout_cell *lc, *lcchild;
+ struct window_pane *wp;
+ u_int npanes, ncells, sx = 0, sy = 0;
+ u_short csum;
+
+ /* Check validity. */
+ if (sscanf(layout, "%hx,", &csum) != 1)
+ return (-1);
+ layout += 5;
+ if (csum != layout_checksum(layout)) {
+ *cause = xstrdup("invalid layout");
+ return (-1);
+ }
+
+ /* Build the layout. */
+ lc = layout_construct(NULL, &layout);
+ if (lc == NULL) {
+ *cause = xstrdup("invalid layout");
+ return (-1);
+ }
+ if (*layout != '\0') {
+ *cause = xstrdup("invalid layout");
+ goto fail;
+ }
+
+ /* Check this window will fit into the layout. */
+ for (;;) {
+ npanes = window_count_panes(w);
+ ncells = layout_count_cells(lc);
+ if (npanes > ncells) {
+ xasprintf(cause, "have %u panes but need %u", npanes,
+ ncells);
+ goto fail;
+ }
+ if (npanes == ncells)
+ break;
+
+ /* Fewer panes than cells - close the bottom right. */
+ lcchild = layout_find_bottomright(lc);
+ layout_destroy_cell(w, lcchild, &lc);
+ }
+
+ /*
+ * It appears older versions of tmux were able to generate layouts with
+ * an incorrect top cell size - if it is larger than the top child then
+ * correct that (if this is still wrong the check code will catch it).
+ */
+ switch (lc->type) {
+ case LAYOUT_WINDOWPANE:
+ break;
+ case LAYOUT_LEFTRIGHT:
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ sy = lcchild->sy + 1;
+ sx += lcchild->sx + 1;
+ }
+ break;
+ case LAYOUT_TOPBOTTOM:
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ sx = lcchild->sx + 1;
+ sy += lcchild->sy + 1;
+ }
+ break;
+ }
+ if (lc->type != LAYOUT_WINDOWPANE && (lc->sx != sx || lc->sy != sy)) {
+ log_debug("fix layout %u,%u to %u,%u", lc->sx, lc->sy, sx,sy);
+ layout_print_cell(lc, __func__, 0);
+ lc->sx = sx - 1; lc->sy = sy - 1;
+ }
+
+ /* Check the new layout. */
+ if (!layout_check(lc)) {
+ *cause = xstrdup("size mismatch after applying layout");
+ return (-1);
+ }
+
+ /* Resize to the layout size. */
+ window_resize(w, lc->sx, lc->sy, -1, -1);
+
+ /* Destroy the old layout and swap to the new. */
+ layout_free_cell(w->layout_root);
+ w->layout_root = lc;
+
+ /* Assign the panes into the cells. */
+ wp = TAILQ_FIRST(&w->panes);
+ layout_assign(&wp, lc);
+
+ /* Update pane offsets and sizes. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+ recalculate_sizes();
+
+ layout_print_cell(lc, __func__, 0);
+
+ notify_window("window-layout-changed", w);
+
+ return (0);
+
+fail:
+ layout_free_cell(lc);
+ return (-1);
+}
+
+/* Assign panes into cells. */
+static void
+layout_assign(struct window_pane **wp, struct layout_cell *lc)
+{
+ struct layout_cell *lcchild;
+
+ switch (lc->type) {
+ case LAYOUT_WINDOWPANE:
+ layout_make_leaf(lc, *wp);
+ *wp = TAILQ_NEXT(*wp, entry);
+ return;
+ case LAYOUT_LEFTRIGHT:
+ case LAYOUT_TOPBOTTOM:
+ TAILQ_FOREACH(lcchild, &lc->cells, entry)
+ layout_assign(wp, lcchild);
+ return;
+ }
+}
+
+/* Construct a cell from all or part of a layout tree. */
+static struct layout_cell *
+layout_construct(struct layout_cell *lcparent, const char **layout)
+{
+ struct layout_cell *lc, *lcchild;
+ u_int sx, sy, xoff, yoff;
+ const char *saved;
+
+ if (!isdigit((u_char) **layout))
+ return (NULL);
+ if (sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4)
+ return (NULL);
+
+ while (isdigit((u_char) **layout))
+ (*layout)++;
+ if (**layout != 'x')
+ return (NULL);
+ (*layout)++;
+ while (isdigit((u_char) **layout))
+ (*layout)++;
+ if (**layout != ',')
+ return (NULL);
+ (*layout)++;
+ while (isdigit((u_char) **layout))
+ (*layout)++;
+ if (**layout != ',')
+ return (NULL);
+ (*layout)++;
+ while (isdigit((u_char) **layout))
+ (*layout)++;
+ if (**layout == ',') {
+ saved = *layout;
+ (*layout)++;
+ while (isdigit((u_char) **layout))
+ (*layout)++;
+ if (**layout == 'x')
+ *layout = saved;
+ }
+
+ lc = layout_create_cell(lcparent);
+ lc->sx = sx;
+ lc->sy = sy;
+ lc->xoff = xoff;
+ lc->yoff = yoff;
+
+ switch (**layout) {
+ case ',':
+ case '}':
+ case ']':
+ case '\0':
+ return (lc);
+ case '{':
+ lc->type = LAYOUT_LEFTRIGHT;
+ break;
+ case '[':
+ lc->type = LAYOUT_TOPBOTTOM;
+ break;
+ default:
+ goto fail;
+ }
+
+ do {
+ (*layout)++;
+ lcchild = layout_construct(lc, layout);
+ if (lcchild == NULL)
+ goto fail;
+ TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
+ } while (**layout == ',');
+
+ switch (lc->type) {
+ case LAYOUT_LEFTRIGHT:
+ if (**layout != '}')
+ goto fail;
+ break;
+ case LAYOUT_TOPBOTTOM:
+ if (**layout != ']')
+ goto fail;
+ break;
+ default:
+ goto fail;
+ }
+ (*layout)++;
+
+ return (lc);
+
+fail:
+ layout_free_cell(lc);
+ return (NULL);
+}
diff --git a/layout-set.c b/layout-set.c
new file mode 100644
index 0000000..c702817
--- /dev/null
+++ b/layout-set.c
@@ -0,0 +1,487 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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"
+
+/*
+ * Set window layouts - predefined methods to arrange windows. These are
+ * one-off and generate a layout tree.
+ */
+
+static void layout_set_even_h(struct window *);
+static void layout_set_even_v(struct window *);
+static void layout_set_main_h(struct window *);
+static void layout_set_main_v(struct window *);
+static void layout_set_tiled(struct window *);
+
+static const struct {
+ const char *name;
+ void (*arrange)(struct window *);
+} layout_sets[] = {
+ { "even-horizontal", layout_set_even_h },
+ { "even-vertical", layout_set_even_v },
+ { "main-horizontal", layout_set_main_h },
+ { "main-vertical", layout_set_main_v },
+ { "tiled", layout_set_tiled },
+};
+
+int
+layout_set_lookup(const char *name)
+{
+ u_int i;
+ int matched = -1;
+
+ for (i = 0; i < nitems(layout_sets); i++) {
+ if (strncmp(layout_sets[i].name, name, strlen(name)) == 0) {
+ if (matched != -1) /* ambiguous */
+ return (-1);
+ matched = i;
+ }
+ }
+
+ return (matched);
+}
+
+u_int
+layout_set_select(struct window *w, u_int layout)
+{
+ if (layout > nitems(layout_sets) - 1)
+ layout = nitems(layout_sets) - 1;
+
+ if (layout_sets[layout].arrange != NULL)
+ layout_sets[layout].arrange(w);
+
+ w->lastlayout = layout;
+ return (layout);
+}
+
+u_int
+layout_set_next(struct window *w)
+{
+ u_int layout;
+
+ if (w->lastlayout == -1)
+ layout = 0;
+ else {
+ layout = w->lastlayout + 1;
+ if (layout > nitems(layout_sets) - 1)
+ layout = 0;
+ }
+
+ if (layout_sets[layout].arrange != NULL)
+ layout_sets[layout].arrange(w);
+ w->lastlayout = layout;
+ return (layout);
+}
+
+u_int
+layout_set_previous(struct window *w)
+{
+ u_int layout;
+
+ if (w->lastlayout == -1)
+ layout = nitems(layout_sets) - 1;
+ else {
+ layout = w->lastlayout;
+ if (layout == 0)
+ layout = nitems(layout_sets) - 1;
+ else
+ layout--;
+ }
+
+ if (layout_sets[layout].arrange != NULL)
+ layout_sets[layout].arrange(w);
+ w->lastlayout = layout;
+ return (layout);
+}
+
+static void
+layout_set_even(struct window *w, enum layout_type type)
+{
+ struct window_pane *wp;
+ struct layout_cell *lc, *lcnew;
+ u_int n, sx, sy;
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ /* Get number of panes. */
+ n = window_count_panes(w);
+ if (n <= 1)
+ return;
+
+ /* Free the old root and construct a new. */
+ layout_free(w);
+ lc = w->layout_root = layout_create_cell(NULL);
+ if (type == LAYOUT_LEFTRIGHT) {
+ sx = (n * (PANE_MINIMUM + 1)) - 1;
+ if (sx < w->sx)
+ sx = w->sx;
+ sy = w->sy;
+ } else {
+ sy = (n * (PANE_MINIMUM + 1)) - 1;
+ if (sy < w->sy)
+ sy = w->sy;
+ sx = w->sx;
+ }
+ layout_set_size(lc, sx, sy, 0, 0);
+ layout_make_node(lc, type);
+
+ /* Build new leaf cells. */
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ lcnew = layout_create_cell(lc);
+ layout_make_leaf(lcnew, wp);
+ lcnew->sx = w->sx;
+ lcnew->sy = w->sy;
+ TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
+ }
+
+ /* Spread out cells. */
+ layout_spread_cell(w, lc);
+
+ /* Fix cell offsets. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ window_resize(w, lc->sx, lc->sy, -1, -1);
+ notify_window("window-layout-changed", w);
+ server_redraw_window(w);
+}
+
+static void
+layout_set_even_h(struct window *w)
+{
+ layout_set_even(w, LAYOUT_LEFTRIGHT);
+}
+
+static void
+layout_set_even_v(struct window *w)
+{
+ layout_set_even(w, LAYOUT_TOPBOTTOM);
+}
+
+static void
+layout_set_main_h(struct window *w)
+{
+ struct window_pane *wp;
+ struct layout_cell *lc, *lcmain, *lcother, *lcchild;
+ u_int n, mainh, otherh, sx, sy;
+ char *cause;
+ const char *s;
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ /* Get number of panes. */
+ n = window_count_panes(w);
+ if (n <= 1)
+ return;
+ n--; /* take off main pane */
+
+ /* Find available height - take off one line for the border. */
+ sy = w->sy - 1;
+
+ /* Get the main pane height. */
+ s = options_get_string(w->options, "main-pane-height");
+ mainh = args_string_percentage(s, 0, sy, sy, &cause);
+ if (cause != NULL) {
+ mainh = 24;
+ free(cause);
+ }
+
+ /* Work out the other pane height. */
+ if (mainh + PANE_MINIMUM >= sy) {
+ if (sy <= PANE_MINIMUM + PANE_MINIMUM)
+ mainh = PANE_MINIMUM;
+ else
+ mainh = sy - PANE_MINIMUM;
+ otherh = PANE_MINIMUM;
+ } else {
+ s = options_get_string(w->options, "other-pane-height");
+ otherh = args_string_percentage(s, 0, sy, sy, &cause);
+ if (cause != NULL || otherh == 0) {
+ otherh = sy - mainh;
+ free(cause);
+ } else if (otherh > sy || sy - otherh < mainh)
+ otherh = sy - mainh;
+ else
+ mainh = sy - otherh;
+ }
+
+ /* Work out what width is needed. */
+ sx = (n * (PANE_MINIMUM + 1)) - 1;
+ if (sx < w->sx)
+ sx = w->sx;
+
+ /* Free old tree and create a new root. */
+ layout_free(w);
+ lc = w->layout_root = layout_create_cell(NULL);
+ layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
+ layout_make_node(lc, LAYOUT_TOPBOTTOM);
+
+ /* Create the main pane. */
+ lcmain = layout_create_cell(lc);
+ layout_set_size(lcmain, sx, mainh, 0, 0);
+ layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
+ TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
+
+ /* Create the other pane. */
+ lcother = layout_create_cell(lc);
+ layout_set_size(lcother, sx, otherh, 0, 0);
+ if (n == 1) {
+ wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
+ layout_make_leaf(lcother, wp);
+ TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
+ } else {
+ layout_make_node(lcother, LAYOUT_LEFTRIGHT);
+ TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
+
+ /* Add the remaining panes as children. */
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp == TAILQ_FIRST(&w->panes))
+ continue;
+ lcchild = layout_create_cell(lcother);
+ layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
+ layout_make_leaf(lcchild, wp);
+ TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
+ }
+ layout_spread_cell(w, lcother);
+ }
+
+ /* Fix cell offsets. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ window_resize(w, lc->sx, lc->sy, -1, -1);
+ notify_window("window-layout-changed", w);
+ server_redraw_window(w);
+}
+
+static void
+layout_set_main_v(struct window *w)
+{
+ struct window_pane *wp;
+ struct layout_cell *lc, *lcmain, *lcother, *lcchild;
+ u_int n, mainw, otherw, sx, sy;
+ char *cause;
+ const char *s;
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ /* Get number of panes. */
+ n = window_count_panes(w);
+ if (n <= 1)
+ return;
+ n--; /* take off main pane */
+
+ /* Find available width - take off one line for the border. */
+ sx = w->sx - 1;
+
+ /* Get the main pane width. */
+ s = options_get_string(w->options, "main-pane-width");
+ mainw = args_string_percentage(s, 0, sx, sx, &cause);
+ if (cause != NULL) {
+ mainw = 80;
+ free(cause);
+ }
+
+ /* Work out the other pane width. */
+ if (mainw + PANE_MINIMUM >= sx) {
+ if (sx <= PANE_MINIMUM + PANE_MINIMUM)
+ mainw = PANE_MINIMUM;
+ else
+ mainw = sx - PANE_MINIMUM;
+ otherw = PANE_MINIMUM;
+ } else {
+ s = options_get_string(w->options, "other-pane-width");
+ otherw = args_string_percentage(s, 0, sx, sx, &cause);
+ if (cause != NULL || otherw == 0) {
+ otherw = sx - mainw;
+ free(cause);
+ } else if (otherw > sx || sx - otherw < mainw)
+ otherw = sx - mainw;
+ else
+ mainw = sx - otherw;
+ }
+
+ /* Work out what height is needed. */
+ sy = (n * (PANE_MINIMUM + 1)) - 1;
+ if (sy < w->sy)
+ sy = w->sy;
+
+ /* Free old tree and create a new root. */
+ layout_free(w);
+ lc = w->layout_root = layout_create_cell(NULL);
+ layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
+ layout_make_node(lc, LAYOUT_LEFTRIGHT);
+
+ /* Create the main pane. */
+ lcmain = layout_create_cell(lc);
+ layout_set_size(lcmain, mainw, sy, 0, 0);
+ layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes));
+ TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
+
+ /* Create the other pane. */
+ lcother = layout_create_cell(lc);
+ layout_set_size(lcother, otherw, sy, 0, 0);
+ if (n == 1) {
+ wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
+ layout_make_leaf(lcother, wp);
+ TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
+ } else {
+ layout_make_node(lcother, LAYOUT_TOPBOTTOM);
+ TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
+
+ /* Add the remaining panes as children. */
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp == TAILQ_FIRST(&w->panes))
+ continue;
+ lcchild = layout_create_cell(lcother);
+ layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
+ layout_make_leaf(lcchild, wp);
+ TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
+ }
+ layout_spread_cell(w, lcother);
+ }
+
+ /* Fix cell offsets. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ window_resize(w, lc->sx, lc->sy, -1, -1);
+ notify_window("window-layout-changed", w);
+ server_redraw_window(w);
+}
+
+void
+layout_set_tiled(struct window *w)
+{
+ struct window_pane *wp;
+ struct layout_cell *lc, *lcrow, *lcchild;
+ u_int n, width, height, used, sx, sy;
+ u_int i, j, columns, rows;
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ /* Get number of panes. */
+ n = window_count_panes(w);
+ if (n <= 1)
+ return;
+
+ /* How many rows and columns are wanted? */
+ rows = columns = 1;
+ while (rows * columns < n) {
+ rows++;
+ if (rows * columns < n)
+ columns++;
+ }
+
+ /* What width and height should they be? */
+ width = (w->sx - (columns - 1)) / columns;
+ if (width < PANE_MINIMUM)
+ width = PANE_MINIMUM;
+ height = (w->sy - (rows - 1)) / rows;
+ if (height < PANE_MINIMUM)
+ height = PANE_MINIMUM;
+
+ /* Free old tree and create a new root. */
+ layout_free(w);
+ lc = w->layout_root = layout_create_cell(NULL);
+ sx = ((width + 1) * columns) - 1;
+ if (sx < w->sx)
+ sx = w->sx;
+ sy = ((height + 1) * rows) - 1;
+ if (sy < w->sy)
+ sy = w->sy;
+ layout_set_size(lc, sx, sy, 0, 0);
+ layout_make_node(lc, LAYOUT_TOPBOTTOM);
+
+ /* Create a grid of the cells. */
+ wp = TAILQ_FIRST(&w->panes);
+ for (j = 0; j < rows; j++) {
+ /* If this is the last cell, all done. */
+ if (wp == NULL)
+ break;
+
+ /* Create the new row. */
+ lcrow = layout_create_cell(lc);
+ layout_set_size(lcrow, w->sx, height, 0, 0);
+ TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
+
+ /* If only one column, just use the row directly. */
+ if (n - (j * columns) == 1 || columns == 1) {
+ layout_make_leaf(lcrow, wp);
+ wp = TAILQ_NEXT(wp, entry);
+ continue;
+ }
+
+ /* Add in the columns. */
+ layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
+ for (i = 0; i < columns; i++) {
+ /* Create and add a pane cell. */
+ lcchild = layout_create_cell(lcrow);
+ layout_set_size(lcchild, width, height, 0, 0);
+ layout_make_leaf(lcchild, wp);
+ TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
+
+ /* Move to the next cell. */
+ if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
+ break;
+ }
+
+ /*
+ * Adjust the row and columns to fit the full width if
+ * necessary.
+ */
+ if (i == columns)
+ i--;
+ used = ((i + 1) * (width + 1)) - 1;
+ if (w->sx <= used)
+ continue;
+ lcchild = TAILQ_LAST(&lcrow->cells, layout_cells);
+ layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT,
+ w->sx - used);
+ }
+
+ /* Adjust the last row height to fit if necessary. */
+ used = (rows * height) + rows - 1;
+ if (w->sy > used) {
+ lcrow = TAILQ_LAST(&lc->cells, layout_cells);
+ layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
+ w->sy - used);
+ }
+
+ /* Fix cell offsets. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+
+ layout_print_cell(w->layout_root, __func__, 1);
+
+ window_resize(w, lc->sx, lc->sy, -1, -1);
+ notify_window("window-layout-changed", w);
+ server_redraw_window(w);
+}
diff --git a/layout.c b/layout.c
new file mode 100644
index 0000000..04a13b0
--- /dev/null
+++ b/layout.c
@@ -0,0 +1,1120 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2016 Stephen Kent <smkent@smkent.net>
+ *
+ * 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 "tmux.h"
+
+/*
+ * The window layout is a tree of cells each of which can be one of: a
+ * left-right container for a list of cells, a top-bottom container for a list
+ * of cells, or a container for a window pane.
+ *
+ * Each window has a pointer to the root of its layout tree (containing its
+ * panes), every pane has a pointer back to the cell containing it, and each
+ * cell a pointer to its parent cell.
+ */
+
+static u_int layout_resize_check(struct window *, struct layout_cell *,
+ enum layout_type);
+static int layout_resize_pane_grow(struct window *, struct layout_cell *,
+ enum layout_type, int, int);
+static int layout_resize_pane_shrink(struct window *, struct layout_cell *,
+ enum layout_type, int);
+static u_int layout_new_pane_size(struct window *, u_int,
+ struct layout_cell *, enum layout_type, u_int, u_int,
+ u_int);
+static int layout_set_size_check(struct window *, struct layout_cell *,
+ enum layout_type, int);
+static void layout_resize_child_cells(struct window *,
+ struct layout_cell *);
+
+struct layout_cell *
+layout_create_cell(struct layout_cell *lcparent)
+{
+ struct layout_cell *lc;
+
+ lc = xmalloc(sizeof *lc);
+ lc->type = LAYOUT_WINDOWPANE;
+ lc->parent = lcparent;
+
+ TAILQ_INIT(&lc->cells);
+
+ lc->sx = UINT_MAX;
+ lc->sy = UINT_MAX;
+
+ lc->xoff = UINT_MAX;
+ lc->yoff = UINT_MAX;
+
+ lc->wp = NULL;
+
+ return (lc);
+}
+
+void
+layout_free_cell(struct layout_cell *lc)
+{
+ struct layout_cell *lcchild;
+
+ switch (lc->type) {
+ case LAYOUT_LEFTRIGHT:
+ case LAYOUT_TOPBOTTOM:
+ while (!TAILQ_EMPTY(&lc->cells)) {
+ lcchild = TAILQ_FIRST(&lc->cells);
+ TAILQ_REMOVE(&lc->cells, lcchild, entry);
+ layout_free_cell(lcchild);
+ }
+ break;
+ case LAYOUT_WINDOWPANE:
+ if (lc->wp != NULL)
+ lc->wp->layout_cell = NULL;
+ break;
+ }
+
+ free(lc);
+}
+
+void
+layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n)
+{
+ struct layout_cell *lcchild;
+ const char *type;
+
+ switch (lc->type) {
+ case LAYOUT_LEFTRIGHT:
+ type = "LEFTRIGHT";
+ break;
+ case LAYOUT_TOPBOTTOM:
+ type = "TOPBOTTOM";
+ break;
+ case LAYOUT_WINDOWPANE:
+ type = "WINDOWPANE";
+ break;
+ default:
+ type = "UNKNOWN";
+ break;
+ }
+ log_debug("%s:%*s%p type %s [parent %p] wp=%p [%u,%u %ux%u]", hdr, n,
+ " ", lc, type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx,
+ lc->sy);
+ switch (lc->type) {
+ case LAYOUT_LEFTRIGHT:
+ case LAYOUT_TOPBOTTOM:
+ TAILQ_FOREACH(lcchild, &lc->cells, entry)
+ layout_print_cell(lcchild, hdr, n + 1);
+ break;
+ case LAYOUT_WINDOWPANE:
+ break;
+ }
+}
+
+struct layout_cell *
+layout_search_by_border(struct layout_cell *lc, u_int x, u_int y)
+{
+ struct layout_cell *lcchild, *last = NULL;
+
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (x >= lcchild->xoff && x < lcchild->xoff + lcchild->sx &&
+ y >= lcchild->yoff && y < lcchild->yoff + lcchild->sy) {
+ /* Inside the cell - recurse. */
+ return (layout_search_by_border(lcchild, x, y));
+ }
+
+ if (last == NULL) {
+ last = lcchild;
+ continue;
+ }
+
+ switch (lc->type) {
+ case LAYOUT_LEFTRIGHT:
+ if (x < lcchild->xoff && x >= last->xoff + last->sx)
+ return (last);
+ break;
+ case LAYOUT_TOPBOTTOM:
+ if (y < lcchild->yoff && y >= last->yoff + last->sy)
+ return (last);
+ break;
+ case LAYOUT_WINDOWPANE:
+ break;
+ }
+
+ last = lcchild;
+ }
+
+ return (NULL);
+}
+
+void
+layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, u_int xoff,
+ u_int yoff)
+{
+ lc->sx = sx;
+ lc->sy = sy;
+
+ lc->xoff = xoff;
+ lc->yoff = yoff;
+}
+
+void
+layout_make_leaf(struct layout_cell *lc, struct window_pane *wp)
+{
+ lc->type = LAYOUT_WINDOWPANE;
+
+ TAILQ_INIT(&lc->cells);
+
+ wp->layout_cell = lc;
+ lc->wp = wp;
+}
+
+void
+layout_make_node(struct layout_cell *lc, enum layout_type type)
+{
+ if (type == LAYOUT_WINDOWPANE)
+ fatalx("bad layout type");
+ lc->type = type;
+
+ TAILQ_INIT(&lc->cells);
+
+ if (lc->wp != NULL)
+ lc->wp->layout_cell = NULL;
+ lc->wp = NULL;
+}
+
+/* Fix cell offsets for a child cell. */
+static void
+layout_fix_offsets1(struct layout_cell *lc)
+{
+ struct layout_cell *lcchild;
+ u_int xoff, yoff;
+
+ if (lc->type == LAYOUT_LEFTRIGHT) {
+ xoff = lc->xoff;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ lcchild->xoff = xoff;
+ lcchild->yoff = lc->yoff;
+ if (lcchild->type != LAYOUT_WINDOWPANE)
+ layout_fix_offsets1(lcchild);
+ xoff += lcchild->sx + 1;
+ }
+ } else {
+ yoff = lc->yoff;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ lcchild->xoff = lc->xoff;
+ lcchild->yoff = yoff;
+ if (lcchild->type != LAYOUT_WINDOWPANE)
+ layout_fix_offsets1(lcchild);
+ yoff += lcchild->sy + 1;
+ }
+ }
+}
+
+/* Update cell offsets based on their sizes. */
+void
+layout_fix_offsets(struct window *w)
+{
+ struct layout_cell *lc = w->layout_root;
+
+ lc->xoff = 0;
+ lc->yoff = 0;
+
+ layout_fix_offsets1(lc);
+}
+
+/* Is this a top cell? */
+static int
+layout_cell_is_top(struct window *w, struct layout_cell *lc)
+{
+ struct layout_cell *next;
+
+ while (lc != w->layout_root) {
+ next = lc->parent;
+ if (next->type == LAYOUT_TOPBOTTOM &&
+ lc != TAILQ_FIRST(&next->cells))
+ return (0);
+ lc = next;
+ }
+ return (1);
+}
+
+/* Is this a bottom cell? */
+static int
+layout_cell_is_bottom(struct window *w, struct layout_cell *lc)
+{
+ struct layout_cell *next;
+
+ while (lc != w->layout_root) {
+ next = lc->parent;
+ if (next->type == LAYOUT_TOPBOTTOM &&
+ lc != TAILQ_LAST(&next->cells, layout_cells))
+ return (0);
+ lc = next;
+ }
+ return (1);
+}
+
+/*
+ * Returns 1 if we need to add an extra line for the pane status line. This is
+ * the case for the most upper or lower panes only.
+ */
+static int
+layout_add_border(struct window *w, struct layout_cell *lc, int status)
+{
+ if (status == PANE_STATUS_TOP)
+ return (layout_cell_is_top(w, lc));
+ if (status == PANE_STATUS_BOTTOM)
+ return (layout_cell_is_bottom(w, lc));
+ return (0);
+}
+
+/* Update pane offsets and sizes based on their cells. */
+void
+layout_fix_panes(struct window *w, struct window_pane *skip)
+{
+ struct window_pane *wp;
+ struct layout_cell *lc;
+ int status;
+
+ status = options_get_number(w->options, "pane-border-status");
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if ((lc = wp->layout_cell) == NULL || wp == skip)
+ continue;
+
+ wp->xoff = lc->xoff;
+ wp->yoff = lc->yoff;
+
+ if (layout_add_border(w, lc, status)) {
+ if (status == PANE_STATUS_TOP)
+ wp->yoff++;
+ window_pane_resize(wp, lc->sx, lc->sy - 1);
+ } else
+ window_pane_resize(wp, lc->sx, lc->sy);
+ }
+}
+
+/* Count the number of available cells in a layout. */
+u_int
+layout_count_cells(struct layout_cell *lc)
+{
+ struct layout_cell *lcchild;
+ u_int count;
+
+ switch (lc->type) {
+ case LAYOUT_WINDOWPANE:
+ return (1);
+ case LAYOUT_LEFTRIGHT:
+ case LAYOUT_TOPBOTTOM:
+ count = 0;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry)
+ count += layout_count_cells(lcchild);
+ return (count);
+ default:
+ fatalx("bad layout type");
+ }
+}
+
+/* Calculate how much size is available to be removed from a cell. */
+static u_int
+layout_resize_check(struct window *w, struct layout_cell *lc,
+ enum layout_type type)
+{
+ struct layout_cell *lcchild;
+ u_int available, minimum;
+ int status;
+
+ status = options_get_number(w->options, "pane-border-status");
+ if (lc->type == LAYOUT_WINDOWPANE) {
+ /* Space available in this cell only. */
+ if (type == LAYOUT_LEFTRIGHT) {
+ available = lc->sx;
+ minimum = PANE_MINIMUM;
+ } else {
+ available = lc->sy;
+ if (layout_add_border(w, lc, status))
+ minimum = PANE_MINIMUM + 1;
+ else
+ minimum = PANE_MINIMUM;
+ }
+ if (available > minimum)
+ available -= minimum;
+ else
+ available = 0;
+ } else if (lc->type == type) {
+ /* Same type: total of available space in all child cells. */
+ available = 0;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry)
+ available += layout_resize_check(w, lcchild, type);
+ } else {
+ /* Different type: minimum of available space in child cells. */
+ minimum = UINT_MAX;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ available = layout_resize_check(w, lcchild, type);
+ if (available < minimum)
+ minimum = available;
+ }
+ available = minimum;
+ }
+
+ return (available);
+}
+
+/*
+ * Adjust cell size evenly, including altering its children. This function
+ * expects the change to have already been bounded to the space available.
+ */
+void
+layout_resize_adjust(struct window *w, struct layout_cell *lc,
+ enum layout_type type, int change)
+{
+ struct layout_cell *lcchild;
+
+ /* Adjust the cell size. */
+ if (type == LAYOUT_LEFTRIGHT)
+ lc->sx += change;
+ else
+ lc->sy += change;
+
+ /* If this is a leaf cell, that is all that is necessary. */
+ if (type == LAYOUT_WINDOWPANE)
+ return;
+
+ /* Child cell runs in a different direction. */
+ if (lc->type != type) {
+ TAILQ_FOREACH(lcchild, &lc->cells, entry)
+ layout_resize_adjust(w, lcchild, type, change);
+ return;
+ }
+
+ /*
+ * Child cell runs in the same direction. Adjust each child equally
+ * until no further change is possible.
+ */
+ while (change != 0) {
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (change == 0)
+ break;
+ if (change > 0) {
+ layout_resize_adjust(w, lcchild, type, 1);
+ change--;
+ continue;
+ }
+ if (layout_resize_check(w, lcchild, type) > 0) {
+ layout_resize_adjust(w, lcchild, type, -1);
+ change++;
+ }
+ }
+ }
+}
+
+/* Destroy a cell and redistribute the space. */
+void
+layout_destroy_cell(struct window *w, struct layout_cell *lc,
+ struct layout_cell **lcroot)
+{
+ struct layout_cell *lcother, *lcparent;
+
+ /*
+ * If no parent, this is the last pane so window close is imminent and
+ * there is no need to resize anything.
+ */
+ lcparent = lc->parent;
+ if (lcparent == NULL) {
+ layout_free_cell(lc);
+ *lcroot = NULL;
+ return;
+ }
+
+ /* Merge the space into the previous or next cell. */
+ if (lc == TAILQ_FIRST(&lcparent->cells))
+ lcother = TAILQ_NEXT(lc, entry);
+ else
+ lcother = TAILQ_PREV(lc, layout_cells, entry);
+ if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT)
+ layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1);
+ else if (lcother != NULL)
+ layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1);
+
+ /* Remove this from the parent's list. */
+ TAILQ_REMOVE(&lcparent->cells, lc, entry);
+ layout_free_cell(lc);
+
+ /*
+ * If the parent now has one cell, remove the parent from the tree and
+ * replace it by that cell.
+ */
+ lc = TAILQ_FIRST(&lcparent->cells);
+ if (TAILQ_NEXT(lc, entry) == NULL) {
+ TAILQ_REMOVE(&lcparent->cells, lc, entry);
+
+ lc->parent = lcparent->parent;
+ if (lc->parent == NULL) {
+ lc->xoff = 0; lc->yoff = 0;
+ *lcroot = lc;
+ } else
+ TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry);
+
+ layout_free_cell(lcparent);
+ }
+}
+
+void
+layout_init(struct window *w, struct window_pane *wp)
+{
+ struct layout_cell *lc;
+
+ lc = w->layout_root = layout_create_cell(NULL);
+ layout_set_size(lc, w->sx, w->sy, 0, 0);
+ layout_make_leaf(lc, wp);
+ layout_fix_panes(w, NULL);
+}
+
+void
+layout_free(struct window *w)
+{
+ layout_free_cell(w->layout_root);
+}
+
+/* Resize the entire layout after window resize. */
+void
+layout_resize(struct window *w, u_int sx, u_int sy)
+{
+ struct layout_cell *lc = w->layout_root;
+ int xlimit, ylimit, xchange, ychange;
+
+ /*
+ * Adjust horizontally. Do not attempt to reduce the layout lower than
+ * the minimum (more than the amount returned by layout_resize_check).
+ *
+ * This can mean that the window size is smaller than the total layout
+ * size: redrawing this is handled at a higher level, but it does leave
+ * a problem with growing the window size here: if the current size is
+ * < the minimum, growing proportionately by adding to each pane is
+ * wrong as it would keep the layout size larger than the window size.
+ * Instead, spread the difference between the minimum and the new size
+ * out proportionately - this should leave the layout fitting the new
+ * window size.
+ */
+ xchange = sx - lc->sx;
+ xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
+ if (xchange < 0 && xchange < -xlimit)
+ xchange = -xlimit;
+ if (xlimit == 0) {
+ if (sx <= lc->sx) /* lc->sx is minimum possible */
+ xchange = 0;
+ else
+ xchange = sx - lc->sx;
+ }
+ if (xchange != 0)
+ layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, xchange);
+
+ /* Adjust vertically in a similar fashion. */
+ ychange = sy - lc->sy;
+ ylimit = layout_resize_check(w, lc, LAYOUT_TOPBOTTOM);
+ if (ychange < 0 && ychange < -ylimit)
+ ychange = -ylimit;
+ if (ylimit == 0) {
+ if (sy <= lc->sy) /* lc->sy is minimum possible */
+ ychange = 0;
+ else
+ ychange = sy - lc->sy;
+ }
+ if (ychange != 0)
+ layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, ychange);
+
+ /* Fix cell offsets. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+}
+
+/* Resize a pane to an absolute size. */
+void
+layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
+ u_int new_size)
+{
+ struct layout_cell *lc, *lcparent;
+ int change, size;
+
+ lc = wp->layout_cell;
+
+ /* Find next parent of the same type. */
+ lcparent = lc->parent;
+ while (lcparent != NULL && lcparent->type != type) {
+ lc = lcparent;
+ lcparent = lc->parent;
+ }
+ if (lcparent == NULL)
+ return;
+
+ /* Work out the size adjustment. */
+ if (type == LAYOUT_LEFTRIGHT)
+ size = lc->sx;
+ else
+ size = lc->sy;
+ if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
+ change = size - new_size;
+ else
+ change = new_size - size;
+
+ /* Resize the pane. */
+ layout_resize_pane(wp, type, change, 1);
+}
+
+void
+layout_resize_layout(struct window *w, struct layout_cell *lc,
+ enum layout_type type, int change, int opposite)
+{
+ int needed, size;
+
+ /* Grow or shrink the cell. */
+ needed = change;
+ while (needed != 0) {
+ if (change > 0) {
+ size = layout_resize_pane_grow(w, lc, type, needed,
+ opposite);
+ needed -= size;
+ } else {
+ size = layout_resize_pane_shrink(w, lc, type, needed);
+ needed += size;
+ }
+
+ if (size == 0) /* no more change possible */
+ break;
+ }
+
+ /* Fix cell offsets. */
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+ notify_window("window-layout-changed", w);
+}
+
+/* Resize a single pane within the layout. */
+void
+layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
+ int opposite)
+{
+ struct layout_cell *lc, *lcparent;
+
+ lc = wp->layout_cell;
+
+ /* Find next parent of the same type. */
+ lcparent = lc->parent;
+ while (lcparent != NULL && lcparent->type != type) {
+ lc = lcparent;
+ lcparent = lc->parent;
+ }
+ if (lcparent == NULL)
+ return;
+
+ /* If this is the last cell, move back one. */
+ if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
+ lc = TAILQ_PREV(lc, layout_cells, entry);
+
+ layout_resize_layout(wp->window, lc, type, change, opposite);
+}
+
+/* Helper function to grow pane. */
+static int
+layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
+ enum layout_type type, int needed, int opposite)
+{
+ struct layout_cell *lcadd, *lcremove;
+ u_int size = 0;
+
+ /* Growing. Always add to the current cell. */
+ lcadd = lc;
+
+ /* Look towards the tail for a suitable cell for reduction. */
+ lcremove = TAILQ_NEXT(lc, entry);
+ while (lcremove != NULL) {
+ size = layout_resize_check(w, lcremove, type);
+ if (size > 0)
+ break;
+ lcremove = TAILQ_NEXT(lcremove, entry);
+ }
+
+ /* If none found, look towards the head. */
+ if (opposite && lcremove == NULL) {
+ lcremove = TAILQ_PREV(lc, layout_cells, entry);
+ while (lcremove != NULL) {
+ size = layout_resize_check(w, lcremove, type);
+ if (size > 0)
+ break;
+ lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
+ }
+ }
+ if (lcremove == NULL)
+ return (0);
+
+ /* Change the cells. */
+ if (size > (u_int) needed)
+ size = needed;
+ layout_resize_adjust(w, lcadd, type, size);
+ layout_resize_adjust(w, lcremove, type, -size);
+ return (size);
+}
+
+/* Helper function to shrink pane. */
+static int
+layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
+ enum layout_type type, int needed)
+{
+ struct layout_cell *lcadd, *lcremove;
+ u_int size;
+
+ /* Shrinking. Find cell to remove from by walking towards head. */
+ lcremove = lc;
+ do {
+ size = layout_resize_check(w, lcremove, type);
+ if (size != 0)
+ break;
+ lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
+ } while (lcremove != NULL);
+ if (lcremove == NULL)
+ return (0);
+
+ /* And add onto the next cell (from the original cell). */
+ lcadd = TAILQ_NEXT(lc, entry);
+ if (lcadd == NULL)
+ return (0);
+
+ /* Change the cells. */
+ if (size > (u_int) -needed)
+ size = -needed;
+ layout_resize_adjust(w, lcadd, type, size);
+ layout_resize_adjust(w, lcremove, type, -size);
+ return (size);
+}
+
+/* Assign window pane to newly split cell. */
+void
+layout_assign_pane(struct layout_cell *lc, struct window_pane *wp,
+ int do_not_resize)
+{
+ layout_make_leaf(lc, wp);
+ if (do_not_resize)
+ layout_fix_panes(wp->window, wp);
+ else
+ layout_fix_panes(wp->window, NULL);
+}
+
+/* Calculate the new pane size for resized parent. */
+static u_int
+layout_new_pane_size(struct window *w, u_int previous, struct layout_cell *lc,
+ enum layout_type type, u_int size, u_int count_left, u_int size_left)
+{
+ u_int new_size, min, max, available;
+
+ /* If this is the last cell, it can take all of the remaining size. */
+ if (count_left == 1)
+ return (size_left);
+
+ /* How much is available in this parent? */
+ available = layout_resize_check(w, lc, type);
+
+ /*
+ * Work out the minimum size of this cell and the new size
+ * proportionate to the previous size.
+ */
+ min = (PANE_MINIMUM + 1) * (count_left - 1);
+ if (type == LAYOUT_LEFTRIGHT) {
+ if (lc->sx - available > min)
+ min = lc->sx - available;
+ new_size = (lc->sx * size) / previous;
+ } else {
+ if (lc->sy - available > min)
+ min = lc->sy - available;
+ new_size = (lc->sy * size) / previous;
+ }
+
+ /* Check against the maximum and minimum size. */
+ max = size_left - min;
+ if (new_size > max)
+ new_size = max;
+ if (new_size < PANE_MINIMUM)
+ new_size = PANE_MINIMUM;
+ return (new_size);
+}
+
+/* Check if the cell and all its children can be resized to a specific size. */
+static int
+layout_set_size_check(struct window *w, struct layout_cell *lc,
+ enum layout_type type, int size)
+{
+ struct layout_cell *lcchild;
+ u_int new_size, available, previous, count, idx;
+
+ /* Cells with no children must just be bigger than minimum. */
+ if (lc->type == LAYOUT_WINDOWPANE)
+ return (size >= PANE_MINIMUM);
+ available = size;
+
+ /* Count number of children. */
+ count = 0;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry)
+ count++;
+
+ /* Check new size will work for each child. */
+ if (lc->type == type) {
+ if (available < (count * 2) - 1)
+ return (0);
+
+ if (type == LAYOUT_LEFTRIGHT)
+ previous = lc->sx;
+ else
+ previous = lc->sy;
+
+ idx = 0;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ new_size = layout_new_pane_size(w, previous, lcchild,
+ type, size, count - idx, available);
+ if (idx == count - 1) {
+ if (new_size > available)
+ return (0);
+ available -= new_size;
+ } else {
+ if (new_size + 1 > available)
+ return (0);
+ available -= new_size + 1;
+ }
+ if (!layout_set_size_check(w, lcchild, type, new_size))
+ return (0);
+ idx++;
+ }
+ } else {
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (lcchild->type == LAYOUT_WINDOWPANE)
+ continue;
+ if (!layout_set_size_check(w, lcchild, type, size))
+ return (0);
+ }
+ }
+
+ return (1);
+}
+
+/* Resize all child cells to fit within the current cell. */
+static void
+layout_resize_child_cells(struct window *w, struct layout_cell *lc)
+{
+ struct layout_cell *lcchild;
+ u_int previous, available, count, idx;
+
+ if (lc->type == LAYOUT_WINDOWPANE)
+ return;
+
+ /* What is the current size used? */
+ count = 0;
+ previous = 0;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ count++;
+ if (lc->type == LAYOUT_LEFTRIGHT)
+ previous += lcchild->sx;
+ else if (lc->type == LAYOUT_TOPBOTTOM)
+ previous += lcchild->sy;
+ }
+ previous += (count - 1);
+
+ /* And how much is available? */
+ available = 0;
+ if (lc->type == LAYOUT_LEFTRIGHT)
+ available = lc->sx;
+ else if (lc->type == LAYOUT_TOPBOTTOM)
+ available = lc->sy;
+
+ /* Resize children into the new size. */
+ idx = 0;
+ TAILQ_FOREACH(lcchild, &lc->cells, entry) {
+ if (lc->type == LAYOUT_TOPBOTTOM) {
+ lcchild->sx = lc->sx;
+ lcchild->xoff = lc->xoff;
+ } else {
+ lcchild->sx = layout_new_pane_size(w, previous, lcchild,
+ lc->type, lc->sx, count - idx, available);
+ available -= (lcchild->sx + 1);
+ }
+ if (lc->type == LAYOUT_LEFTRIGHT)
+ lcchild->sy = lc->sy;
+ else {
+ lcchild->sy = layout_new_pane_size(w, previous, lcchild,
+ lc->type, lc->sy, count - idx, available);
+ available -= (lcchild->sy + 1);
+ }
+ layout_resize_child_cells(w, lcchild);
+ idx++;
+ }
+}
+
+/*
+ * Split a pane into two. size is a hint, or -1 for default half/half
+ * split. This must be followed by layout_assign_pane before much else happens!
+ */
+struct layout_cell *
+layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
+ int flags)
+{
+ struct layout_cell *lc, *lcparent, *lcnew, *lc1, *lc2;
+ u_int sx, sy, xoff, yoff, size1, size2, minimum;
+ u_int new_size, saved_size, resize_first = 0;
+ int full_size = (flags & SPAWN_FULLSIZE), status;
+
+ /*
+ * If full_size is specified, add a new cell at the top of the window
+ * layout. Otherwise, split the cell for the current pane.
+ */
+ if (full_size)
+ lc = wp->window->layout_root;
+ else
+ lc = wp->layout_cell;
+ status = options_get_number(wp->window->options, "pane-border-status");
+
+ /* Copy the old cell size. */
+ sx = lc->sx;
+ sy = lc->sy;
+ xoff = lc->xoff;
+ yoff = lc->yoff;
+
+ /* Check there is enough space for the two new panes. */
+ switch (type) {
+ case LAYOUT_LEFTRIGHT:
+ if (sx < PANE_MINIMUM * 2 + 1)
+ return (NULL);
+ break;
+ case LAYOUT_TOPBOTTOM:
+ if (layout_add_border(wp->window, lc, status))
+ minimum = PANE_MINIMUM * 2 + 2;
+ else
+ minimum = PANE_MINIMUM * 2 + 1;
+ if (sy < minimum)
+ return (NULL);
+ break;
+ default:
+ fatalx("bad layout type");
+ }
+
+ /*
+ * Calculate new cell sizes. size is the target size or -1 for middle
+ * split, size1 is the size of the top/left and size2 the bottom/right.
+ */
+ if (type == LAYOUT_LEFTRIGHT)
+ saved_size = sx;
+ else
+ saved_size = sy;
+ if (size < 0)
+ size2 = ((saved_size + 1) / 2) - 1;
+ else if (flags & SPAWN_BEFORE)
+ size2 = saved_size - size - 1;
+ else
+ size2 = size;
+ if (size2 < PANE_MINIMUM)
+ size2 = PANE_MINIMUM;
+ else if (size2 > saved_size - 2)
+ size2 = saved_size - 2;
+ size1 = saved_size - 1 - size2;
+
+ /* Which size are we using? */
+ if (flags & SPAWN_BEFORE)
+ new_size = size2;
+ else
+ new_size = size1;
+
+ /* Confirm there is enough space for full size pane. */
+ if (full_size && !layout_set_size_check(wp->window, lc, type, new_size))
+ return (NULL);
+
+ if (lc->parent != NULL && lc->parent->type == type) {
+ /*
+ * If the parent exists and is of the same type as the split,
+ * create a new cell and insert it after this one.
+ */
+ lcparent = lc->parent;
+ lcnew = layout_create_cell(lcparent);
+ if (flags & SPAWN_BEFORE)
+ TAILQ_INSERT_BEFORE(lc, lcnew, entry);
+ else
+ TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry);
+ } else if (full_size && lc->parent == NULL && lc->type == type) {
+ /*
+ * If the new full size pane is the same type as the root
+ * split, insert the new pane under the existing root cell
+ * instead of creating a new root cell. The existing layout
+ * must be resized before inserting the new cell.
+ */
+ if (lc->type == LAYOUT_LEFTRIGHT) {
+ lc->sx = new_size;
+ layout_resize_child_cells(wp->window, lc);
+ lc->sx = saved_size;
+ } else if (lc->type == LAYOUT_TOPBOTTOM) {
+ lc->sy = new_size;
+ layout_resize_child_cells(wp->window, lc);
+ lc->sy = saved_size;
+ }
+ resize_first = 1;
+
+ /* Create the new cell. */
+ lcnew = layout_create_cell(lc);
+ size = saved_size - 1 - new_size;
+ if (lc->type == LAYOUT_LEFTRIGHT)
+ layout_set_size(lcnew, size, sy, 0, 0);
+ else if (lc->type == LAYOUT_TOPBOTTOM)
+ layout_set_size(lcnew, sx, size, 0, 0);
+ if (flags & SPAWN_BEFORE)
+ TAILQ_INSERT_HEAD(&lc->cells, lcnew, entry);
+ else
+ TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
+ } else {
+ /*
+ * Otherwise create a new parent and insert it.
+ */
+
+ /* Create and insert the replacement parent. */
+ lcparent = layout_create_cell(lc->parent);
+ layout_make_node(lcparent, type);
+ layout_set_size(lcparent, sx, sy, xoff, yoff);
+ if (lc->parent == NULL)
+ wp->window->layout_root = lcparent;
+ else
+ TAILQ_REPLACE(&lc->parent->cells, lc, lcparent, entry);
+
+ /* Insert the old cell. */
+ lc->parent = lcparent;
+ TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry);
+
+ /* Create the new child cell. */
+ lcnew = layout_create_cell(lcparent);
+ if (flags & SPAWN_BEFORE)
+ TAILQ_INSERT_HEAD(&lcparent->cells, lcnew, entry);
+ else
+ TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry);
+ }
+ if (flags & SPAWN_BEFORE) {
+ lc1 = lcnew;
+ lc2 = lc;
+ } else {
+ lc1 = lc;
+ lc2 = lcnew;
+ }
+
+ /*
+ * Set new cell sizes. size1 is the size of the top/left and size2 the
+ * bottom/right.
+ */
+ if (!resize_first && type == LAYOUT_LEFTRIGHT) {
+ layout_set_size(lc1, size1, sy, xoff, yoff);
+ layout_set_size(lc2, size2, sy, xoff + lc1->sx + 1, yoff);
+ } else if (!resize_first && type == LAYOUT_TOPBOTTOM) {
+ layout_set_size(lc1, sx, size1, xoff, yoff);
+ layout_set_size(lc2, sx, size2, xoff, yoff + lc1->sy + 1);
+ }
+ if (full_size) {
+ if (!resize_first)
+ layout_resize_child_cells(wp->window, lc);
+ layout_fix_offsets(wp->window);
+ } else
+ layout_make_leaf(lc, wp);
+
+ return (lcnew);
+}
+
+/* Destroy the cell associated with a pane. */
+void
+layout_close_pane(struct window_pane *wp)
+{
+ struct window *w = wp->window;
+
+ /* Remove the cell. */
+ layout_destroy_cell(w, wp->layout_cell, &w->layout_root);
+
+ /* Fix pane offsets and sizes. */
+ if (w->layout_root != NULL) {
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+ }
+ notify_window("window-layout-changed", w);
+}
+
+int
+layout_spread_cell(struct window *w, struct layout_cell *parent)
+{
+ struct layout_cell *lc;
+ u_int number, each, size, this;
+ int change, changed, status;
+
+ number = 0;
+ TAILQ_FOREACH (lc, &parent->cells, entry)
+ number++;
+ if (number <= 1)
+ return (0);
+ status = options_get_number(w->options, "pane-border-status");
+
+ if (parent->type == LAYOUT_LEFTRIGHT)
+ size = parent->sx;
+ else if (parent->type == LAYOUT_TOPBOTTOM) {
+ if (layout_add_border(w, parent, status))
+ size = parent->sy - 1;
+ else
+ size = parent->sy;
+ } else
+ return (0);
+ if (size < number - 1)
+ return (0);
+ each = (size - (number - 1)) / number;
+ if (each == 0)
+ return (0);
+
+ changed = 0;
+ TAILQ_FOREACH (lc, &parent->cells, entry) {
+ if (TAILQ_NEXT(lc, entry) == NULL)
+ each = size - ((each + 1) * (number - 1));
+ change = 0;
+ if (parent->type == LAYOUT_LEFTRIGHT) {
+ change = each - (int)lc->sx;
+ layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change);
+ } else if (parent->type == LAYOUT_TOPBOTTOM) {
+ if (layout_add_border(w, lc, status))
+ this = each + 1;
+ else
+ this = each;
+ change = this - (int)lc->sy;
+ layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, change);
+ }
+ if (change != 0)
+ changed = 1;
+ }
+ return (changed);
+}
+
+void
+layout_spread_out(struct window_pane *wp)
+{
+ struct layout_cell *parent;
+ struct window *w = wp->window;
+
+ parent = wp->layout_cell->parent;
+ if (parent == NULL)
+ return;
+
+ do {
+ if (layout_spread_cell(w, parent)) {
+ layout_fix_offsets(w);
+ layout_fix_panes(w, NULL);
+ break;
+ }
+ } while ((parent = parent->parent) != NULL);
+}
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..0e0d1d1
--- /dev/null
+++ b/log.c
@@ -0,0 +1,166 @@
+/* $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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static FILE *log_file;
+static int log_level;
+
+/* Log callback for libevent. */
+static void
+log_event_cb(__unused int severity, const char *msg)
+{
+ log_debug("%s", msg);
+}
+
+/* Increment log level. */
+void
+log_add_level(void)
+{
+ log_level++;
+}
+
+/* Get log level. */
+int
+log_get_level(void)
+{
+ return (log_level);
+}
+
+/* Open logging to file. */
+void
+log_open(const char *name)
+{
+ char *path;
+
+ if (log_level == 0)
+ return;
+ log_close();
+
+ xasprintf(&path, "tmux-%s-%ld.log", name, (long)getpid());
+ log_file = fopen(path, "a");
+ free(path);
+ if (log_file == NULL)
+ return;
+
+ setvbuf(log_file, NULL, _IOLBF, 0);
+ event_set_log_callback(log_event_cb);
+}
+
+/* Toggle logging. */
+void
+log_toggle(const char *name)
+{
+ if (log_level == 0) {
+ log_level = 1;
+ log_open(name);
+ log_debug("log opened");
+ } else {
+ log_debug("log closed");
+ log_level = 0;
+ log_close();
+ }
+}
+
+/* Close logging. */
+void
+log_close(void)
+{
+ if (log_file != NULL)
+ fclose(log_file);
+ log_file = NULL;
+
+ event_set_log_callback(NULL);
+}
+
+/* Write a log message. */
+static void printflike(1, 0)
+log_vwrite(const char *msg, va_list ap, const char *prefix)
+{
+ char *s, *out;
+ struct timeval tv;
+
+ if (log_file == NULL)
+ return;
+
+ if (vasprintf(&s, msg, ap) == -1)
+ return;
+ if (stravis(&out, s, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL) == -1) {
+ free(s);
+ return;
+ }
+ free(s);
+
+ gettimeofday(&tv, NULL);
+ if (fprintf(log_file, "%lld.%06d %s%s\n", (long long)tv.tv_sec,
+ (int)tv.tv_usec, prefix, out) != -1)
+ fflush(log_file);
+ free(out);
+}
+
+/* Log a debug message. */
+void
+log_debug(const char *msg, ...)
+{
+ va_list ap;
+
+ if (log_file == NULL)
+ return;
+
+ va_start(ap, msg);
+ log_vwrite(msg, ap, "");
+ va_end(ap);
+}
+
+/* Log a critical error with error string and die. */
+__dead void
+fatal(const char *msg, ...)
+{
+ char tmp[256];
+ va_list ap;
+
+ if (snprintf(tmp, sizeof tmp, "fatal: %s: ", strerror(errno)) < 0)
+ exit(1);
+
+ va_start(ap, msg);
+ log_vwrite(msg, ap, tmp);
+ va_end(ap);
+
+ exit(1);
+}
+
+/* Log a critical error and die. */
+__dead void
+fatalx(const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ log_vwrite(msg, ap, "fatal: ");
+ va_end(ap);
+
+ exit(1);
+}
diff --git a/mdoc2man.awk b/mdoc2man.awk
new file mode 100644
index 0000000..80e8d5f
--- /dev/null
+++ b/mdoc2man.awk
@@ -0,0 +1,370 @@
+#!/usr/bin/awk
+#
+# $Id: mdoc2man.awk,v 1.9 2009/10/24 00:52:42 dtucker Exp $
+#
+# Version history:
+# v4+ Adapted for OpenSSH Portable (see cvs Id and history)
+# v3, I put the program under a proper license
+# Dan Nelson <dnelson@allantgroup.com> added .An, .Aq and fixed a typo
+# v2, fixed to work on GNU awk --posix and MacOS X
+# v1, first attempt, didn't work on MacOS X
+#
+# Copyright (c) 2003 Peter Stuge <stuge-mdoc2man@cdy.org>
+#
+# 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 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.
+
+
+BEGIN {
+ optlist=0
+ oldoptlist=0
+ nospace=0
+ synopsis=0
+ reference=0
+ block=0
+ ext=0
+ extopt=0
+ literal=0
+ prenl=0
+ breakw=0
+ line=""
+}
+
+function wtail() {
+ retval=""
+ while(w<nwords) {
+ if(length(retval))
+ retval=retval OFS
+ retval=retval words[++w]
+ }
+ return retval
+}
+
+function add(str) {
+ for(;prenl;prenl--)
+ line=line "\n"
+ line=line str
+}
+
+! /^\./ {
+ for(;prenl;prenl--)
+ print ""
+ print
+ if(literal)
+ print ".br"
+ next
+}
+
+/^\.\\"/ { next }
+
+{
+ option=0
+ parens=0
+ angles=0
+ sub("^\\.","")
+ nwords=split($0,words)
+ for(w=1;w<=nwords;w++) {
+ skip=0
+ if(match(words[w],"^Li|Pf$")) {
+ skip=1
+ } else if(match(words[w],"^Xo$")) {
+ skip=1
+ ext=1
+ if(length(line)&&!(match(line," $")||prenl))
+ add(OFS)
+ } else if(match(words[w],"^Xc$")) {
+ skip=1
+ ext=0
+ if(!extopt)
+ prenl++
+ w=nwords
+ } else if(match(words[w],"^Bd$")) {
+ skip=1
+ if(match(words[w+1],"-literal")) {
+ literal=1
+ prenl++
+ w=nwords
+ }
+ } else if(match(words[w],"^Ed$")) {
+ skip=1
+ literal=0
+ } else if(match(words[w],"^Ns$")) {
+ skip=1
+ if(!nospace)
+ nospace=1
+ sub(" $","",line)
+ } else if(match(words[w],"^No$")) {
+ skip=1
+ sub(" $","",line)
+ add(words[++w])
+ } else if(match(words[w],"^Dq$")) {
+ skip=1
+ add("``")
+ add(words[++w])
+ while(w<nwords&&!match(words[w+1],"^[\\.,]"))
+ add(OFS words[++w])
+ add("''")
+ if(!nospace&&match(words[w+1],"^[\\.,]"))
+ nospace=1
+ } else if(match(words[w],"^Sq|Ql$")) {
+ skip=1
+ add("`" words[++w] "'")
+ if(!nospace&&match(words[w+1],"^[\\.,]"))
+ nospace=1
+ } else if(match(words[w],"^Oo$")) {
+ skip=1
+ extopt=1
+ if(!nospace)
+ nospace=1
+ add("[")
+ } else if(match(words[w],"^Oc$")) {
+ skip=1
+ extopt=0
+ add("]")
+ }
+ if(!skip) {
+ if(!nospace&&length(line)&&!(match(line," $")||prenl))
+ add(OFS)
+ if(nospace==1)
+ nospace=0
+ }
+ if(match(words[w],"^Dd$")) {
+ if(match(words[w+1],"^\\$Mdocdate:")) {
+ w++;
+ if(match(words[w+4],"^\\$$")) {
+ words[w+4] = ""
+ }
+ }
+ date=wtail()
+ next
+ } else if(match(words[w],"^Dt$")) {
+ id=wtail()
+ next
+ } else if(match(words[w],"^Ux$")) {
+ add("UNIX")
+ skip=1
+ } else if(match(words[w],"^Ox$")) {
+ add("OpenBSD")
+ skip=1
+ } else if(match(words[w],"^Os$")) {
+ add(".TH " id " \"" date "\" \"" wtail() "\"")
+ } else if(match(words[w],"^Sh$")) {
+ add(".SH")
+ synopsis=match(words[w+1],"SYNOPSIS")
+ } else if(match(words[w],"^Xr$")) {
+ add("\\fB" words[++w] "\\fP(" words[++w] ")" words[++w])
+ } else if(match(words[w],"^Rs$")) {
+ split("",refauthors)
+ nrefauthors=0
+ reftitle=""
+ refissue=""
+ refdate=""
+ refopt=""
+ refreport=""
+ reference=1
+ next
+ } else if(match(words[w],"^Re$")) {
+ prenl++
+ for(i=nrefauthors-1;i>0;i--) {
+ add(refauthors[i])
+ if(i>1)
+ add(", ")
+ }
+ if(nrefauthors>1)
+ add(" and ")
+ if(nrefauthors>0)
+ add(refauthors[0] ", ")
+ add("\\fI" reftitle "\\fP")
+ if(length(refissue))
+ add(", " refissue)
+ if(length(refreport)) {
+ add(", " refreport)
+ }
+ if(length(refdate))
+ add(", " refdate)
+ if(length(refopt))
+ add(", " refopt)
+ add(".")
+ reference=0
+ } else if(reference) {
+ if(match(words[w],"^%A$")) { refauthors[nrefauthors++]=wtail() }
+ if(match(words[w],"^%T$")) {
+ reftitle=wtail()
+ sub("^\"","",reftitle)
+ sub("\"$","",reftitle)
+ }
+ if(match(words[w],"^%N$")) { refissue=wtail() }
+ if(match(words[w],"^%D$")) { refdate=wtail() }
+ if(match(words[w],"^%O$")) { refopt=wtail() }
+ if(match(words[w],"^%R$")) { refreport=wtail() }
+ } else if(match(words[w],"^Nm$")) {
+ if(synopsis) {
+ add(".br")
+ prenl++
+ }
+ n=words[++w]
+ if(!length(name))
+ name=n
+ if(!length(n))
+ n=name
+ add("\\fB" n "\\fP")
+ if(!nospace&&match(words[w+1],"^[\\.,]"))
+ nospace=1
+ } else if(match(words[w],"^Nd$")) {
+ add("\\- " wtail())
+ } else if(match(words[w],"^Fl$")) {
+ add("\\fB\\-" words[++w] "\\fP")
+ if(!nospace&&match(words[w+1],"^[\\.,]"))
+ nospace=1
+ } else if(match(words[w],"^Ar$")) {
+ add("\\fI")
+ if(w==nwords)
+ add("file ...\\fP")
+ else {
+ add(words[++w] "\\fP")
+ while(match(words[w+1],"^\\|$"))
+ add(OFS words[++w] " \\fI" words[++w] "\\fP")
+ }
+ if(!nospace&&match(words[w+1],"^[\\.,]"))
+ nospace=1
+ } else if(match(words[w],"^Cm$")) {
+ add("\\fB" words[++w] "\\fP")
+ while(w<nwords&&match(words[w+1],"^[\\.,:;)]"))
+ add(words[++w])
+ } else if(match(words[w],"^Op$")) {
+ option=1
+ if(!nospace)
+ nospace=1
+ add("[")
+ } else if(match(words[w],"^Pp$")) {
+ prenl++
+ } else if(match(words[w],"^An$")) {
+ prenl++
+ } else if(match(words[w],"^Ss$")) {
+ add(".SS")
+ } else if(match(words[w],"^Pa$")&&!option) {
+ add("\\fI")
+ w++
+ if(match(words[w],"^\\."))
+ add("\\&")
+ add(words[w] "\\fP")
+ while(w<nwords&&match(words[w+1],"^[\\.,:;)]"))
+ add(words[++w])
+ } else if(match(words[w],"^Dv$")) {
+ add(".BR")
+ } else if(match(words[w],"^Em|Ev$")) {
+ add(".IR")
+ } else if(match(words[w],"^Pq$")) {
+ add("(")
+ nospace=1
+ parens=1
+ } else if(match(words[w],"^Aq$")) {
+ add("<")
+ nospace=1
+ angles=1
+ } else if(match(words[w],"^S[xy]$")) {
+ add(".B " wtail())
+ } else if(match(words[w],"^Ic$")) {
+ plain=1
+ add("\\fB")
+ while(w<nwords) {
+ w++
+ if(match(words[w],"^Op$")) {
+ w++
+ add("[")
+ words[nwords]=words[nwords] "]"
+ }
+ if(match(words[w],"^Ar$")) {
+ add("\\fI" words[++w] "\\fP")
+ } else if(match(words[w],"^[\\.,]")) {
+ sub(" $","",line)
+ if(plain) {
+ add("\\fP")
+ plain=0
+ }
+ add(words[w])
+ } else {
+ if(!plain) {
+ add("\\fB")
+ plain=1
+ }
+ add(words[w])
+ }
+ if(!nospace)
+ add(OFS)
+ }
+ sub(" $","",line)
+ if(plain)
+ add("\\fP")
+ } else if(match(words[w],"^Bl$")) {
+ oldoptlist=optlist
+ if(match(words[w+1],"-bullet"))
+ optlist=1
+ else if(match(words[w+1],"-enum")) {
+ optlist=2
+ enum=0
+ } else if(match(words[w+1],"-tag"))
+ optlist=3
+ else if(match(words[w+1],"-item"))
+ optlist=4
+ else if(match(words[w+1],"-bullet"))
+ optlist=1
+ w=nwords
+ } else if(match(words[w],"^El$")) {
+ optlist=oldoptlist
+ } else if(match(words[w],"^Bk$")) {
+ if(match(words[w+1],"-words")) {
+ w++
+ breakw=1
+ }
+ } else if(match(words[w],"^Ek$")) {
+ breakw=0
+ } else if(match(words[w],"^It$")&&optlist) {
+ if(optlist==1)
+ add(".IP \\(bu")
+ else if(optlist==2)
+ add(".IP " ++enum ".")
+ else if(optlist==3) {
+ add(".TP")
+ prenl++
+ if(match(words[w+1],"^Pa$|^Ev$")) {
+ add(".B")
+ w++
+ }
+ } else if(optlist==4)
+ add(".IP")
+ } else if(match(words[w],"^Sm$")) {
+ if(match(words[w+1],"off"))
+ nospace=2
+ else if(match(words[w+1],"on"))
+ nospace=0
+ w++
+ } else if(!skip) {
+ add(words[w])
+ }
+ }
+ if(match(line,"^\\.[^a-zA-Z]"))
+ sub("^\\.","",line)
+ if(parens)
+ add(")")
+ if(angles)
+ add(">")
+ if(option)
+ add("]")
+ if(ext&&!extopt&&!match(line," $"))
+ add(OFS)
+ if(!ext&&!extopt&&length(line)) {
+ print line
+ prenl=0
+ line=""
+ }
+}
diff --git a/menu.c b/menu.c
new file mode 100644
index 0000000..16120be
--- /dev/null
+++ b/menu.c
@@ -0,0 +1,449 @@
+/* $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"
+
+struct menu_data {
+ struct cmdq_item *item;
+ int flags;
+
+ struct cmd_find_state fs;
+ struct screen s;
+
+ u_int px;
+ u_int py;
+
+ struct menu *menu;
+ int choice;
+
+ menu_choice_cb cb;
+ void *data;
+};
+
+void
+menu_add_items(struct menu *menu, const struct menu_item *items,
+ struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
+{
+ const struct menu_item *loop;
+
+ for (loop = items; loop->name != NULL; loop++)
+ menu_add_item(menu, loop, qitem, c, fs);
+}
+
+void
+menu_add_item(struct menu *menu, const struct menu_item *item,
+ struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs)
+{
+ struct menu_item *new_item;
+ const char *key = NULL, *cmd, *suffix = "";
+ char *s, *trimmed, *name;
+ u_int width, max_width;
+ int line;
+ size_t keylen, slen;
+
+ line = (item == NULL || item->name == NULL || *item->name == '\0');
+ if (line && menu->count == 0)
+ return;
+
+ menu->items = xreallocarray(menu->items, menu->count + 1,
+ sizeof *menu->items);
+ new_item = &menu->items[menu->count++];
+ memset(new_item, 0, sizeof *new_item);
+
+ if (line)
+ return;
+
+ if (fs != NULL)
+ s = format_single_from_state(qitem, item->name, c, fs);
+ else
+ s = format_single(qitem, item->name, c, NULL, NULL, NULL);
+ if (*s == '\0') { /* no item if empty after format expanded */
+ menu->count--;
+ return;
+ }
+ max_width = c->tty.sx - 4;
+
+ slen = strlen(s);
+ if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) {
+ key = key_string_lookup_key(item->key, 0);
+ keylen = strlen(key) + 3; /* 3 = space and two brackets */
+
+ /*
+ * Add the key if it is shorter than a quarter of the available
+ * space or there is space for the entire item text and the
+ * key.
+ */
+ if (keylen <= max_width / 4)
+ max_width -= keylen;
+ else if (keylen >= max_width || slen >= max_width - keylen)
+ key = NULL;
+ }
+
+ if (slen > max_width) {
+ max_width--;
+ suffix = ">";
+ }
+ trimmed = format_trim_right(s, max_width);
+ if (key != NULL) {
+ xasprintf(&name, "%s%s#[default] #[align=right](%s)",
+ trimmed, suffix, key);
+ } else
+ xasprintf(&name, "%s%s", trimmed, suffix);
+ free(trimmed);
+
+ new_item->name = name;
+ free(s);
+
+ cmd = item->command;
+ if (cmd != NULL) {
+ if (fs != NULL)
+ s = format_single_from_state(qitem, cmd, c, fs);
+ else
+ s = format_single(qitem, cmd, c, NULL, NULL, NULL);
+ } else
+ s = NULL;
+ new_item->command = s;
+ new_item->key = item->key;
+
+ width = format_width(new_item->name);
+ if (*new_item->name == '-')
+ width--;
+ if (width > menu->width)
+ menu->width = width;
+}
+
+struct menu *
+menu_create(const char *title)
+{
+ struct menu *menu;
+
+ menu = xcalloc(1, sizeof *menu);
+ menu->title = xstrdup(title);
+ menu->width = format_width(title);
+
+ return (menu);
+}
+
+void
+menu_free(struct menu *menu)
+{
+ u_int i;
+
+ for (i = 0; i < menu->count; i++) {
+ free((void *)menu->items[i].name);
+ free((void *)menu->items[i].command);
+ }
+ free(menu->items);
+
+ free((void *)menu->title);
+ free(menu);
+}
+
+struct screen *
+menu_mode_cb(__unused struct client *c, void *data, __unused u_int *cx,
+ __unused u_int *cy)
+{
+ struct menu_data *md = data;
+
+ return (&md->s);
+}
+
+/* Return parts of the input range which are not obstructed by the menu. */
+void
+menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py,
+ u_int nx, struct overlay_ranges *r)
+{
+ struct menu_data *md = data;
+ struct menu *menu = md->menu;
+
+ server_client_overlay_range(md->px, md->py, menu->width + 4,
+ menu->count + 2, px, py, nx, r);
+}
+
+void
+menu_draw_cb(struct client *c, void *data,
+ __unused struct screen_redraw_ctx *rctx)
+{
+ struct menu_data *md = data;
+ struct tty *tty = &c->tty;
+ struct screen *s = &md->s;
+ 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);
+ screen_write_stop(&ctx);
+
+ for (i = 0; i < screen_size_y(&md->s); i++) {
+ tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i,
+ &grid_default_cell, NULL);
+ }
+}
+
+void
+menu_free_cb(__unused struct client *c, void *data)
+{
+ struct menu_data *md = data;
+
+ if (md->item != NULL)
+ cmdq_continue(md->item);
+
+ if (md->cb != NULL)
+ md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data);
+
+ screen_free(&md->s);
+ menu_free(md->menu);
+ free(md);
+}
+
+int
+menu_key_cb(struct client *c, void *data, struct key_event *event)
+{
+ struct menu_data *md = data;
+ struct menu *menu = md->menu;
+ struct mouse_event *m = &event->m;
+ u_int i;
+ int count = menu->count, old = md->choice;
+ const char *name = NULL;
+ const struct menu_item *item;
+ struct cmdq_state *state;
+ enum cmd_parse_status status;
+ char *error;
+
+ if (KEYC_IS_MOUSE(event->key)) {
+ if (md->flags & MENU_NOMOUSE) {
+ if (MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1)
+ return (1);
+ return (0);
+ }
+ if (m->x < md->px ||
+ m->x > md->px + 4 + menu->width ||
+ m->y < md->py + 1 ||
+ m->y > md->py + 1 + count - 1) {
+ if (~md->flags & MENU_STAYOPEN) {
+ if (MOUSE_RELEASE(m->b))
+ return (1);
+ } else {
+ if (!MOUSE_RELEASE(m->b) &&
+ !MOUSE_WHEEL(m->b) &&
+ !MOUSE_DRAG(m->b))
+ return (1);
+ }
+ if (md->choice != -1) {
+ md->choice = -1;
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ }
+ return (0);
+ }
+ if (~md->flags & MENU_STAYOPEN) {
+ if (MOUSE_RELEASE(m->b))
+ goto chosen;
+ } else {
+ if (!MOUSE_WHEEL(m->b) && !MOUSE_DRAG(m->b))
+ goto chosen;
+ }
+ md->choice = m->y - (md->py + 1);
+ if (md->choice != old)
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ return (0);
+ }
+ for (i = 0; i < (u_int)count; i++) {
+ name = menu->items[i].name;
+ if (name == NULL || *name == '-')
+ continue;
+ if (event->key == menu->items[i].key) {
+ md->choice = i;
+ goto chosen;
+ }
+ }
+ switch (event->key & ~KEYC_MASK_FLAGS) {
+ case KEYC_UP:
+ case 'k':
+ if (old == -1)
+ old = 0;
+ do {
+ if (md->choice == -1 || md->choice == 0)
+ md->choice = count - 1;
+ else
+ md->choice--;
+ name = menu->items[md->choice].name;
+ } while ((name == NULL || *name == '-') && md->choice != old);
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ return (0);
+ case KEYC_BSPACE:
+ if (~md->flags & MENU_TAB)
+ break;
+ return (1);
+ case '\011': /* Tab */
+ if (~md->flags & MENU_TAB)
+ break;
+ if (md->choice == count - 1)
+ return (1);
+ /* FALLTHROUGH */
+ case KEYC_DOWN:
+ case 'j':
+ if (old == -1)
+ old = 0;
+ do {
+ if (md->choice == -1 || md->choice == count - 1)
+ md->choice = 0;
+ else
+ md->choice++;
+ name = menu->items[md->choice].name;
+ } 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
+ md->choice = 0;
+ while (md->choice != count && (name == NULL || *name == '-'))
+ md->choice++;
+ if (md->choice == count)
+ md->choice = -1;
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ break;
+ case 'G':
+ case KEYC_NPAGE:
+ if (md->choice > count - 6)
+ md->choice = count - 1;
+ else
+ md->choice += 5;
+ while (md->choice != -1 && (name == NULL || *name == '-'))
+ md->choice--;
+ c->flags |= CLIENT_REDRAWOVERLAY;
+ break;
+ case '\006': /* C-f */
+ break;
+ case '\r':
+ goto chosen;
+ case '\033': /* Escape */
+ case '\003': /* C-c */
+ case '\007': /* C-g */
+ case 'q':
+ return (1);
+ }
+ return (0);
+
+chosen:
+ if (md->choice == -1)
+ return (1);
+ item = &menu->items[md->choice];
+ if (item->name == NULL || *item->name == '-') {
+ if (md->flags & MENU_STAYOPEN)
+ return (0);
+ return (1);
+ }
+ if (md->cb != NULL) {
+ md->cb(md->menu, md->choice, item->key, md->data);
+ md->cb = NULL;
+ return (1);
+ }
+
+ if (md->item != NULL)
+ event = cmdq_get_event(md->item);
+ else
+ event = NULL;
+ state = cmdq_new_state(&md->fs, event, 0);
+
+ status = cmd_parse_and_append(item->command, NULL, c, state, &error);
+ if (status == CMD_PARSE_ERROR) {
+ cmdq_append(c, cmdq_get_error(error));
+ free(error);
+ }
+ cmdq_free_state(state);
+
+ return (1);
+}
+
+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,
+ void *data)
+{
+ struct menu_data *md;
+ u_int i;
+ const char *name;
+
+ if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2)
+ return (NULL);
+ if (px + menu->width + 4 > c->tty.sx)
+ px = c->tty.sx - menu->width - 4;
+ if (py + menu->count + 2 > c->tty.sy)
+ py = c->tty.sy - menu->count - 2;
+
+ md = xcalloc(1, sizeof *md);
+ md->item = item;
+ md->flags = flags;
+
+ if (fs != NULL)
+ cmd_find_copy_state(&md->fs, fs);
+ screen_init(&md->s, menu->width + 4, menu->count + 2, 0);
+ if (~md->flags & MENU_NOMOUSE)
+ md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON);
+ md->s.mode &= ~MODE_CURSOR;
+
+ md->px = px;
+ md->py = py;
+
+ md->menu = menu;
+ if (md->flags & MENU_NOMOUSE) {
+ for (i = 0; i < menu->count; i++) {
+ name = menu->items[i].name;
+ if (name != NULL && *name != '-')
+ break;
+ }
+ if (i != menu->count)
+ md->choice = i;
+ else
+ md->choice = -1;
+ } else
+ md->choice = -1;
+
+ md->cb = cb;
+ md->data = data;
+ return (md);
+}
+
+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,
+ void *data)
+{
+ struct menu_data *md;
+
+ md = menu_prepare(menu, flags, item, px, py, c, fs, cb, data);
+ if (md == NULL)
+ return (-1);
+ server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb,
+ menu_key_cb, menu_free_cb, NULL, md);
+ return (0);
+}
diff --git a/mode-tree.c b/mode-tree.c
new file mode 100644
index 0000000..c007e27
--- /dev/null
+++ b/mode-tree.c
@@ -0,0 +1,1208 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2017 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+struct mode_tree_item;
+TAILQ_HEAD(mode_tree_list, mode_tree_item);
+
+struct mode_tree_data {
+ int dead;
+ u_int references;
+ int zoomed;
+
+ struct window_pane *wp;
+ void *modedata;
+ const struct menu_item *menu;
+
+ const char **sort_list;
+ u_int sort_size;
+ struct mode_tree_sort_criteria sort_crit;
+
+ mode_tree_build_cb buildcb;
+ mode_tree_draw_cb drawcb;
+ mode_tree_search_cb searchcb;
+ mode_tree_menu_cb menucb;
+ mode_tree_height_cb heightcb;
+ mode_tree_key_cb keycb;
+
+ struct mode_tree_list children;
+ struct mode_tree_list saved;
+
+ struct mode_tree_line *line_list;
+ u_int line_size;
+
+ u_int depth;
+
+ u_int width;
+ u_int height;
+
+ u_int offset;
+ u_int current;
+
+ struct screen screen;
+
+ int preview;
+ char *search;
+ char *filter;
+ int no_matches;
+};
+
+struct mode_tree_item {
+ struct mode_tree_item *parent;
+ void *itemdata;
+ u_int line;
+
+ key_code key;
+ const char *keystr;
+ size_t keylen;
+
+ uint64_t tag;
+ const char *name;
+ const char *text;
+
+ int expanded;
+ int tagged;
+
+ int draw_as_parent;
+ int no_tag;
+
+ struct mode_tree_list children;
+ TAILQ_ENTRY(mode_tree_item) entry;
+};
+
+struct mode_tree_line {
+ struct mode_tree_item *item;
+ u_int depth;
+ int last;
+ int flat;
+};
+
+struct mode_tree_menu {
+ struct mode_tree_data *data;
+ struct client *c;
+ u_int line;
+};
+
+static void mode_tree_free_items(struct mode_tree_list *);
+
+static const struct menu_item mode_tree_menu_items[] = {
+ { "Scroll Left", '<', NULL },
+ { "Scroll Right", '>', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Cancel", 'q', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+static struct mode_tree_item *
+mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag)
+{
+ struct mode_tree_item *mti, *child;
+
+ TAILQ_FOREACH(mti, mtl, entry) {
+ if (mti->tag == tag)
+ return (mti);
+ child = mode_tree_find_item(&mti->children, tag);
+ if (child != NULL)
+ return (child);
+ }
+ return (NULL);
+}
+
+static void
+mode_tree_free_item(struct mode_tree_item *mti)
+{
+ mode_tree_free_items(&mti->children);
+
+ free((void *)mti->name);
+ free((void *)mti->text);
+ free((void *)mti->keystr);
+
+ free(mti);
+}
+
+static void
+mode_tree_free_items(struct mode_tree_list *mtl)
+{
+ struct mode_tree_item *mti, *mti1;
+
+ TAILQ_FOREACH_SAFE(mti, mtl, entry, mti1) {
+ TAILQ_REMOVE(mtl, mti, entry);
+ mode_tree_free_item(mti);
+ }
+}
+
+static void
+mode_tree_check_selected(struct mode_tree_data *mtd)
+{
+ /*
+ * If the current line would now be off screen reset the offset to the
+ * last visible line.
+ */
+ if (mtd->current > mtd->height - 1)
+ mtd->offset = mtd->current - mtd->height + 1;
+}
+
+static void
+mode_tree_clear_lines(struct mode_tree_data *mtd)
+{
+ free(mtd->line_list);
+ mtd->line_list = NULL;
+ mtd->line_size = 0;
+}
+
+static void
+mode_tree_build_lines(struct mode_tree_data *mtd,
+ struct mode_tree_list *mtl, u_int depth)
+{
+ struct mode_tree_item *mti;
+ struct mode_tree_line *line;
+ u_int i;
+ int flat = 1;
+
+ mtd->depth = depth;
+ TAILQ_FOREACH(mti, mtl, entry) {
+ mtd->line_list = xreallocarray(mtd->line_list,
+ mtd->line_size + 1, sizeof *mtd->line_list);
+
+ line = &mtd->line_list[mtd->line_size++];
+ line->item = mti;
+ line->depth = depth;
+ line->last = (mti == TAILQ_LAST(mtl, mode_tree_list));
+
+ mti->line = (mtd->line_size - 1);
+ if (!TAILQ_EMPTY(&mti->children))
+ flat = 0;
+ if (mti->expanded)
+ mode_tree_build_lines(mtd, &mti->children, depth + 1);
+
+ if (mtd->keycb != NULL) {
+ mti->key = mtd->keycb(mtd->modedata, mti->itemdata,
+ mti->line);
+ if (mti->key == KEYC_UNKNOWN)
+ mti->key = KEYC_NONE;
+ } else if (mti->line < 10)
+ mti->key = '0' + mti->line;
+ else if (mti->line < 36)
+ mti->key = KEYC_META|('a' + mti->line - 10);
+ else
+ mti->key = KEYC_NONE;
+ if (mti->key != KEYC_NONE) {
+ mti->keystr = xstrdup(key_string_lookup_key(mti->key,
+ 0));
+ mti->keylen = strlen(mti->keystr);
+ } else {
+ mti->keystr = NULL;
+ mti->keylen = 0;
+ }
+ }
+ TAILQ_FOREACH(mti, mtl, entry) {
+ for (i = 0; i < mtd->line_size; i++) {
+ line = &mtd->line_list[i];
+ if (line->item == mti)
+ line->flat = flat;
+ }
+ }
+}
+
+static void
+mode_tree_clear_tagged(struct mode_tree_list *mtl)
+{
+ struct mode_tree_item *mti;
+
+ TAILQ_FOREACH(mti, mtl, entry) {
+ mti->tagged = 0;
+ mode_tree_clear_tagged(&mti->children);
+ }
+}
+
+void
+mode_tree_up(struct mode_tree_data *mtd, int wrap)
+{
+ if (mtd->current == 0) {
+ if (wrap) {
+ mtd->current = mtd->line_size - 1;
+ if (mtd->line_size >= mtd->height)
+ mtd->offset = mtd->line_size - mtd->height;
+ }
+ } else {
+ mtd->current--;
+ if (mtd->current < mtd->offset)
+ mtd->offset--;
+ }
+}
+
+void
+mode_tree_down(struct mode_tree_data *mtd, int wrap)
+{
+ if (mtd->current == mtd->line_size - 1) {
+ if (wrap) {
+ mtd->current = 0;
+ mtd->offset = 0;
+ }
+ } else {
+ mtd->current++;
+ if (mtd->current > mtd->offset + mtd->height - 1)
+ mtd->offset++;
+ }
+}
+
+void *
+mode_tree_get_current(struct mode_tree_data *mtd)
+{
+ return (mtd->line_list[mtd->current].item->itemdata);
+}
+
+const char *
+mode_tree_get_current_name(struct mode_tree_data *mtd)
+{
+ return (mtd->line_list[mtd->current].item->name);
+}
+
+void
+mode_tree_expand_current(struct mode_tree_data *mtd)
+{
+ if (!mtd->line_list[mtd->current].item->expanded) {
+ mtd->line_list[mtd->current].item->expanded = 1;
+ mode_tree_build(mtd);
+ }
+}
+
+void
+mode_tree_collapse_current(struct mode_tree_data *mtd)
+{
+ if (mtd->line_list[mtd->current].item->expanded) {
+ mtd->line_list[mtd->current].item->expanded = 0;
+ mode_tree_build(mtd);
+ }
+}
+
+static int
+mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found)
+{
+ u_int i;
+
+ for (i = 0; i < mtd->line_size; i++) {
+ if (mtd->line_list[i].item->tag == tag)
+ break;
+ }
+ if (i != mtd->line_size) {
+ *found = i;
+ return (1);
+ }
+ return (0);
+}
+
+void
+mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag)
+{
+ u_int found;
+
+ if (!mode_tree_get_tag(mtd, tag, &found))
+ return;
+ if (!mtd->line_list[found].item->expanded) {
+ mtd->line_list[found].item->expanded = 1;
+ mode_tree_build(mtd);
+ }
+}
+
+int
+mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag)
+{
+ u_int found;
+
+ if (mode_tree_get_tag(mtd, tag, &found)) {
+ mtd->current = found;
+ if (mtd->current > mtd->height - 1)
+ mtd->offset = mtd->current - mtd->height + 1;
+ else
+ mtd->offset = 0;
+ return (1);
+ }
+ mtd->current = 0;
+ mtd->offset = 0;
+ return (0);
+}
+
+u_int
+mode_tree_count_tagged(struct mode_tree_data *mtd)
+{
+ struct mode_tree_item *mti;
+ u_int i, tagged;
+
+ tagged = 0;
+ for (i = 0; i < mtd->line_size; i++) {
+ mti = mtd->line_list[i].item;
+ if (mti->tagged)
+ tagged++;
+ }
+ return (tagged);
+}
+
+void
+mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb,
+ struct client *c, key_code key, int current)
+{
+ struct mode_tree_item *mti;
+ u_int i;
+ int fired;
+
+ fired = 0;
+ for (i = 0; i < mtd->line_size; i++) {
+ mti = mtd->line_list[i].item;
+ if (mti->tagged) {
+ fired = 1;
+ cb(mtd->modedata, mti->itemdata, c, key);
+ }
+ }
+ if (!fired && current) {
+ mti = mtd->line_list[mtd->current].item;
+ cb(mtd->modedata, mti->itemdata, c, key);
+ }
+}
+
+struct mode_tree_data *
+mode_tree_start(struct window_pane *wp, struct args *args,
+ mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
+ mode_tree_search_cb searchcb, mode_tree_menu_cb menucb,
+ mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata,
+ const struct menu_item *menu, const char **sort_list, u_int sort_size,
+ struct screen **s)
+{
+ struct mode_tree_data *mtd;
+ const char *sort;
+ u_int i;
+
+ mtd = xcalloc(1, sizeof *mtd);
+ mtd->references = 1;
+
+ mtd->wp = wp;
+ mtd->modedata = modedata;
+ mtd->menu = menu;
+
+ mtd->sort_list = sort_list;
+ mtd->sort_size = sort_size;
+
+ mtd->preview = !args_has(args, 'N');
+
+ sort = args_get(args, 'O');
+ if (sort != NULL) {
+ for (i = 0; i < sort_size; i++) {
+ if (strcasecmp(sort, sort_list[i]) == 0)
+ mtd->sort_crit.field = i;
+ }
+ }
+ mtd->sort_crit.reversed = args_has(args, 'r');
+
+ if (args_has(args, 'f'))
+ mtd->filter = xstrdup(args_get(args, 'f'));
+ else
+ mtd->filter = NULL;
+
+ mtd->buildcb = buildcb;
+ mtd->drawcb = drawcb;
+ mtd->searchcb = searchcb;
+ mtd->menucb = menucb;
+ mtd->heightcb = heightcb;
+ mtd->keycb = keycb;
+
+ TAILQ_INIT(&mtd->children);
+
+ *s = &mtd->screen;
+ screen_init(*s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
+ (*s)->mode &= ~MODE_CURSOR;
+
+ return (mtd);
+}
+
+void
+mode_tree_zoom(struct mode_tree_data *mtd, struct args *args)
+{
+ struct window_pane *wp = mtd->wp;
+
+ if (args_has(args, 'Z')) {
+ mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED);
+ if (!mtd->zoomed && window_zoom(wp) == 0)
+ server_redraw_window(wp->window);
+ } else
+ mtd->zoomed = -1;
+}
+
+static void
+mode_tree_set_height(struct mode_tree_data *mtd)
+{
+ struct screen *s = &mtd->screen;
+ u_int height;
+
+ if (mtd->heightcb != NULL) {
+ height = mtd->heightcb(mtd, screen_size_y(s));
+ if (height < screen_size_y(s))
+ mtd->height = screen_size_y(s) - height;
+ } else {
+ mtd->height = (screen_size_y(s) / 3) * 2;
+ if (mtd->height > mtd->line_size)
+ mtd->height = screen_size_y(s) / 2;
+ }
+ if (mtd->height < 10)
+ mtd->height = screen_size_y(s);
+ if (screen_size_y(s) - mtd->height < 2)
+ mtd->height = screen_size_y(s);
+}
+
+void
+mode_tree_build(struct mode_tree_data *mtd)
+{
+ struct screen *s = &mtd->screen;
+ uint64_t tag;
+
+ if (mtd->line_list != NULL)
+ tag = mtd->line_list[mtd->current].item->tag;
+ else
+ tag = UINT64_MAX;
+
+ TAILQ_CONCAT(&mtd->saved, &mtd->children, entry);
+ TAILQ_INIT(&mtd->children);
+
+ mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter);
+ mtd->no_matches = TAILQ_EMPTY(&mtd->children);
+ if (mtd->no_matches)
+ mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL);
+
+ mode_tree_free_items(&mtd->saved);
+ TAILQ_INIT(&mtd->saved);
+
+ mode_tree_clear_lines(mtd);
+ mode_tree_build_lines(mtd, &mtd->children, 0);
+
+ if (tag == UINT64_MAX)
+ tag = mtd->line_list[mtd->current].item->tag;
+ mode_tree_set_current(mtd, tag);
+
+ mtd->width = screen_size_x(s);
+ if (mtd->preview)
+ mode_tree_set_height(mtd);
+ else
+ mtd->height = screen_size_y(s);
+ mode_tree_check_selected(mtd);
+}
+
+static void
+mode_tree_remove_ref(struct mode_tree_data *mtd)
+{
+ if (--mtd->references == 0)
+ free(mtd);
+}
+
+void
+mode_tree_free(struct mode_tree_data *mtd)
+{
+ struct window_pane *wp = mtd->wp;
+
+ if (mtd->zoomed == 0)
+ server_unzoom_window(wp->window);
+
+ mode_tree_free_items(&mtd->children);
+ mode_tree_clear_lines(mtd);
+ screen_free(&mtd->screen);
+
+ free(mtd->search);
+ free(mtd->filter);
+
+ mtd->dead = 1;
+ mode_tree_remove_ref(mtd);
+}
+
+void
+mode_tree_resize(struct mode_tree_data *mtd, u_int sx, u_int sy)
+{
+ struct screen *s = &mtd->screen;
+
+ screen_resize(s, sx, sy, 0);
+
+ mode_tree_build(mtd);
+ mode_tree_draw(mtd);
+
+ mtd->wp->flags |= PANE_REDRAW;
+}
+
+struct mode_tree_item *
+mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
+ void *itemdata, uint64_t tag, const char *name, const char *text,
+ int expanded)
+{
+ struct mode_tree_item *mti, *saved;
+
+ log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag,
+ name, (text == NULL ? "" : text));
+
+ mti = xcalloc(1, sizeof *mti);
+ mti->parent = parent;
+ mti->itemdata = itemdata;
+
+ mti->tag = tag;
+ mti->name = xstrdup(name);
+ if (text != NULL)
+ mti->text = xstrdup(text);
+
+ saved = mode_tree_find_item(&mtd->saved, tag);
+ if (saved != NULL) {
+ if (parent == NULL || parent->expanded)
+ mti->tagged = saved->tagged;
+ mti->expanded = saved->expanded;
+ } else if (expanded == -1)
+ mti->expanded = 1;
+ else
+ mti->expanded = expanded;
+
+ TAILQ_INIT(&mti->children);
+
+ if (parent != NULL)
+ TAILQ_INSERT_TAIL(&parent->children, mti, entry);
+ else
+ TAILQ_INSERT_TAIL(&mtd->children, mti, entry);
+
+ return (mti);
+}
+
+void
+mode_tree_draw_as_parent(struct mode_tree_item *mti)
+{
+ mti->draw_as_parent = 1;
+}
+
+void
+mode_tree_no_tag(struct mode_tree_item *mti)
+{
+ mti->no_tag = 1;
+}
+
+void
+mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti)
+{
+ struct mode_tree_item *parent = mti->parent;
+
+ if (parent != NULL)
+ TAILQ_REMOVE(&parent->children, mti, entry);
+ else
+ TAILQ_REMOVE(&mtd->children, mti, entry);
+ mode_tree_free_item(mti);
+}
+
+void
+mode_tree_draw(struct mode_tree_data *mtd)
+{
+ struct window_pane *wp = mtd->wp;
+ struct screen *s = &mtd->screen;
+ struct mode_tree_line *line;
+ struct mode_tree_item *mti;
+ struct options *oo = wp->window->options;
+ struct screen_write_ctx ctx;
+ struct grid_cell gc0, gc;
+ u_int w, h, i, j, sy, box_x, box_y, width;
+ char *text, *start, *key;
+ const char *tag, *symbol;
+ size_t size, n;
+ int keylen, pad;
+
+ if (mtd->line_size == 0)
+ return;
+
+ memcpy(&gc0, &grid_default_cell, sizeof gc0);
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ style_apply(&gc, oo, "mode-style", NULL);
+
+ w = mtd->width;
+ h = mtd->height;
+
+ screen_write_start(&ctx, s);
+ screen_write_clearscreen(&ctx, 8);
+
+ keylen = 0;
+ for (i = 0; i < mtd->line_size; i++) {
+ mti = mtd->line_list[i].item;
+ if (mti->key == KEYC_NONE)
+ continue;
+ if ((int)mti->keylen + 3 > keylen)
+ keylen = mti->keylen + 3;
+ }
+
+ for (i = 0; i < mtd->line_size; i++) {
+ if (i < mtd->offset)
+ continue;
+ if (i > mtd->offset + h - 1)
+ break;
+ line = &mtd->line_list[i];
+ mti = line->item;
+
+ screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
+
+ pad = keylen - 2 - mti->keylen;
+ if (mti->key != KEYC_NONE)
+ xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
+ else
+ key = xstrdup("");
+
+ if (line->flat)
+ symbol = "";
+ else if (TAILQ_EMPTY(&mti->children))
+ symbol = " ";
+ else if (mti->expanded)
+ symbol = "- ";
+ else
+ symbol = "+ ";
+
+ if (line->depth == 0)
+ start = xstrdup(symbol);
+ else {
+ size = (4 * line->depth) + 32;
+
+ start = xcalloc(1, size);
+ for (j = 1; j < line->depth; j++) {
+ if (mti->parent != NULL &&
+ mtd->line_list[mti->parent->line].last)
+ strlcat(start, " ", size);
+ else
+ strlcat(start, "\001x\001 ", size);
+ }
+ if (line->last)
+ strlcat(start, "\001mq\001> ", size);
+ else
+ strlcat(start, "\001tq\001> ", size);
+ strlcat(start, symbol, size);
+ }
+
+ if (mti->tagged)
+ tag = "*";
+ else
+ tag = "";
+ xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name,
+ tag, (mti->text != NULL) ? ": " : "" );
+ width = utf8_cstrwidth(text);
+ if (width > w)
+ width = w;
+ free(start);
+
+ if (mti->tagged) {
+ gc.attr ^= GRID_ATTR_BRIGHT;
+ gc0.attr ^= GRID_ATTR_BRIGHT;
+ }
+
+ if (i != mtd->current) {
+ screen_write_clearendofline(&ctx, 8);
+ screen_write_nputs(&ctx, w, &gc0, "%s", text);
+ if (mti->text != NULL) {
+ format_draw(&ctx, &gc0, w - width, mti->text,
+ NULL, 0);
+ }
+ } else {
+ screen_write_clearendofline(&ctx, gc.bg);
+ screen_write_nputs(&ctx, w, &gc, "%s", text);
+ if (mti->text != NULL) {
+ format_draw(&ctx, &gc, w - width, mti->text,
+ NULL, 0);
+ }
+ }
+ free(text);
+ free(key);
+
+ if (mti->tagged) {
+ gc.attr ^= GRID_ATTR_BRIGHT;
+ gc0.attr ^= GRID_ATTR_BRIGHT;
+ }
+ }
+
+ sy = screen_size_y(s);
+ if (!mtd->preview || sy <= 4 || h <= 4 || sy - h <= 4 || w <= 4)
+ goto done;
+
+ line = &mtd->line_list[mtd->current];
+ mti = line->item;
+ if (mti->draw_as_parent)
+ mti = mti->parent;
+
+ screen_write_cursormove(&ctx, 0, h, 0);
+ screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
+
+ if (mtd->sort_list != NULL) {
+ xasprintf(&text, " %s (sort: %s%s)", mti->name,
+ mtd->sort_list[mtd->sort_crit.field],
+ mtd->sort_crit.reversed ? ", reversed" : "");
+ } else
+ xasprintf(&text, " %s", mti->name);
+ if (w - 2 >= strlen(text)) {
+ screen_write_cursormove(&ctx, 1, h, 0);
+ screen_write_puts(&ctx, &gc0, "%s", text);
+
+ if (mtd->no_matches)
+ n = (sizeof "no matches") - 1;
+ else
+ n = (sizeof "active") - 1;
+ if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
+ screen_write_puts(&ctx, &gc0, " (filter: ");
+ if (mtd->no_matches)
+ screen_write_puts(&ctx, &gc, "no matches");
+ else
+ screen_write_puts(&ctx, &gc0, "active");
+ screen_write_puts(&ctx, &gc0, ") ");
+ } else
+ screen_write_puts(&ctx, &gc0, " ");
+ }
+ free(text);
+
+ box_x = w - 4;
+ box_y = sy - h - 2;
+
+ if (box_x != 0 && box_y != 0) {
+ screen_write_cursormove(&ctx, 2, h + 1, 0);
+ mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y);
+ }
+
+done:
+ screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
+ screen_write_stop(&ctx);
+}
+
+static struct mode_tree_item *
+mode_tree_search_for(struct mode_tree_data *mtd)
+{
+ struct mode_tree_item *mti, *last, *next;
+
+ if (mtd->search == NULL)
+ return (NULL);
+
+ mti = last = mtd->line_list[mtd->current].item;
+ for (;;) {
+ if (!TAILQ_EMPTY(&mti->children))
+ mti = TAILQ_FIRST(&mti->children);
+ else if ((next = TAILQ_NEXT(mti, entry)) != NULL)
+ mti = next;
+ else {
+ for (;;) {
+ mti = mti->parent;
+ if (mti == NULL)
+ break;
+ if ((next = TAILQ_NEXT(mti, entry)) != NULL) {
+ mti = next;
+ break;
+ }
+ }
+ }
+ if (mti == NULL)
+ mti = TAILQ_FIRST(&mtd->children);
+ if (mti == last)
+ break;
+
+ if (mtd->searchcb == NULL) {
+ if (strstr(mti->name, mtd->search) != NULL)
+ return (mti);
+ continue;
+ }
+ if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search))
+ return (mti);
+ }
+ return (NULL);
+}
+
+static void
+mode_tree_search_set(struct mode_tree_data *mtd)
+{
+ struct mode_tree_item *mti, *loop;
+ uint64_t tag;
+
+ mti = mode_tree_search_for(mtd);
+ if (mti == NULL)
+ return;
+ tag = mti->tag;
+
+ loop = mti->parent;
+ while (loop != NULL) {
+ loop->expanded = 1;
+ loop = loop->parent;
+ }
+
+ mode_tree_build(mtd);
+ mode_tree_set_current(mtd, tag);
+ mode_tree_draw(mtd);
+ mtd->wp->flags |= PANE_REDRAW;
+}
+
+static int
+mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
+ __unused int done)
+{
+ struct mode_tree_data *mtd = data;
+
+ if (mtd->dead)
+ return (0);
+
+ free(mtd->search);
+ if (s == NULL || *s == '\0') {
+ mtd->search = NULL;
+ return (0);
+ }
+ mtd->search = xstrdup(s);
+ mode_tree_search_set(mtd);
+
+ return (0);
+}
+
+static void
+mode_tree_search_free(void *data)
+{
+ mode_tree_remove_ref(data);
+}
+
+static int
+mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
+ __unused int done)
+{
+ struct mode_tree_data *mtd = data;
+
+ if (mtd->dead)
+ return (0);
+
+ if (mtd->filter != NULL)
+ free(mtd->filter);
+ if (s == NULL || *s == '\0')
+ mtd->filter = NULL;
+ else
+ mtd->filter = xstrdup(s);
+
+ mode_tree_build(mtd);
+ mode_tree_draw(mtd);
+ mtd->wp->flags |= PANE_REDRAW;
+
+ return (0);
+}
+
+static void
+mode_tree_filter_free(void *data)
+{
+ mode_tree_remove_ref(data);
+}
+
+static void
+mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx,
+ key_code key, void *data)
+{
+ struct mode_tree_menu *mtm = data;
+ struct mode_tree_data *mtd = mtm->data;
+
+ if (mtd->dead || key == KEYC_NONE)
+ goto out;
+
+ if (mtm->line >= mtd->line_size)
+ goto out;
+ mtd->current = mtm->line;
+ mtd->menucb(mtd->modedata, mtm->c, key);
+
+out:
+ mode_tree_remove_ref(mtd);
+ free(mtm);
+}
+
+static void
+mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
+ u_int y, int outside)
+{
+ struct mode_tree_item *mti;
+ struct menu *menu;
+ const struct menu_item *items;
+ struct mode_tree_menu *mtm;
+ char *title;
+ u_int line;
+
+ if (mtd->offset + y > mtd->line_size - 1)
+ line = mtd->current;
+ else
+ line = mtd->offset + y;
+ mti = mtd->line_list[line].item;
+
+ if (!outside) {
+ items = mtd->menu;
+ xasprintf(&title, "#[align=centre]%s", mti->name);
+ } else {
+ items = mode_tree_menu_items;
+ title = xstrdup("");
+ }
+ menu = menu_create(title);
+ menu_add_items(menu, items, NULL, c, NULL);
+ free(title);
+
+ mtm = xmalloc(sizeof *mtm);
+ mtm->data = mtd;
+ mtm->c = c;
+ mtm->line = line;
+ mtd->references++;
+
+ if (x >= (menu->width + 4) / 2)
+ x -= (menu->width + 4) / 2;
+ else
+ x = 0;
+ if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback,
+ mtm) != 0)
+ menu_free(menu);
+}
+
+int
+mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
+ struct mouse_event *m, u_int *xp, u_int *yp)
+{
+ struct mode_tree_line *line;
+ struct mode_tree_item *current, *parent, *mti;
+ u_int i, x, y;
+ int choice;
+
+ if (KEYC_IS_MOUSE(*key) && m != NULL) {
+ if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
+ *key = KEYC_NONE;
+ return (0);
+ }
+ if (xp != NULL)
+ *xp = x;
+ if (yp != NULL)
+ *yp = y;
+ if (x > mtd->width || y > mtd->height) {
+ if (*key == KEYC_MOUSEDOWN3_PANE)
+ mode_tree_display_menu(mtd, c, x, y, 1);
+ if (!mtd->preview)
+ *key = KEYC_NONE;
+ return (0);
+ }
+ if (mtd->offset + y < mtd->line_size) {
+ if (*key == KEYC_MOUSEDOWN1_PANE ||
+ *key == KEYC_MOUSEDOWN3_PANE ||
+ *key == KEYC_DOUBLECLICK1_PANE)
+ mtd->current = mtd->offset + y;
+ if (*key == KEYC_DOUBLECLICK1_PANE)
+ *key = '\r';
+ else {
+ if (*key == KEYC_MOUSEDOWN3_PANE)
+ mode_tree_display_menu(mtd, c, x, y, 0);
+ *key = KEYC_NONE;
+ }
+ } else {
+ if (*key == KEYC_MOUSEDOWN3_PANE)
+ mode_tree_display_menu(mtd, c, x, y, 0);
+ *key = KEYC_NONE;
+ }
+ return (0);
+ }
+
+ line = &mtd->line_list[mtd->current];
+ current = line->item;
+
+ choice = -1;
+ for (i = 0; i < mtd->line_size; i++) {
+ if (*key == mtd->line_list[i].item->key) {
+ choice = i;
+ break;
+ }
+ }
+ if (choice != -1) {
+ if ((u_int)choice > mtd->line_size - 1) {
+ *key = KEYC_NONE;
+ return (0);
+ }
+ mtd->current = choice;
+ *key = '\r';
+ return (0);
+ }
+
+ switch (*key) {
+ case 'q':
+ case '\033': /* Escape */
+ case '\007': /* C-g */
+ return (1);
+ case KEYC_UP:
+ case 'k':
+ case KEYC_WHEELUP_PANE:
+ case '\020': /* C-p */
+ mode_tree_up(mtd, 1);
+ break;
+ case KEYC_DOWN:
+ case 'j':
+ case KEYC_WHEELDOWN_PANE:
+ case '\016': /* C-n */
+ mode_tree_down(mtd, 1);
+ break;
+ case KEYC_PPAGE:
+ case '\002': /* C-b */
+ for (i = 0; i < mtd->height; i++) {
+ if (mtd->current == 0)
+ break;
+ mode_tree_up(mtd, 1);
+ }
+ break;
+ case KEYC_NPAGE:
+ case '\006': /* C-f */
+ for (i = 0; i < mtd->height; i++) {
+ if (mtd->current == mtd->line_size - 1)
+ break;
+ mode_tree_down(mtd, 1);
+ }
+ break;
+ case 'g':
+ case KEYC_HOME:
+ mtd->current = 0;
+ mtd->offset = 0;
+ break;
+ case 'G':
+ case KEYC_END:
+ mtd->current = mtd->line_size - 1;
+ if (mtd->current > mtd->height - 1)
+ mtd->offset = mtd->current - mtd->height + 1;
+ else
+ mtd->offset = 0;
+ break;
+ case 't':
+ /*
+ * Do not allow parents and children to both be tagged: untag
+ * all parents and children of current.
+ */
+ if (current->no_tag)
+ break;
+ if (!current->tagged) {
+ parent = current->parent;
+ while (parent != NULL) {
+ parent->tagged = 0;
+ parent = parent->parent;
+ }
+ mode_tree_clear_tagged(&current->children);
+ current->tagged = 1;
+ } else
+ current->tagged = 0;
+ if (m != NULL)
+ mode_tree_down(mtd, 0);
+ break;
+ case 'T':
+ for (i = 0; i < mtd->line_size; i++)
+ mtd->line_list[i].item->tagged = 0;
+ break;
+ case '\024': /* C-t */
+ for (i = 0; i < mtd->line_size; i++) {
+ if ((mtd->line_list[i].item->parent == NULL &&
+ !mtd->line_list[i].item->no_tag) ||
+ (mtd->line_list[i].item->parent != NULL &&
+ mtd->line_list[i].item->parent->no_tag))
+ mtd->line_list[i].item->tagged = 1;
+ else
+ mtd->line_list[i].item->tagged = 0;
+ }
+ break;
+ case 'O':
+ mtd->sort_crit.field++;
+ if (mtd->sort_crit.field >= mtd->sort_size)
+ mtd->sort_crit.field = 0;
+ mode_tree_build(mtd);
+ break;
+ case 'r':
+ mtd->sort_crit.reversed = !mtd->sort_crit.reversed;
+ mode_tree_build(mtd);
+ break;
+ case KEYC_LEFT:
+ case 'h':
+ case '-':
+ if (line->flat || !current->expanded)
+ current = current->parent;
+ if (current == NULL)
+ mode_tree_up(mtd, 0);
+ else {
+ current->expanded = 0;
+ mtd->current = current->line;
+ mode_tree_build(mtd);
+ }
+ break;
+ case KEYC_RIGHT:
+ case 'l':
+ case '+':
+ if (line->flat || current->expanded)
+ mode_tree_down(mtd, 0);
+ else if (!line->flat) {
+ current->expanded = 1;
+ mode_tree_build(mtd);
+ }
+ break;
+ case '-'|KEYC_META:
+ TAILQ_FOREACH(mti, &mtd->children, entry)
+ mti->expanded = 0;
+ mode_tree_build(mtd);
+ break;
+ case '+'|KEYC_META:
+ TAILQ_FOREACH(mti, &mtd->children, entry)
+ mti->expanded = 1;
+ mode_tree_build(mtd);
+ break;
+ case '?':
+ case '/':
+ case '\023': /* C-s */
+ mtd->references++;
+ status_prompt_set(c, NULL, "(search) ", "",
+ mode_tree_search_callback, mode_tree_search_free, mtd,
+ PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
+ break;
+ case 'n':
+ mode_tree_search_set(mtd);
+ break;
+ case 'f':
+ mtd->references++;
+ status_prompt_set(c, NULL, "(filter) ", mtd->filter,
+ mode_tree_filter_callback, mode_tree_filter_free, mtd,
+ PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
+ break;
+ case 'v':
+ mtd->preview = !mtd->preview;
+ mode_tree_build(mtd);
+ if (mtd->preview)
+ mode_tree_check_selected(mtd);
+ break;
+ }
+ return (0);
+}
+
+void
+mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
+ const char *template, const char *name)
+{
+ struct cmdq_state *state;
+ char *command, *error;
+ enum cmd_parse_status status;
+
+ command = cmd_template_replace(template, name, 1);
+ if (command != NULL && *command != '\0') {
+ state = cmdq_new_state(fs, NULL, 0);
+ status = cmd_parse_and_append(command, NULL, c, state, &error);
+ if (status == CMD_PARSE_ERROR) {
+ if (c != NULL) {
+ *error = toupper((u_char)*error);
+ status_message_set(c, -1, 1, 0, "%s", error);
+ }
+ free(error);
+ }
+ cmdq_free_state(state);
+ }
+ free(command);
+}
diff --git a/names.c b/names.c
new file mode 100644
index 0000000..aeb6733
--- /dev/null
+++ b/names.c
@@ -0,0 +1,172 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 <ctype.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static void name_time_callback(int, short, void *);
+static int name_time_expired(struct window *, struct timeval *);
+
+static char *format_window_name(struct window *);
+
+static void
+name_time_callback(__unused int fd, __unused short events, void *arg)
+{
+ struct window *w = arg;
+
+ /* The event loop will call check_window_name for us on the way out. */
+ log_debug("@%u name timer expired", w->id);
+}
+
+static int
+name_time_expired(struct window *w, struct timeval *tv)
+{
+ struct timeval offset;
+
+ timersub(tv, &w->name_time, &offset);
+ if (offset.tv_sec != 0 || offset.tv_usec > NAME_INTERVAL)
+ return (0);
+ return (NAME_INTERVAL - offset.tv_usec);
+}
+
+void
+check_window_name(struct window *w)
+{
+ struct timeval tv, next;
+ char *name;
+ int left;
+
+ if (w->active == NULL)
+ return;
+
+ if (!options_get_number(w->options, "automatic-rename"))
+ return;
+
+ if (~w->active->flags & PANE_CHANGED) {
+ log_debug("@%u active pane not changed", w->id);
+ return;
+ }
+ log_debug("@%u active pane changed", w->id);
+
+ gettimeofday(&tv, NULL);
+ left = name_time_expired(w, &tv);
+ if (left != 0) {
+ if (!event_initialized(&w->name_event))
+ evtimer_set(&w->name_event, name_time_callback, w);
+ if (!evtimer_pending(&w->name_event, NULL)) {
+ log_debug("@%u name timer queued (%d left)", w->id,
+ left);
+ timerclear(&next);
+ next.tv_usec = left;
+ event_add(&w->name_event, &next);
+ } else {
+ log_debug("@%u name timer already queued (%d left)",
+ w->id, left);
+ }
+ return;
+ }
+ memcpy(&w->name_time, &tv, sizeof w->name_time);
+ if (event_initialized(&w->name_event))
+ evtimer_del(&w->name_event);
+
+ w->active->flags &= ~PANE_CHANGED;
+
+ name = format_window_name(w);
+ if (strcmp(name, w->name) != 0) {
+ log_debug("@%u new name %s (was %s)", w->id, name, w->name);
+ window_set_name(w, name);
+ server_redraw_window_borders(w);
+ server_status_window(w);
+ } else
+ log_debug("@%u name not changed (still %s)", w->id, w->name);
+
+ free(name);
+}
+
+char *
+default_window_name(struct window *w)
+{
+ char *cmd, *s;
+
+ if (w->active == NULL)
+ return (xstrdup(""));
+ cmd = cmd_stringify_argv(w->active->argc, w->active->argv);
+ if (cmd != NULL && *cmd != '\0')
+ s = parse_window_name(cmd);
+ else
+ s = parse_window_name(w->active->shell);
+ free(cmd);
+ return (s);
+}
+
+static char *
+format_window_name(struct window *w)
+{
+ struct format_tree *ft;
+ const char *fmt;
+ char *name;
+
+ ft = format_create(NULL, NULL, FORMAT_WINDOW|w->id, 0);
+ format_defaults_window(ft, w);
+ format_defaults_pane(ft, w->active);
+
+ fmt = options_get_string(w->options, "automatic-rename-format");
+ name = format_expand(ft, fmt);
+
+ format_free(ft);
+ return (name);
+}
+
+char *
+parse_window_name(const char *in)
+{
+ char *copy, *name, *ptr;
+
+ name = copy = xstrdup(in);
+ if (*name == '"')
+ name++;
+ name[strcspn(name, "\"")] = '\0';
+
+ if (strncmp(name, "exec ", (sizeof "exec ") - 1) == 0)
+ name = name + (sizeof "exec ") - 1;
+
+ while (*name == ' ' || *name == '-')
+ name++;
+ if ((ptr = strchr(name, ' ')) != NULL)
+ *ptr = '\0';
+
+ if (*name != '\0') {
+ ptr = name + strlen(name) - 1;
+ while (ptr > name &&
+ !isalnum((u_char)*ptr) &&
+ !ispunct((u_char)*ptr))
+ *ptr-- = '\0';
+ }
+
+ if (*name == '/')
+ name = basename(name);
+ name = xstrdup(name);
+ free(copy);
+ return (name);
+}
diff --git a/notify.c b/notify.c
new file mode 100644
index 0000000..8525585
--- /dev/null
+++ b/notify.c
@@ -0,0 +1,300 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 George Nachman <tmux@georgester.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"
+
+struct notify_entry {
+ const char *name;
+ struct cmd_find_state fs;
+ struct format_tree *formats;
+
+ struct client *client;
+ struct session *session;
+ struct window *window;
+ int pane;
+};
+
+static struct cmdq_item *
+notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne,
+ struct cmd_list *cmdlist, struct cmdq_state *state)
+{
+ struct cmdq_item *new_item;
+ char *s;
+
+ if (cmdlist == NULL)
+ return (item);
+ if (log_get_level() != 0) {
+ s = cmd_list_print(cmdlist, 0);
+ log_debug("%s: hook %s is: %s", __func__, ne->name, s);
+ free(s);
+ }
+ new_item = cmdq_get_command(cmdlist, state);
+ return (cmdq_insert_after(item, new_item));
+}
+
+static void
+notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne)
+{
+ struct cmd_find_state fs;
+ struct options *oo;
+ struct cmdq_state *state;
+ struct options_entry *o;
+ struct options_array_item *a;
+ struct cmd_list *cmdlist;
+ const char *value;
+ struct cmd_parse_result *pr;
+
+ log_debug("%s: inserting hook %s", __func__, ne->name);
+
+ cmd_find_clear_state(&fs, 0);
+ if (cmd_find_empty_state(&ne->fs) || !cmd_find_valid_state(&ne->fs))
+ cmd_find_from_nothing(&fs, 0);
+ else
+ cmd_find_copy_state(&fs, &ne->fs);
+
+ if (fs.s == NULL)
+ oo = global_s_options;
+ else
+ oo = fs.s->options;
+ o = options_get(oo, ne->name);
+ if (o == NULL && fs.wp != NULL) {
+ oo = fs.wp->options;
+ o = options_get(oo, ne->name);
+ }
+ if (o == NULL && fs.wl != NULL) {
+ oo = fs.wl->window->options;
+ o = options_get(oo, ne->name);
+ }
+ if (o == NULL) {
+ log_debug("%s: hook %s not found", __func__, ne->name);
+ return;
+ }
+
+ state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS);
+ cmdq_add_formats(state, ne->formats);
+
+ if (*ne->name == '@') {
+ value = options_get_string(oo, ne->name);
+ pr = cmd_parse_from_string(value, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ log_debug("%s: can't parse hook %s: %s", __func__,
+ ne->name, pr->error);
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ notify_insert_one_hook(item, ne, pr->cmdlist, state);
+ break;
+ }
+ } else {
+ a = options_array_first(o);
+ while (a != NULL) {
+ cmdlist = options_array_item_value(a)->cmdlist;
+ item = notify_insert_one_hook(item, ne, cmdlist, state);
+ a = options_array_next(a);
+ }
+ }
+
+ cmdq_free_state(state);
+}
+
+static enum cmd_retval
+notify_callback(struct cmdq_item *item, void *data)
+{
+ struct notify_entry *ne = data;
+
+ log_debug("%s: %s", __func__, ne->name);
+
+ if (strcmp(ne->name, "pane-mode-changed") == 0)
+ control_notify_pane_mode_changed(ne->pane);
+ if (strcmp(ne->name, "window-layout-changed") == 0)
+ control_notify_window_layout_changed(ne->window);
+ if (strcmp(ne->name, "window-pane-changed") == 0)
+ control_notify_window_pane_changed(ne->window);
+ if (strcmp(ne->name, "window-unlinked") == 0)
+ control_notify_window_unlinked(ne->session, ne->window);
+ if (strcmp(ne->name, "window-linked") == 0)
+ control_notify_window_linked(ne->session, ne->window);
+ if (strcmp(ne->name, "window-renamed") == 0)
+ control_notify_window_renamed(ne->window);
+ if (strcmp(ne->name, "client-session-changed") == 0)
+ control_notify_client_session_changed(ne->client);
+ if (strcmp(ne->name, "client-detached") == 0)
+ control_notify_client_detached(ne->client);
+ if (strcmp(ne->name, "session-renamed") == 0)
+ control_notify_session_renamed(ne->session);
+ if (strcmp(ne->name, "session-created") == 0)
+ control_notify_session_created(ne->session);
+ if (strcmp(ne->name, "session-closed") == 0)
+ control_notify_session_closed(ne->session);
+ if (strcmp(ne->name, "session-window-changed") == 0)
+ control_notify_session_window_changed(ne->session);
+
+ notify_insert_hook(item, ne);
+
+ if (ne->client != NULL)
+ server_client_unref(ne->client);
+ if (ne->session != NULL)
+ session_remove_ref(ne->session, __func__);
+ if (ne->window != NULL)
+ window_remove_ref(ne->window, __func__);
+
+ if (ne->fs.s != NULL)
+ session_remove_ref(ne->fs.s, __func__);
+
+ format_free(ne->formats);
+ free((void *)ne->name);
+ free(ne);
+
+ return (CMD_RETURN_NORMAL);
+}
+
+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 notify_entry *ne;
+ struct cmdq_item *item;
+
+ item = cmdq_running(NULL);
+ if (item != NULL && (cmdq_get_flags(item) & CMDQ_STATE_NOHOOKS))
+ return;
+
+ ne = xcalloc(1, sizeof *ne);
+ ne->name = xstrdup(name);
+
+ ne->client = c;
+ ne->session = s;
+ ne->window = w;
+ ne->pane = (wp != NULL ? wp->id : -1);
+
+ ne->formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS);
+ format_add(ne->formats, "hook", "%s", name);
+ if (c != NULL)
+ format_add(ne->formats, "hook_client", "%s", c->name);
+ if (s != NULL) {
+ format_add(ne->formats, "hook_session", "$%u", s->id);
+ format_add(ne->formats, "hook_session_name", "%s", s->name);
+ }
+ if (w != NULL) {
+ format_add(ne->formats, "hook_window", "@%u", w->id);
+ format_add(ne->formats, "hook_window_name", "%s", w->name);
+ }
+ if (wp != NULL)
+ format_add(ne->formats, "hook_pane", "%%%d", wp->id);
+ format_log_debug(ne->formats, __func__);
+
+ if (c != NULL)
+ c->references++;
+ if (s != NULL)
+ session_add_ref(s, __func__);
+ if (w != NULL)
+ window_add_ref(w, __func__);
+
+ cmd_find_copy_state(&ne->fs, fs);
+ if (ne->fs.s != NULL) /* cmd_find_valid_state needs session */
+ session_add_ref(ne->fs.s, __func__);
+
+ cmdq_append(NULL, cmdq_get_callback(notify_callback, ne));
+}
+
+void
+notify_hook(struct cmdq_item *item, const char *name)
+{
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct notify_entry ne;
+
+ memset(&ne, 0, sizeof ne);
+
+ ne.name = name;
+ cmd_find_copy_state(&ne.fs, target);
+
+ ne.client = cmdq_get_client(item);
+ ne.session = target->s;
+ ne.window = target->w;
+ ne.pane = (target->wp != NULL ? target->wp->id : -1);
+
+ ne.formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS);
+ format_add(ne.formats, "hook", "%s", name);
+ format_log_debug(ne.formats, __func__);
+
+ notify_insert_hook(item, &ne);
+ format_free(ne.formats);
+}
+
+void
+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);
+}
+
+void
+notify_session(const char *name, struct session *s)
+{
+ struct cmd_find_state fs;
+
+ if (session_alive(s))
+ cmd_find_from_session(&fs, s, 0);
+ else
+ cmd_find_from_nothing(&fs, 0);
+ notify_add(name, &fs, NULL, s, NULL, NULL);
+}
+
+void
+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);
+}
+
+void
+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);
+}
+
+void
+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);
+}
+
+void
+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);
+}
diff --git a/options-table.c b/options-table.c
new file mode 100644
index 0000000..17be7ec
--- /dev/null
+++ b/options-table.c
@@ -0,0 +1,1281 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2011 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 <string.h>
+
+#include "tmux.h"
+
+/*
+ * This file has a tables with all the server, session and window
+ * options. These tables are the master copy of the options with their real
+ * (user-visible) types, range limits and default values. At start these are
+ * copied into the runtime global options trees (which only has number and
+ * string types). These tables are then used to look up the real type when the
+ * user sets an option or its value needs to be shown.
+ */
+
+/* Choice option type lists. */
+static const char *options_table_mode_keys_list[] = {
+ "emacs", "vi", NULL
+};
+static const char *options_table_clock_mode_style_list[] = {
+ "12", "24", NULL
+};
+static const char *options_table_status_list[] = {
+ "off", "on", "2", "3", "4", "5", NULL
+};
+static const char *options_table_status_keys_list[] = {
+ "emacs", "vi", NULL
+};
+static const char *options_table_status_justify_list[] = {
+ "left", "centre", "right", "absolute-centre", NULL
+};
+static const char *options_table_status_position_list[] = {
+ "top", "bottom", NULL
+};
+static const char *options_table_bell_action_list[] = {
+ "none", "any", "current", "other", NULL
+};
+static const char *options_table_visual_bell_list[] = {
+ "off", "on", "both", NULL
+};
+static const char *options_table_cursor_style_list[] = {
+ "default", "blinking-block", "block", "blinking-underline", "underline",
+ "blinking-bar", "bar", NULL
+};
+static const char *options_table_pane_status_list[] = {
+ "off", "top", "bottom", NULL
+};
+static const char *options_table_pane_border_indicators_list[] = {
+ "off", "colour", "arrows", "both", NULL
+};
+static const char *options_table_pane_border_lines_list[] = {
+ "single", "double", "heavy", "simple", "number", NULL
+};
+static const char *options_table_popup_border_lines_list[] = {
+ "single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
+};
+static const char *options_table_set_clipboard_list[] = {
+ "off", "external", "on", NULL
+};
+static const char *options_table_window_size_list[] = {
+ "largest", "smallest", "manual", "latest", NULL
+};
+static const char *options_table_remain_on_exit_list[] = {
+ "off", "on", "failed", NULL
+};
+static const char *options_table_detach_on_destroy_list[] = {
+ "off", "on", "no-detached", NULL
+};
+static const char *options_table_extended_keys_list[] = {
+ "off", "on", "always", NULL
+};
+
+/* Status line format. */
+#define OPTIONS_TABLE_STATUS_FORMAT1 \
+ "#[align=left range=left #{E:status-left-style}]" \
+ "#[push-default]" \
+ "#{T;=/#{status-left-length}:status-left}" \
+ "#[pop-default]" \
+ "#[norange default]" \
+ "#[list=on align=#{status-justify}]" \
+ "#[list=left-marker]<#[list=right-marker]>#[list=on]" \
+ "#{W:" \
+ "#[range=window|#{window_index} " \
+ "#{E:window-status-style}" \
+ "#{?#{&&:#{window_last_flag}," \
+ "#{!=:#{E:window-status-last-style},default}}, " \
+ "#{E:window-status-last-style}," \
+ "}" \
+ "#{?#{&&:#{window_bell_flag}," \
+ "#{!=:#{E:window-status-bell-style},default}}, " \
+ "#{E:window-status-bell-style}," \
+ "#{?#{&&:#{||:#{window_activity_flag}," \
+ "#{window_silence_flag}}," \
+ "#{!=:" \
+ "#{E:window-status-activity-style}," \
+ "default}}, " \
+ "#{E:window-status-activity-style}," \
+ "}" \
+ "}" \
+ "]" \
+ "#[push-default]" \
+ "#{T:window-status-format}" \
+ "#[pop-default]" \
+ "#[norange default]" \
+ "#{?window_end_flag,,#{window-status-separator}}" \
+ "," \
+ "#[range=window|#{window_index} list=focus " \
+ "#{?#{!=:#{E:window-status-current-style},default}," \
+ "#{E:window-status-current-style}," \
+ "#{E:window-status-style}" \
+ "}" \
+ "#{?#{&&:#{window_last_flag}," \
+ "#{!=:#{E:window-status-last-style},default}}, " \
+ "#{E:window-status-last-style}," \
+ "}" \
+ "#{?#{&&:#{window_bell_flag}," \
+ "#{!=:#{E:window-status-bell-style},default}}, " \
+ "#{E:window-status-bell-style}," \
+ "#{?#{&&:#{||:#{window_activity_flag}," \
+ "#{window_silence_flag}}," \
+ "#{!=:" \
+ "#{E:window-status-activity-style}," \
+ "default}}, " \
+ "#{E:window-status-activity-style}," \
+ "}" \
+ "}" \
+ "]" \
+ "#[push-default]" \
+ "#{T:window-status-current-format}" \
+ "#[pop-default]" \
+ "#[norange list=on default]" \
+ "#{?window_end_flag,,#{window-status-separator}}" \
+ "}" \
+ "#[nolist align=right range=right #{E:status-right-style}]" \
+ "#[push-default]" \
+ "#{T;=/#{status-right-length}:status-right}" \
+ "#[pop-default]" \
+ "#[norange default]"
+#define OPTIONS_TABLE_STATUS_FORMAT2 \
+ "#[align=centre]#{P:#{?pane_active,#[reverse],}" \
+ "#{pane_index}[#{pane_width}x#{pane_height}]#[default] }"
+static const char *options_table_status_format_default[] = {
+ OPTIONS_TABLE_STATUS_FORMAT1, OPTIONS_TABLE_STATUS_FORMAT2, NULL
+};
+
+/* Helpers for hook options. */
+#define OPTIONS_TABLE_HOOK(hook_name, default_value) \
+ { .name = hook_name, \
+ .type = OPTIONS_TABLE_COMMAND, \
+ .scope = OPTIONS_TABLE_SESSION, \
+ .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \
+ .default_str = default_value, \
+ .separator = "" \
+ }
+
+#define OPTIONS_TABLE_PANE_HOOK(hook_name, default_value) \
+ { .name = hook_name, \
+ .type = OPTIONS_TABLE_COMMAND, \
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, \
+ .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \
+ .default_str = default_value, \
+ .separator = "" \
+ }
+
+#define OPTIONS_TABLE_WINDOW_HOOK(hook_name, default_value) \
+ { .name = hook_name, \
+ .type = OPTIONS_TABLE_COMMAND, \
+ .scope = OPTIONS_TABLE_WINDOW, \
+ .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \
+ .default_str = default_value, \
+ .separator = "" \
+ }
+
+/* Map of name conversions. */
+const struct options_name_map options_other_names[] = {
+ { "display-panes-color", "display-panes-colour" },
+ { "display-panes-active-color", "display-panes-active-colour" },
+ { "clock-mode-color", "clock-mode-colour" },
+ { "cursor-color", "cursor-colour" },
+ { "pane-colors", "pane-colours" },
+ { NULL, NULL }
+};
+
+/* Top-level options. */
+const struct options_table_entry options_table[] = {
+ /* Server options. */
+ { .name = "backspace",
+ .type = OPTIONS_TABLE_KEY,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_num = '\177',
+ .text = "The key to send for backspace."
+ },
+
+ { .name = "buffer-limit",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SERVER,
+ .minimum = 1,
+ .maximum = INT_MAX,
+ .default_num = 50,
+ .text = "The maximum number of automatic buffers. "
+ "When this is reached, the oldest buffer is deleted."
+ },
+
+ { .name = "command-alias",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .default_str = "split-pane=split-window,"
+ "splitp=split-window,"
+ "server-info=show-messages -JT,"
+ "info=show-messages -JT,"
+ "choose-window=choose-tree -w,"
+ "choose-session=choose-tree -s",
+ .separator = ",",
+ .text = "Array of command aliases. "
+ "Each entry is an alias and a command separated by '='."
+ },
+
+ { .name = "copy-command",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_str = "",
+ .text = "Shell command run when text is copied. "
+ "If empty, no command is run."
+ },
+
+ { .name = "cursor-colour",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_num = -1,
+ .text = "Colour of the cursor."
+ },
+
+ { .name = "cursor-style",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .choices = options_table_cursor_style_list,
+ .default_num = 0,
+ .text = "Style of the cursor."
+ },
+
+ { .name = "default-terminal",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_str = TMUX_TERM,
+ .text = "Default for the 'TERM' environment variable."
+ },
+
+ { .name = "editor",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_str = _PATH_VI,
+ .text = "Editor run to edit files."
+ },
+
+ { .name = "escape-time",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SERVER,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 500,
+ .unit = "milliseconds",
+ .text = "Time to wait before assuming a key is Escape."
+ },
+
+ { .name = "exit-empty",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_num = 1,
+ .text = "Whether the server should exit if there are no sessions."
+ },
+
+ { .name = "exit-unattached",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_num = 0,
+ .text = "Whether the server should exit if there are no attached "
+ "clients."
+ },
+
+ { .name = "extended-keys",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SERVER,
+ .choices = options_table_extended_keys_list,
+ .default_num = 0,
+ .text = "Whether to request extended key sequences from terminals "
+ "that support it."
+ },
+
+ { .name = "focus-events",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_num = 0,
+ .text = "Whether to send focus events to applications."
+ },
+
+ { .name = "history-file",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .default_str = "",
+ .text = "Location of the command prompt history file. "
+ "Empty does not write a history file."
+ },
+
+ { .name = "message-limit",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SERVER,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 1000,
+ .text = "Maximum number of server messages to keep."
+ },
+
+ { .name = "prompt-history-limit",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SERVER,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 100,
+ .text = "Maximum number of commands to keep in history."
+ },
+
+ { .name = "set-clipboard",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SERVER,
+ .choices = options_table_set_clipboard_list,
+ .default_num = 1,
+ .text = "Whether to attempt to set the system clipboard ('on' or "
+ "'external') and whether to allow applications to create "
+ "paste buffers with an escape sequence ('on' only)."
+ },
+
+ { .name = "terminal-overrides",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .default_str = "",
+ .separator = ",",
+ .text = "List of terminal capabilities overrides."
+ },
+
+ { .name = "terminal-features",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .default_str = "xterm*:clipboard:ccolour:cstyle:focus:title,"
+ "screen*:title",
+ .separator = ",",
+ .text = "List of terminal features, used if they cannot be "
+ "automatically detected."
+ },
+
+ { .name = "user-keys",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SERVER,
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .default_str = "",
+ .separator = ",",
+ .text = "User key assignments. "
+ "Each sequence in the list is translated into a key: "
+ "'User0', 'User1' and so on."
+ },
+
+ /* Session options. */
+ { .name = "activity-action",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_bell_action_list,
+ .default_num = ALERT_OTHER,
+ .text = "Action to take on an activity alert."
+ },
+
+ { .name = "assume-paste-time",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 1,
+ .unit = "milliseconds",
+ .text = "Maximum time between input to assume it is pasting rather "
+ "than typing."
+ },
+
+ { .name = "base-index",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 0,
+ .text = "Default index of the first window in each session."
+ },
+
+ { .name = "bell-action",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_bell_action_list,
+ .default_num = ALERT_ANY,
+ .text = "Action to take on a bell alert."
+ },
+
+ { .name = "default-command",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "",
+ .text = "Default command to run in new panes. If empty, a shell is "
+ "started."
+ },
+
+ { .name = "default-shell",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = _PATH_BSHELL,
+ .text = "Location of default shell."
+ },
+
+ { .name = "default-size",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .pattern = "[0-9]*x[0-9]*",
+ .default_str = "80x24",
+ .text = "Initial size of new sessions."
+ },
+
+ { .name = "destroy-unattached",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 0,
+ .text = "Whether to destroy sessions when they have no attached "
+ "clients."
+ },
+
+ { .name = "detach-on-destroy",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_detach_on_destroy_list,
+ .default_num = 1,
+ .text = "Whether to detach when a session is destroyed, or switch "
+ "the client to another session if any exist."
+ },
+
+ { .name = "display-panes-active-colour",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 1,
+ .text = "Colour of the active pane for 'display-panes'."
+ },
+
+ { .name = "display-panes-colour",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 4,
+ .text = "Colour of not active panes for 'display-panes'."
+ },
+
+ { .name = "display-panes-time",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 1,
+ .maximum = INT_MAX,
+ .default_num = 1000,
+ .unit = "milliseconds",
+ .text = "Time for which 'display-panes' should show pane numbers."
+ },
+
+ { .name = "display-time",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 750,
+ .unit = "milliseconds",
+ .text = "Time for which status line messages should appear."
+ },
+
+ { .name = "history-limit",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 2000,
+ .unit = "lines",
+ .text = "Maximum number of lines to keep in the history for each "
+ "pane. "
+ "If changed, the new value applies only to new panes."
+ },
+
+ { .name = "key-table",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "root",
+ .text = "Default key table. "
+ "Key presses are first looked up in this table."
+ },
+
+ { .name = "lock-after-time",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 0,
+ .unit = "seconds",
+ .text = "Time after which a client is locked if not used."
+ },
+
+ { .name = "lock-command",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "lock -np",
+ .text = "Shell command to run to lock a client."
+ },
+
+ { .name = "message-command-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "bg=black,fg=yellow",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the command prompt when in command mode, if "
+ "'mode-keys' is set to 'vi'."
+ },
+
+ { .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."
+ },
+
+ { .name = "mouse",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 0,
+ .text = "Whether the mouse is recognised and mouse key bindings are "
+ "executed. "
+ "Applications inside panes can use the mouse even when 'off'."
+ },
+
+ { .name = "prefix",
+ .type = OPTIONS_TABLE_KEY,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = '\002',
+ .text = "The prefix key."
+ },
+
+ { .name = "prefix2",
+ .type = OPTIONS_TABLE_KEY,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = KEYC_NONE,
+ .text = "A second prefix key."
+ },
+
+ { .name = "renumber-windows",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 0,
+ .text = "Whether windows are automatically renumbered rather than "
+ "leaving gaps."
+ },
+
+ { .name = "repeat-time",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = SHRT_MAX,
+ .default_num = 500,
+ .unit = "milliseconds",
+ .text = "Time to wait for a key binding to repeat, if it is bound "
+ "with the '-r' flag."
+ },
+
+ { .name = "set-titles",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 0,
+ .text = "Whether to set the terminal title, if supported."
+ },
+
+ { .name = "set-titles-string",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "#S:#I:#W - \"#T\" #{session_alerts}",
+ .text = "Format of the terminal title to set."
+ },
+
+ { .name = "silence-action",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_bell_action_list,
+ .default_num = ALERT_OTHER,
+ .text = "Action to take on a silence alert."
+ },
+
+ { .name = "status",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_status_list,
+ .default_num = 1,
+ .text = "Number of lines in the status line."
+ },
+
+ { .name = "status-bg",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 8,
+ .text = "Background colour of the status line. This option is "
+ "deprecated, use 'status-style' instead."
+ },
+
+ { .name = "status-fg",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_num = 8,
+ .text = "Foreground colour of the status line. This option is "
+ "deprecated, use 'status-style' instead."
+ },
+
+ { .name = "status-format",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .default_arr = options_table_status_format_default,
+ .text = "Formats for the status lines. "
+ "Each array member is the format for one status line. "
+ "The default status line is made up of several components "
+ "which may be configured individually with other options such "
+ "as 'status-left'."
+ },
+
+ { .name = "status-interval",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 15,
+ .unit = "seconds",
+ .text = "Number of seconds between status line updates."
+ },
+
+ { .name = "status-justify",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_status_justify_list,
+ .default_num = 0,
+ .text = "Position of the window list in the status line."
+ },
+
+ { .name = "status-keys",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_status_keys_list,
+ .default_num = MODEKEY_EMACS,
+ .text = "Key set to use at the command prompt."
+ },
+
+ { .name = "status-left",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "[#{session_name}] ",
+ .text = "Contents of the left side of the status line."
+ },
+
+ { .name = "status-left-length",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = SHRT_MAX,
+ .default_num = 10,
+ .text = "Maximum width of the left side of the status line."
+ },
+
+ { .name = "status-left-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the left side of the status line."
+ },
+
+ { .name = "status-position",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_status_position_list,
+ .default_num = 1,
+ .text = "Position of the status line."
+ },
+
+ { .name = "status-right",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "#{?window_bigger,"
+ "[#{window_offset_x}#,#{window_offset_y}] ,}"
+ "\"#{=21:pane_title}\" %H:%M %d-%b-%y",
+ .text = "Contents of the right side of the status line."
+
+ },
+
+ { .name = "status-right-length",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_SESSION,
+ .minimum = 0,
+ .maximum = SHRT_MAX,
+ .default_num = 40,
+ .text = "Maximum width of the right side of the status line."
+ },
+
+ { .name = "status-right-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the right side of the status line."
+ },
+
+ { .name = "status-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .default_str = "bg=green,fg=black",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the status line."
+ },
+
+ { .name = "update-environment",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .default_str = "DISPLAY KRB5CCNAME SSH_ASKPASS SSH_AUTH_SOCK "
+ "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY",
+ .text = "List of environment variables to update in the session "
+ "environment when a client is attached."
+ },
+
+ { .name = "visual-activity",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_visual_bell_list,
+ .default_num = VISUAL_OFF,
+ .text = "How activity alerts should be shown: a message ('on'), "
+ "a message and a bell ('both') or nothing ('off')."
+ },
+
+ { .name = "visual-bell",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_visual_bell_list,
+ .default_num = VISUAL_OFF,
+ .text = "How bell alerts should be shown: a message ('on'), "
+ "a message and a bell ('both') or nothing ('off')."
+ },
+
+ { .name = "visual-silence",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SESSION,
+ .choices = options_table_visual_bell_list,
+ .default_num = VISUAL_OFF,
+ .text = "How silence alerts should be shown: a message ('on'), "
+ "a message and a bell ('both') or nothing ('off')."
+ },
+
+ { .name = "word-separators",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_SESSION,
+ /*
+ * The set of non-alphanumeric printable ASCII characters minus the
+ * underscore.
+ */
+ .default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~",
+ .text = "Characters considered to separate words."
+ },
+
+ /* Window options. */
+ { .name = "aggressive-resize",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 0,
+ .text = "When 'window-size' is 'smallest', whether the maximum size "
+ "of a window is the smallest attached session where it is "
+ "the current window ('on') or the smallest session it is "
+ "linked to ('off')."
+ },
+
+ { .name = "allow-passthrough",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_num = 0,
+ .text = "Whether applications are allowed to use the escape sequence "
+ "to bypass tmux."
+ },
+
+ { .name = "allow-rename",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_num = 0,
+ .text = "Whether applications are allowed to use the escape sequence "
+ "to rename windows."
+ },
+
+ { .name = "alternate-screen",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_num = 1,
+ .text = "Whether applications are allowed to use the alternate "
+ "screen."
+ },
+
+ { .name = "automatic-rename",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 1,
+ .text = "Whether windows are automatically renamed."
+ },
+
+ { .name = "automatic-rename-format",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "#{?pane_in_mode,[tmux],#{pane_current_command}}"
+ "#{?pane_dead,[dead],}",
+ .text = "Format used to automatically rename windows."
+ },
+
+ { .name = "clock-mode-colour",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 4,
+ .text = "Colour of the clock in clock mode."
+ },
+
+ { .name = "clock-mode-style",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_clock_mode_style_list,
+ .default_num = 1,
+ .text = "Time format of the clock in clock mode."
+ },
+
+ { .name = "copy-mode-match-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "bg=cyan,fg=black",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of search matches in copy mode."
+ },
+
+ { .name = "copy-mode-current-match-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "bg=magenta,fg=black",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the current search match in copy mode."
+ },
+
+ { .name = "copy-mode-mark-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "bg=red,fg=black",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the marked line in copy mode."
+ },
+
+ { .name = "fill-character",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "",
+ .text = "Character used to fill unused parts of window."
+ },
+
+ { .name = "main-pane-height",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "24",
+ .text = "Height of the main pane in the 'main-horizontal' layout. "
+ "This may be a percentage, for example '10%'."
+ },
+
+ { .name = "main-pane-width",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "80",
+ .text = "Width of the main pane in the 'main-vertical' layout. "
+ "This may be a percentage, for example '10%'."
+ },
+
+ { .name = "mode-keys",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_mode_keys_list,
+ .default_num = MODEKEY_EMACS,
+ .text = "Key set used in copy mode."
+ },
+
+ { .name = "mode-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "bg=yellow,fg=black",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of indicators and highlighting in modes."
+ },
+
+ { .name = "monitor-activity",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 0,
+ .text = "Whether an alert is triggered by activity."
+ },
+
+ { .name = "monitor-bell",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 1,
+ .text = "Whether an alert is triggered by a bell."
+ },
+
+ { .name = "monitor-silence",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .minimum = 0,
+ .maximum = INT_MAX,
+ .default_num = 0,
+ .text = "Time after which an alert is triggered by silence. "
+ "Zero means no alert."
+
+ },
+
+ { .name = "other-pane-height",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "0",
+ .text = "Height of the other panes in the 'main-horizontal' layout. "
+ "This may be a percentage, for example '10%'."
+ },
+
+ { .name = "other-pane-width",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "0",
+ .text = "Height of the other panes in the 'main-vertical' layout. "
+ "This may be a percentage, for example '10%'."
+ },
+
+ { .name = "pane-active-border-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the active pane border."
+ },
+
+ { .name = "pane-base-index",
+ .type = OPTIONS_TABLE_NUMBER,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .minimum = 0,
+ .maximum = USHRT_MAX,
+ .default_num = 0,
+ .text = "Index of the first pane in each window."
+ },
+
+ { .name = "pane-border-format",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_str = "#{?pane_active,#[reverse],}#{pane_index}#[default] "
+ "\"#{pane_title}\"",
+ .text = "Format of text in the pane status lines."
+ },
+
+ { .name = "pane-border-indicators",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_pane_border_indicators_list,
+ .default_num = PANE_BORDER_COLOUR,
+ .text = "Whether to indicate the active pane by colouring border or "
+ "displaying arrow markers."
+ },
+
+ { .name = "pane-border-lines",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_pane_border_lines_list,
+ .default_num = PANE_LINES_SINGLE,
+ .text = "Type of characters used to draw pane border lines. Some of "
+ "these are only supported on terminals with UTF-8 support."
+ },
+
+ { .name = "pane-border-status",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_pane_status_list,
+ .default_num = PANE_STATUS_OFF,
+ .text = "Position of the pane status lines."
+ },
+
+ { .name = "pane-border-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the pane status lines."
+ },
+
+ { .name = "pane-colours",
+ .type = OPTIONS_TABLE_COLOUR,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_str = "",
+ .flags = OPTIONS_TABLE_IS_ARRAY,
+ .text = "The default colour palette for colours zero to 255."
+ },
+
+ { .name = "popup-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Default style of popups."
+ },
+
+ { .name = "popup-border-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Default style of popup borders."
+ },
+
+ { .name = "popup-border-lines",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_popup_border_lines_list,
+ .default_num = BOX_LINES_SINGLE,
+ .text = "Type of characters used to draw popup border lines. Some of "
+ "these are only supported on terminals with UTF-8 support."
+ },
+
+ { .name = "remain-on-exit",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .choices = options_table_remain_on_exit_list,
+ .default_num = 0,
+ .text = "Whether panes should remain ('on') or be automatically "
+ "killed ('off' or 'failed') when the program inside exits."
+ },
+
+ { .name = "remain-on-exit-format",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_str = "Pane is dead ("
+ "#{?#{!=:#{pane_dead_status},},"
+ "status #{pane_dead_status},}"
+ "#{?#{!=:#{pane_dead_signal},},"
+ "signal #{pane_dead_signal},}, "
+ "#{t:pane_dead_time})",
+ .text = "Message shown after the program in a pane has exited, if "
+ "remain-on-exit is enabled."
+ },
+
+ { .name = "scroll-on-clear",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_num = 1,
+ .text = "Whether the contents of the screen should be scrolled into"
+ "history when clearing the whole screen."
+ },
+
+ { .name = "synchronize-panes",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_num = 0,
+ .text = "Whether typing should be sent to all panes simultaneously."
+ },
+
+ { .name = "window-active-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Default style of the active pane."
+ },
+
+ { .name = "window-size",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .choices = options_table_window_size_list,
+ .default_num = WINDOW_SIZE_LATEST,
+ .text = "How window size is calculated. "
+ "'latest' uses the size of the most recently used client, "
+ "'largest' the largest client, 'smallest' the smallest "
+ "client and 'manual' a size set by the 'resize-window' "
+ "command."
+ },
+
+ { .name = "window-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Default style of panes that are not the active pane."
+ },
+
+ { .name = "window-status-activity-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "reverse",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of windows in the status line with an activity alert."
+ },
+
+ { .name = "window-status-bell-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "reverse",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of windows in the status line with a bell alert."
+ },
+
+ { .name = "window-status-current-format",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "#I:#W#{?window_flags,#{window_flags}, }",
+ .text = "Format of the current window in the status line."
+ },
+
+ { .name = "window-status-current-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the current window in the status line."
+ },
+
+ { .name = "window-status-format",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "#I:#W#{?window_flags,#{window_flags}, }",
+ .text = "Format of windows in the status line, except the current "
+ "window."
+ },
+
+ { .name = "window-status-last-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of the last window in the status line."
+ },
+
+ { .name = "window-status-separator",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = " ",
+ .text = "Separator between windows in the status line."
+ },
+
+ { .name = "window-status-style",
+ .type = OPTIONS_TABLE_STRING,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_str = "default",
+ .flags = OPTIONS_TABLE_IS_STYLE,
+ .separator = ",",
+ .text = "Style of windows in the status line, except the current and "
+ "last windows."
+ },
+
+ { .name = "wrap-search",
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 1,
+ .text = "Whether searching in copy mode should wrap at the top or "
+ "bottom."
+ },
+
+ { .name = "xterm-keys", /* no longer used */
+ .type = OPTIONS_TABLE_FLAG,
+ .scope = OPTIONS_TABLE_WINDOW,
+ .default_num = 1,
+ .text = "Whether xterm-style function key sequences should be sent. "
+ "This option is no longer used."
+ },
+
+ /* Hook options. */
+ OPTIONS_TABLE_HOOK("after-bind-key", ""),
+ OPTIONS_TABLE_HOOK("after-capture-pane", ""),
+ OPTIONS_TABLE_HOOK("after-copy-mode", ""),
+ OPTIONS_TABLE_HOOK("after-display-message", ""),
+ OPTIONS_TABLE_HOOK("after-display-panes", ""),
+ OPTIONS_TABLE_HOOK("after-kill-pane", ""),
+ OPTIONS_TABLE_HOOK("after-list-buffers", ""),
+ OPTIONS_TABLE_HOOK("after-list-clients", ""),
+ OPTIONS_TABLE_HOOK("after-list-keys", ""),
+ OPTIONS_TABLE_HOOK("after-list-panes", ""),
+ OPTIONS_TABLE_HOOK("after-list-sessions", ""),
+ OPTIONS_TABLE_HOOK("after-list-windows", ""),
+ OPTIONS_TABLE_HOOK("after-load-buffer", ""),
+ OPTIONS_TABLE_HOOK("after-lock-server", ""),
+ OPTIONS_TABLE_HOOK("after-new-session", ""),
+ OPTIONS_TABLE_HOOK("after-new-window", ""),
+ OPTIONS_TABLE_HOOK("after-paste-buffer", ""),
+ OPTIONS_TABLE_HOOK("after-pipe-pane", ""),
+ OPTIONS_TABLE_HOOK("after-queue", ""),
+ OPTIONS_TABLE_HOOK("after-refresh-client", ""),
+ OPTIONS_TABLE_HOOK("after-rename-session", ""),
+ OPTIONS_TABLE_HOOK("after-rename-window", ""),
+ OPTIONS_TABLE_HOOK("after-resize-pane", ""),
+ OPTIONS_TABLE_HOOK("after-resize-window", ""),
+ OPTIONS_TABLE_HOOK("after-save-buffer", ""),
+ OPTIONS_TABLE_HOOK("after-select-layout", ""),
+ OPTIONS_TABLE_HOOK("after-select-pane", ""),
+ OPTIONS_TABLE_HOOK("after-select-window", ""),
+ OPTIONS_TABLE_HOOK("after-send-keys", ""),
+ OPTIONS_TABLE_HOOK("after-set-buffer", ""),
+ OPTIONS_TABLE_HOOK("after-set-environment", ""),
+ OPTIONS_TABLE_HOOK("after-set-hook", ""),
+ OPTIONS_TABLE_HOOK("after-set-option", ""),
+ OPTIONS_TABLE_HOOK("after-show-environment", ""),
+ OPTIONS_TABLE_HOOK("after-show-messages", ""),
+ OPTIONS_TABLE_HOOK("after-show-options", ""),
+ OPTIONS_TABLE_HOOK("after-split-window", ""),
+ OPTIONS_TABLE_HOOK("after-unbind-key", ""),
+ OPTIONS_TABLE_HOOK("alert-activity", ""),
+ OPTIONS_TABLE_HOOK("alert-bell", ""),
+ OPTIONS_TABLE_HOOK("alert-silence", ""),
+ OPTIONS_TABLE_HOOK("client-active", ""),
+ OPTIONS_TABLE_HOOK("client-attached", ""),
+ OPTIONS_TABLE_HOOK("client-detached", ""),
+ OPTIONS_TABLE_HOOK("client-focus-in", ""),
+ OPTIONS_TABLE_HOOK("client-focus-out", ""),
+ OPTIONS_TABLE_HOOK("client-resized", ""),
+ OPTIONS_TABLE_HOOK("client-session-changed", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-died", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-exited", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-focus-in", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-focus-out", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-mode-changed", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-set-clipboard", ""),
+ OPTIONS_TABLE_PANE_HOOK("pane-title-changed", ""),
+ OPTIONS_TABLE_HOOK("session-closed", ""),
+ OPTIONS_TABLE_HOOK("session-created", ""),
+ OPTIONS_TABLE_HOOK("session-renamed", ""),
+ OPTIONS_TABLE_HOOK("session-window-changed", ""),
+ OPTIONS_TABLE_WINDOW_HOOK("window-layout-changed", ""),
+ OPTIONS_TABLE_HOOK("window-linked", ""),
+ OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""),
+ OPTIONS_TABLE_WINDOW_HOOK("window-renamed", ""),
+ OPTIONS_TABLE_WINDOW_HOOK("window-resized", ""),
+ OPTIONS_TABLE_HOOK("window-unlinked", ""),
+
+ { .name = NULL }
+};
diff --git a/options.c b/options.c
new file mode 100644
index 0000000..f9cf2af
--- /dev/null
+++ b/options.c
@@ -0,0 +1,1193 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 <ctype.h>
+#include <fnmatch.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Option handling; each option has a name, type and value and is stored in
+ * a red-black tree.
+ */
+
+struct options_array_item {
+ u_int index;
+ union options_value value;
+ RB_ENTRY(options_array_item) entry;
+};
+static int
+options_array_cmp(struct options_array_item *a1, struct options_array_item *a2)
+{
+ if (a1->index < a2->index)
+ return (-1);
+ if (a1->index > a2->index)
+ return (1);
+ return (0);
+}
+RB_GENERATE_STATIC(options_array, options_array_item, entry, options_array_cmp);
+
+struct options_entry {
+ struct options *owner;
+
+ const char *name;
+ const struct options_table_entry *tableentry;
+ union options_value value;
+
+ int cached;
+ struct style style;
+
+ RB_ENTRY(options_entry) entry;
+};
+
+struct options {
+ RB_HEAD(options_tree, options_entry) tree;
+ struct options *parent;
+};
+
+static struct options_entry *options_add(struct options *, const char *);
+static void options_remove(struct options_entry *);
+
+#define OPTIONS_IS_STRING(o) \
+ ((o)->tableentry == NULL || \
+ (o)->tableentry->type == OPTIONS_TABLE_STRING)
+#define OPTIONS_IS_NUMBER(o) \
+ ((o)->tableentry != NULL && \
+ ((o)->tableentry->type == OPTIONS_TABLE_NUMBER || \
+ (o)->tableentry->type == OPTIONS_TABLE_KEY || \
+ (o)->tableentry->type == OPTIONS_TABLE_COLOUR || \
+ (o)->tableentry->type == OPTIONS_TABLE_FLAG || \
+ (o)->tableentry->type == OPTIONS_TABLE_CHOICE))
+#define OPTIONS_IS_COMMAND(o) \
+ ((o)->tableentry != NULL && \
+ (o)->tableentry->type == OPTIONS_TABLE_COMMAND)
+
+#define OPTIONS_IS_ARRAY(o) \
+ ((o)->tableentry != NULL && \
+ ((o)->tableentry->flags & OPTIONS_TABLE_IS_ARRAY))
+
+static int options_cmp(struct options_entry *, struct options_entry *);
+RB_GENERATE_STATIC(options_tree, options_entry, entry, options_cmp);
+
+static int
+options_cmp(struct options_entry *lhs, struct options_entry *rhs)
+{
+ return (strcmp(lhs->name, rhs->name));
+}
+
+static const char *
+options_map_name(const char *name)
+{
+ const struct options_name_map *map;
+
+ for (map = options_other_names; map->from != NULL; map++) {
+ if (strcmp(map->from, name) == 0)
+ return (map->to);
+ }
+ return (name);
+}
+
+static const struct options_table_entry *
+options_parent_table_entry(struct options *oo, const char *s)
+{
+ struct options_entry *o;
+
+ if (oo->parent == NULL)
+ fatalx("no parent options for %s", s);
+ o = options_get(oo->parent, s);
+ if (o == NULL)
+ fatalx("%s not in parent options", s);
+ return (o->tableentry);
+}
+
+static void
+options_value_free(struct options_entry *o, union options_value *ov)
+{
+ if (OPTIONS_IS_STRING(o))
+ free(ov->string);
+ if (OPTIONS_IS_COMMAND(o) && ov->cmdlist != NULL)
+ cmd_list_free(ov->cmdlist);
+}
+
+static char *
+options_value_to_string(struct options_entry *o, union options_value *ov,
+ int numeric)
+{
+ char *s;
+
+ if (OPTIONS_IS_COMMAND(o))
+ return (cmd_list_print(ov->cmdlist, 0));
+ if (OPTIONS_IS_NUMBER(o)) {
+ switch (o->tableentry->type) {
+ case OPTIONS_TABLE_NUMBER:
+ xasprintf(&s, "%lld", ov->number);
+ break;
+ case OPTIONS_TABLE_KEY:
+ s = xstrdup(key_string_lookup_key(ov->number, 0));
+ break;
+ case OPTIONS_TABLE_COLOUR:
+ s = xstrdup(colour_tostring(ov->number));
+ break;
+ case OPTIONS_TABLE_FLAG:
+ if (numeric)
+ xasprintf(&s, "%lld", ov->number);
+ else
+ s = xstrdup(ov->number ? "on" : "off");
+ break;
+ case OPTIONS_TABLE_CHOICE:
+ s = xstrdup(o->tableentry->choices[ov->number]);
+ break;
+ default:
+ fatalx("not a number option type");
+ }
+ return (s);
+ }
+ if (OPTIONS_IS_STRING(o))
+ return (xstrdup(ov->string));
+ return (xstrdup(""));
+}
+
+struct options *
+options_create(struct options *parent)
+{
+ struct options *oo;
+
+ oo = xcalloc(1, sizeof *oo);
+ RB_INIT(&oo->tree);
+ oo->parent = parent;
+ return (oo);
+}
+
+void
+options_free(struct options *oo)
+{
+ struct options_entry *o, *tmp;
+
+ RB_FOREACH_SAFE(o, options_tree, &oo->tree, tmp)
+ options_remove(o);
+ free(oo);
+}
+
+struct options *
+options_get_parent(struct options *oo)
+{
+ return (oo->parent);
+}
+
+void
+options_set_parent(struct options *oo, struct options *parent)
+{
+ oo->parent = parent;
+}
+
+struct options_entry *
+options_first(struct options *oo)
+{
+ return (RB_MIN(options_tree, &oo->tree));
+}
+
+struct options_entry *
+options_next(struct options_entry *o)
+{
+ return (RB_NEXT(options_tree, &oo->tree, o));
+}
+
+struct options_entry *
+options_get_only(struct options *oo, const char *name)
+{
+ struct options_entry o = { .name = name }, *found;
+
+ found = RB_FIND(options_tree, &oo->tree, &o);
+ if (found == NULL) {
+ o.name = options_map_name(name);
+ return (RB_FIND(options_tree, &oo->tree, &o));
+ }
+ return (found);
+}
+
+struct options_entry *
+options_get(struct options *oo, const char *name)
+{
+ struct options_entry *o;
+
+ o = options_get_only(oo, name);
+ while (o == NULL) {
+ oo = oo->parent;
+ if (oo == NULL)
+ break;
+ o = options_get_only(oo, name);
+ }
+ return (o);
+}
+
+struct options_entry *
+options_empty(struct options *oo, const struct options_table_entry *oe)
+{
+ struct options_entry *o;
+
+ o = options_add(oo, oe->name);
+ o->tableentry = oe;
+
+ if (oe->flags & OPTIONS_TABLE_IS_ARRAY)
+ RB_INIT(&o->value.array);
+
+ return (o);
+}
+
+struct options_entry *
+options_default(struct options *oo, const struct options_table_entry *oe)
+{
+ struct options_entry *o;
+ union options_value *ov;
+ u_int i;
+
+ o = options_empty(oo, oe);
+ ov = &o->value;
+
+ if (oe->flags & OPTIONS_TABLE_IS_ARRAY) {
+ if (oe->default_arr == NULL) {
+ options_array_assign(o, oe->default_str, NULL);
+ return (o);
+ }
+ for (i = 0; oe->default_arr[i] != NULL; i++)
+ options_array_set(o, i, oe->default_arr[i], 0, NULL);
+ return (o);
+ }
+
+ switch (oe->type) {
+ case OPTIONS_TABLE_STRING:
+ ov->string = xstrdup(oe->default_str);
+ break;
+ default:
+ ov->number = oe->default_num;
+ break;
+ }
+ return (o);
+}
+
+char *
+options_default_to_string(const struct options_table_entry *oe)
+{
+ char *s;
+
+ switch (oe->type) {
+ case OPTIONS_TABLE_STRING:
+ case OPTIONS_TABLE_COMMAND:
+ s = xstrdup(oe->default_str);
+ break;
+ case OPTIONS_TABLE_NUMBER:
+ xasprintf(&s, "%lld", oe->default_num);
+ break;
+ case OPTIONS_TABLE_KEY:
+ s = xstrdup(key_string_lookup_key(oe->default_num, 0));
+ break;
+ case OPTIONS_TABLE_COLOUR:
+ s = xstrdup(colour_tostring(oe->default_num));
+ break;
+ case OPTIONS_TABLE_FLAG:
+ s = xstrdup(oe->default_num ? "on" : "off");
+ break;
+ case OPTIONS_TABLE_CHOICE:
+ s = xstrdup(oe->choices[oe->default_num]);
+ break;
+ default:
+ fatalx("unknown option type");
+ }
+ return (s);
+}
+
+static struct options_entry *
+options_add(struct options *oo, const char *name)
+{
+ struct options_entry *o;
+
+ o = options_get_only(oo, name);
+ if (o != NULL)
+ options_remove(o);
+
+ o = xcalloc(1, sizeof *o);
+ o->owner = oo;
+ o->name = xstrdup(name);
+
+ RB_INSERT(options_tree, &oo->tree, o);
+ return (o);
+}
+
+static void
+options_remove(struct options_entry *o)
+{
+ struct options *oo = o->owner;
+
+ if (OPTIONS_IS_ARRAY(o))
+ options_array_clear(o);
+ else
+ options_value_free(o, &o->value);
+ RB_REMOVE(options_tree, &oo->tree, o);
+ free((void *)o->name);
+ free(o);
+}
+
+const char *
+options_name(struct options_entry *o)
+{
+ return (o->name);
+}
+
+struct options *
+options_owner(struct options_entry *o)
+{
+ return (o->owner);
+}
+
+const struct options_table_entry *
+options_table_entry(struct options_entry *o)
+{
+ return (o->tableentry);
+}
+
+static struct options_array_item *
+options_array_item(struct options_entry *o, u_int idx)
+{
+ struct options_array_item a;
+
+ a.index = idx;
+ return (RB_FIND(options_array, &o->value.array, &a));
+}
+
+static struct options_array_item *
+options_array_new(struct options_entry *o, u_int idx)
+{
+ struct options_array_item *a;
+
+ a = xcalloc(1, sizeof *a);
+ a->index = idx;
+ RB_INSERT(options_array, &o->value.array, a);
+ return (a);
+}
+
+static void
+options_array_free(struct options_entry *o, struct options_array_item *a)
+{
+ options_value_free(o, &a->value);
+ RB_REMOVE(options_array, &o->value.array, a);
+ free(a);
+}
+
+void
+options_array_clear(struct options_entry *o)
+{
+ struct options_array_item *a, *a1;
+
+ if (!OPTIONS_IS_ARRAY(o))
+ return;
+
+ RB_FOREACH_SAFE(a, options_array, &o->value.array, a1)
+ options_array_free(o, a);
+}
+
+union options_value *
+options_array_get(struct options_entry *o, u_int idx)
+{
+ struct options_array_item *a;
+
+ if (!OPTIONS_IS_ARRAY(o))
+ return (NULL);
+ a = options_array_item(o, idx);
+ if (a == NULL)
+ return (NULL);
+ return (&a->value);
+}
+
+int
+options_array_set(struct options_entry *o, u_int idx, const char *value,
+ int append, char **cause)
+{
+ struct options_array_item *a;
+ char *new;
+ struct cmd_parse_result *pr;
+ long long number;
+
+ if (!OPTIONS_IS_ARRAY(o)) {
+ if (cause != NULL)
+ *cause = xstrdup("not an array");
+ return (-1);
+ }
+
+ if (value == NULL) {
+ a = options_array_item(o, idx);
+ if (a != NULL)
+ options_array_free(o, a);
+ return (0);
+ }
+
+ if (OPTIONS_IS_COMMAND(o)) {
+ pr = cmd_parse_from_string(value, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ if (cause != NULL)
+ *cause = pr->error;
+ else
+ free(pr->error);
+ return (-1);
+ case CMD_PARSE_SUCCESS:
+ break;
+ }
+
+ a = options_array_item(o, idx);
+ if (a == NULL)
+ a = options_array_new(o, idx);
+ else
+ options_value_free(o, &a->value);
+ a->value.cmdlist = pr->cmdlist;
+ return (0);
+ }
+
+ if (OPTIONS_IS_STRING(o)) {
+ a = options_array_item(o, idx);
+ if (a != NULL && append)
+ xasprintf(&new, "%s%s", a->value.string, value);
+ else
+ new = xstrdup(value);
+ if (a == NULL)
+ a = options_array_new(o, idx);
+ else
+ options_value_free(o, &a->value);
+ a->value.string = new;
+ return (0);
+ }
+
+ if (o->tableentry->type == OPTIONS_TABLE_COLOUR) {
+ if ((number = colour_fromstring(value)) == -1) {
+ xasprintf(cause, "bad colour: %s", value);
+ return (-1);
+ }
+ a = options_array_item(o, idx);
+ if (a == NULL)
+ a = options_array_new(o, idx);
+ else
+ options_value_free(o, &a->value);
+ a->value.number = number;
+ return (0);
+ }
+
+ if (cause != NULL)
+ *cause = xstrdup("wrong array type");
+ return (-1);
+}
+
+int
+options_array_assign(struct options_entry *o, const char *s, char **cause)
+{
+ const char *separator;
+ char *copy, *next, *string;
+ u_int i;
+
+ separator = o->tableentry->separator;
+ if (separator == NULL)
+ separator = " ,";
+ if (*separator == '\0') {
+ if (*s == '\0')
+ return (0);
+ for (i = 0; i < UINT_MAX; i++) {
+ if (options_array_item(o, i) == NULL)
+ break;
+ }
+ return (options_array_set(o, i, s, 0, cause));
+ }
+
+ if (*s == '\0')
+ return (0);
+ copy = string = xstrdup(s);
+ while ((next = strsep(&string, separator)) != NULL) {
+ if (*next == '\0')
+ continue;
+ for (i = 0; i < UINT_MAX; i++) {
+ if (options_array_item(o, i) == NULL)
+ break;
+ }
+ if (i == UINT_MAX)
+ break;
+ if (options_array_set(o, i, next, 0, cause) != 0) {
+ free(copy);
+ return (-1);
+ }
+ }
+ free(copy);
+ return (0);
+}
+
+struct options_array_item *
+options_array_first(struct options_entry *o)
+{
+ if (!OPTIONS_IS_ARRAY(o))
+ return (NULL);
+ return (RB_MIN(options_array, &o->value.array));
+}
+
+struct options_array_item *
+options_array_next(struct options_array_item *a)
+{
+ return (RB_NEXT(options_array, &o->value.array, a));
+}
+
+u_int
+options_array_item_index(struct options_array_item *a)
+{
+ return (a->index);
+}
+
+union options_value *
+options_array_item_value(struct options_array_item *a)
+{
+ return (&a->value);
+}
+
+int
+options_is_array(struct options_entry *o)
+{
+ return (OPTIONS_IS_ARRAY(o));
+}
+
+int
+options_is_string(struct options_entry *o)
+{
+ return (OPTIONS_IS_STRING(o));
+}
+
+char *
+options_to_string(struct options_entry *o, int idx, int numeric)
+{
+ struct options_array_item *a;
+
+ if (OPTIONS_IS_ARRAY(o)) {
+ if (idx == -1)
+ return (xstrdup(""));
+ a = options_array_item(o, idx);
+ if (a == NULL)
+ return (xstrdup(""));
+ return (options_value_to_string(o, &a->value, numeric));
+ }
+ return (options_value_to_string(o, &o->value, numeric));
+}
+
+char *
+options_parse(const char *name, int *idx)
+{
+ char *copy, *cp, *end;
+
+ if (*name == '\0')
+ return (NULL);
+ copy = xstrdup(name);
+ if ((cp = strchr(copy, '[')) == NULL) {
+ *idx = -1;
+ return (copy);
+ }
+ end = strchr(cp + 1, ']');
+ if (end == NULL || end[1] != '\0' || !isdigit((u_char)end[-1])) {
+ free(copy);
+ return (NULL);
+ }
+ if (sscanf(cp, "[%d]", idx) != 1 || *idx < 0) {
+ free(copy);
+ return (NULL);
+ }
+ *cp = '\0';
+ return (copy);
+}
+
+struct options_entry *
+options_parse_get(struct options *oo, const char *s, int *idx, int only)
+{
+ struct options_entry *o;
+ char *name;
+
+ name = options_parse(s, idx);
+ if (name == NULL)
+ return (NULL);
+ if (only)
+ o = options_get_only(oo, name);
+ else
+ o = options_get(oo, name);
+ free(name);
+ return (o);
+}
+
+char *
+options_match(const char *s, int *idx, int *ambiguous)
+{
+ const struct options_table_entry *oe, *found;
+ char *parsed;
+ const char *name;
+ size_t namelen;
+
+ parsed = options_parse(s, idx);
+ if (parsed == NULL)
+ return (NULL);
+ if (*parsed == '@') {
+ *ambiguous = 0;
+ return (parsed);
+ }
+
+ name = options_map_name(parsed);
+ namelen = strlen(name);
+
+ found = NULL;
+ for (oe = options_table; oe->name != NULL; oe++) {
+ if (strcmp(oe->name, name) == 0) {
+ found = oe;
+ break;
+ }
+ if (strncmp(oe->name, name, namelen) == 0) {
+ if (found != NULL) {
+ *ambiguous = 1;
+ free(parsed);
+ return (NULL);
+ }
+ found = oe;
+ }
+ }
+ free(parsed);
+ if (found == NULL) {
+ *ambiguous = 0;
+ return (NULL);
+ }
+ return (xstrdup(found->name));
+}
+
+struct options_entry *
+options_match_get(struct options *oo, const char *s, int *idx, int only,
+ int *ambiguous)
+{
+ char *name;
+ struct options_entry *o;
+
+ name = options_match(s, idx, ambiguous);
+ if (name == NULL)
+ return (NULL);
+ *ambiguous = 0;
+ if (only)
+ o = options_get_only(oo, name);
+ else
+ o = options_get(oo, name);
+ free(name);
+ return (o);
+}
+
+const char *
+options_get_string(struct options *oo, const char *name)
+{
+ struct options_entry *o;
+
+ o = options_get(oo, name);
+ if (o == NULL)
+ fatalx("missing option %s", name);
+ if (!OPTIONS_IS_STRING(o))
+ fatalx("option %s is not a string", name);
+ return (o->value.string);
+}
+
+long long
+options_get_number(struct options *oo, const char *name)
+{
+ struct options_entry *o;
+
+ o = options_get(oo, name);
+ if (o == NULL)
+ fatalx("missing option %s", name);
+ if (!OPTIONS_IS_NUMBER(o))
+ fatalx("option %s is not a number", name);
+ return (o->value.number);
+}
+
+struct options_entry *
+options_set_string(struct options *oo, const char *name, int append,
+ const char *fmt, ...)
+{
+ struct options_entry *o;
+ va_list ap;
+ const char *separator = "";
+ char *s, *value;
+
+ va_start(ap, fmt);
+ xvasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ o = options_get_only(oo, name);
+ if (o != NULL && append && OPTIONS_IS_STRING(o)) {
+ if (*name != '@') {
+ separator = o->tableentry->separator;
+ if (separator == NULL)
+ separator = "";
+ }
+ xasprintf(&value, "%s%s%s", o->value.string, separator, s);
+ free(s);
+ } else
+ value = s;
+ if (o == NULL && *name == '@')
+ o = options_add(oo, name);
+ else if (o == NULL) {
+ o = options_default(oo, options_parent_table_entry(oo, name));
+ if (o == NULL)
+ return (NULL);
+ }
+
+ if (!OPTIONS_IS_STRING(o))
+ fatalx("option %s is not a string", name);
+ free(o->value.string);
+ o->value.string = value;
+ o->cached = 0;
+ return (o);
+}
+
+struct options_entry *
+options_set_number(struct options *oo, const char *name, long long value)
+{
+ struct options_entry *o;
+
+ if (*name == '@')
+ fatalx("user option %s must be a string", name);
+
+ o = options_get_only(oo, name);
+ if (o == NULL) {
+ o = options_default(oo, options_parent_table_entry(oo, name));
+ if (o == NULL)
+ return (NULL);
+ }
+
+ if (!OPTIONS_IS_NUMBER(o))
+ fatalx("option %s is not a number", name);
+ o->value.number = value;
+ return (o);
+}
+
+int
+options_scope_from_name(struct args *args, int window,
+ const char *name, struct cmd_find_state *fs, struct options **oo,
+ char **cause)
+{
+ struct session *s = fs->s;
+ struct winlink *wl = fs->wl;
+ struct window_pane *wp = fs->wp;
+ const char *target = args_get(args, 't');
+ const struct options_table_entry *oe;
+ int scope = OPTIONS_TABLE_NONE;
+
+ if (*name == '@')
+ return (options_scope_from_flags(args, window, fs, oo, cause));
+
+ for (oe = options_table; oe->name != NULL; oe++) {
+ if (strcmp(oe->name, name) == 0)
+ break;
+ }
+ if (oe->name == NULL) {
+ xasprintf(cause, "unknown option: %s", name);
+ return (OPTIONS_TABLE_NONE);
+ }
+ switch (oe->scope) {
+ case OPTIONS_TABLE_SERVER:
+ *oo = global_options;
+ scope = OPTIONS_TABLE_SERVER;
+ break;
+ case OPTIONS_TABLE_SESSION:
+ if (args_has(args, 'g')) {
+ *oo = global_s_options;
+ scope = OPTIONS_TABLE_SESSION;
+ } else if (s == NULL && target != NULL)
+ xasprintf(cause, "no such session: %s", target);
+ else if (s == NULL)
+ xasprintf(cause, "no current session");
+ else {
+ *oo = s->options;
+ scope = OPTIONS_TABLE_SESSION;
+ }
+ break;
+ case OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE:
+ if (args_has(args, 'p')) {
+ if (wp == NULL && target != NULL)
+ xasprintf(cause, "no such pane: %s", target);
+ else if (wp == NULL)
+ xasprintf(cause, "no current pane");
+ else {
+ *oo = wp->options;
+ scope = OPTIONS_TABLE_PANE;
+ }
+ break;
+ }
+ /* FALLTHROUGH */
+ case OPTIONS_TABLE_WINDOW:
+ if (args_has(args, 'g')) {
+ *oo = global_w_options;
+ scope = OPTIONS_TABLE_WINDOW;
+ } else if (wl == NULL && target != NULL)
+ xasprintf(cause, "no such window: %s", target);
+ else if (wl == NULL)
+ xasprintf(cause, "no current window");
+ else {
+ *oo = wl->window->options;
+ scope = OPTIONS_TABLE_WINDOW;
+ }
+ break;
+ }
+ return (scope);
+}
+
+int
+options_scope_from_flags(struct args *args, int window,
+ struct cmd_find_state *fs, struct options **oo, char **cause)
+{
+ struct session *s = fs->s;
+ struct winlink *wl = fs->wl;
+ struct window_pane *wp = fs->wp;
+ const char *target = args_get(args, 't');
+
+ if (args_has(args, 's')) {
+ *oo = global_options;
+ return (OPTIONS_TABLE_SERVER);
+ }
+
+ if (args_has(args, 'p')) {
+ if (wp == NULL) {
+ if (target != NULL)
+ xasprintf(cause, "no such pane: %s", target);
+ else
+ xasprintf(cause, "no current pane");
+ return (OPTIONS_TABLE_NONE);
+ }
+ *oo = wp->options;
+ return (OPTIONS_TABLE_PANE);
+ } else if (window || args_has(args, 'w')) {
+ if (args_has(args, 'g')) {
+ *oo = global_w_options;
+ return (OPTIONS_TABLE_WINDOW);
+ }
+ if (wl == NULL) {
+ if (target != NULL)
+ xasprintf(cause, "no such window: %s", target);
+ else
+ xasprintf(cause, "no current window");
+ return (OPTIONS_TABLE_NONE);
+ }
+ *oo = wl->window->options;
+ return (OPTIONS_TABLE_WINDOW);
+ } else {
+ if (args_has(args, 'g')) {
+ *oo = global_s_options;
+ return (OPTIONS_TABLE_SESSION);
+ }
+ if (s == NULL) {
+ if (target != NULL)
+ xasprintf(cause, "no such session: %s", target);
+ else
+ xasprintf(cause, "no current session");
+ return (OPTIONS_TABLE_NONE);
+ }
+ *oo = s->options;
+ return (OPTIONS_TABLE_SESSION);
+ }
+}
+
+struct style *
+options_string_to_style(struct options *oo, const char *name,
+ struct format_tree *ft)
+{
+ struct options_entry *o;
+ const char *s;
+ char *expanded;
+
+ o = options_get(oo, name);
+ if (o == NULL || !OPTIONS_IS_STRING(o))
+ return (NULL);
+
+ if (o->cached)
+ return (&o->style);
+ s = o->value.string;
+ log_debug("%s: %s is '%s'", __func__, name, s);
+
+ style_set(&o->style, &grid_default_cell);
+ o->cached = (strstr(s, "#{") == NULL);
+
+ if (ft != NULL && !o->cached) {
+ expanded = format_expand(ft, s);
+ if (style_parse(&o->style, &grid_default_cell, expanded) != 0) {
+ free(expanded);
+ return (NULL);
+ }
+ free(expanded);
+ } else {
+ if (style_parse(&o->style, &grid_default_cell, s) != 0)
+ return (NULL);
+ }
+ return (&o->style);
+}
+
+static int
+options_from_string_check(const struct options_table_entry *oe,
+ const char *value, char **cause)
+{
+ struct style sy;
+
+ if (oe == NULL)
+ return (0);
+ if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) {
+ xasprintf(cause, "not a suitable shell: %s", value);
+ return (-1);
+ }
+ if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) {
+ xasprintf(cause, "value is invalid: %s", value);
+ return (-1);
+ }
+ if ((oe->flags & OPTIONS_TABLE_IS_STYLE) &&
+ strstr(value, "#{") == NULL &&
+ style_parse(&sy, &grid_default_cell, value) != 0) {
+ xasprintf(cause, "invalid style: %s", value);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+options_from_string_flag(struct options *oo, const char *name,
+ const char *value, char **cause)
+{
+ int flag;
+
+ if (value == NULL || *value == '\0')
+ flag = !options_get_number(oo, name);
+ else if (strcmp(value, "1") == 0 ||
+ strcasecmp(value, "on") == 0 ||
+ strcasecmp(value, "yes") == 0)
+ flag = 1;
+ else if (strcmp(value, "0") == 0 ||
+ strcasecmp(value, "off") == 0 ||
+ strcasecmp(value, "no") == 0)
+ flag = 0;
+ else {
+ xasprintf(cause, "bad value: %s", value);
+ return (-1);
+ }
+ options_set_number(oo, name, flag);
+ return (0);
+}
+
+int
+options_find_choice(const struct options_table_entry *oe, const char *value,
+ char **cause)
+{
+ const char **cp;
+ int n = 0, choice = -1;
+
+ for (cp = oe->choices; *cp != NULL; cp++) {
+ if (strcmp(*cp, value) == 0)
+ choice = n;
+ n++;
+ }
+ if (choice == -1) {
+ xasprintf(cause, "unknown value: %s", value);
+ return (-1);
+ }
+ return (choice);
+}
+
+static int
+options_from_string_choice(const struct options_table_entry *oe,
+ struct options *oo, const char *name, const char *value, char **cause)
+{
+ int choice = -1;
+
+ if (value == NULL) {
+ choice = options_get_number(oo, name);
+ if (choice < 2)
+ choice = !choice;
+ } else {
+ choice = options_find_choice(oe, value, cause);
+ if (choice < 0)
+ return (-1);
+ }
+ options_set_number(oo, name, choice);
+ return (0);
+}
+
+int
+options_from_string(struct options *oo, const struct options_table_entry *oe,
+ const char *name, const char *value, int append, char **cause)
+{
+ enum options_table_type type;
+ long long number;
+ const char *errstr, *new;
+ char *old;
+ key_code key;
+
+ if (oe != NULL) {
+ if (value == NULL &&
+ oe->type != OPTIONS_TABLE_FLAG &&
+ oe->type != OPTIONS_TABLE_CHOICE) {
+ xasprintf(cause, "empty value");
+ return (-1);
+ }
+ type = oe->type;
+ } else {
+ if (*name != '@') {
+ xasprintf(cause, "bad option name");
+ return (-1);
+ }
+ type = OPTIONS_TABLE_STRING;
+ }
+
+ switch (type) {
+ case OPTIONS_TABLE_STRING:
+ old = xstrdup(options_get_string(oo, name));
+ options_set_string(oo, name, append, "%s", value);
+
+ new = options_get_string(oo, name);
+ if (options_from_string_check(oe, new, cause) != 0) {
+ options_set_string(oo, name, 0, "%s", old);
+ free(old);
+ return (-1);
+ }
+ free(old);
+ return (0);
+ case OPTIONS_TABLE_NUMBER:
+ number = strtonum(value, oe->minimum, oe->maximum, &errstr);
+ if (errstr != NULL) {
+ xasprintf(cause, "value is %s: %s", errstr, value);
+ return (-1);
+ }
+ options_set_number(oo, name, number);
+ return (0);
+ case OPTIONS_TABLE_KEY:
+ key = key_string_lookup_string(value);
+ if (key == KEYC_UNKNOWN) {
+ xasprintf(cause, "bad key: %s", value);
+ return (-1);
+ }
+ options_set_number(oo, name, key);
+ return (0);
+ case OPTIONS_TABLE_COLOUR:
+ if ((number = colour_fromstring(value)) == -1) {
+ xasprintf(cause, "bad colour: %s", value);
+ return (-1);
+ }
+ options_set_number(oo, name, number);
+ return (0);
+ case OPTIONS_TABLE_FLAG:
+ return (options_from_string_flag(oo, name, value, cause));
+ case OPTIONS_TABLE_CHOICE:
+ return (options_from_string_choice(oe, oo, name, value, cause));
+ case OPTIONS_TABLE_COMMAND:
+ break;
+ }
+ return (-1);
+}
+
+void
+options_push_changes(const char *name)
+{
+ struct client *loop;
+ struct session *s;
+ struct window *w;
+ struct window_pane *wp;
+ int c;
+
+ log_debug("%s: %s", __func__, name);
+
+ if (strcmp(name, "automatic-rename") == 0) {
+ RB_FOREACH(w, windows, &windows) {
+ if (w->active == NULL)
+ continue;
+ if (options_get_number(w->options, name))
+ w->active->flags |= PANE_CHANGED;
+ }
+ }
+ 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;
+ }
+ }
+ 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);
+ }
+ }
+ if (strcmp(name, "fill-character") == 0) {
+ RB_FOREACH(w, windows, &windows)
+ window_set_fill_character(w);
+ }
+ if (strcmp(name, "key-table") == 0) {
+ TAILQ_FOREACH(loop, &clients, entry)
+ server_client_set_key_table(loop, NULL);
+ }
+ if (strcmp(name, "user-keys") == 0) {
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop->tty.flags & TTY_OPENED)
+ tty_keys_build(&loop->tty);
+ }
+ }
+ if (strcmp(name, "status") == 0 ||
+ strcmp(name, "status-interval") == 0)
+ status_timer_start_all();
+ if (strcmp(name, "monitor-silence") == 0)
+ alerts_reset_all();
+ if (strcmp(name, "window-style") == 0 ||
+ strcmp(name, "window-active-style") == 0) {
+ RB_FOREACH(wp, window_pane_tree, &all_window_panes)
+ wp->flags |= PANE_STYLECHANGED;
+ }
+ if (strcmp(name, "pane-colours") == 0) {
+ RB_FOREACH(wp, window_pane_tree, &all_window_panes)
+ colour_palette_from_option(&wp->palette, wp->options);
+ }
+ if (strcmp(name, "pane-border-status") == 0) {
+ RB_FOREACH(w, windows, &windows)
+ layout_fix_panes(w, NULL);
+ }
+ RB_FOREACH(s, sessions, &sessions)
+ status_update_cache(s);
+
+ recalculate_sizes();
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop->session != NULL)
+ server_redraw_client(loop);
+ }
+}
+
+int
+options_remove_or_default(struct options_entry *o, int idx, char **cause)
+{
+ struct options *oo = o->owner;
+
+ if (idx == -1) {
+ if (o->tableentry != NULL &&
+ (oo == global_options ||
+ oo == global_s_options ||
+ oo == global_w_options))
+ options_default(oo, o->tableentry);
+ else
+ options_remove(o);
+ } else if (options_array_set(o, idx, NULL, 0, cause) != 0)
+ return (-1);
+ return (0);
+}
diff --git a/osdep-aix.c b/osdep-aix.c
new file mode 100644
index 0000000..e1ce491
--- /dev/null
+++ b/osdep-aix.c
@@ -0,0 +1,95 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2011 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/param.h>
+#include <sys/procfs.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "tmux.h"
+
+char *
+osdep_get_name(__unused int fd, char *tty)
+{
+ struct psinfo p;
+ char *path;
+ ssize_t bytes;
+ int f, ttyfd, retval;
+ pid_t pgrp;
+
+ if ((ttyfd = open(tty, O_RDONLY|O_NOCTTY)) == -1)
+ return (NULL);
+
+ retval = ioctl(ttyfd, TIOCGPGRP, &pgrp);
+ close(ttyfd);
+ if (retval == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%u/psinfo", (u_int) pgrp);
+ f = open(path, O_RDONLY);
+ free(path);
+ if (f < 0)
+ return (NULL);
+
+ bytes = read(f, &p, sizeof(p));
+ close(f);
+ if (bytes != sizeof(p))
+ return (NULL);
+
+ return (xstrdup(p.pr_fname));
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ static char target[MAXPATHLEN + 1];
+ char *path;
+ const char *ttypath;
+ ssize_t n;
+ pid_t pgrp;
+ int len, retval, ttyfd;
+
+ if ((ttypath = ptsname(fd)) == NULL)
+ return (NULL);
+ if ((ttyfd = open(ttypath, O_RDONLY|O_NOCTTY)) == -1)
+ return (NULL);
+
+ retval = ioctl(ttyfd, TIOCGPGRP, &pgrp);
+ close(ttyfd);
+ if (retval == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%u/cwd", (u_int) pgrp);
+ n = readlink(path, target, MAXPATHLEN);
+ free(path);
+ if (n > 0) {
+ target[n] = '\0';
+ if ((len = strlen(target)) > 1 && target[len - 1] == '/')
+ target[len - 1] = '\0';
+ return (target);
+ }
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-cygwin.c b/osdep-cygwin.c
new file mode 100644
index 0000000..4346373
--- /dev/null
+++ b/osdep-cygwin.c
@@ -0,0 +1,87 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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/param.h>
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+char *
+osdep_get_name(int fd, __unused char *tty)
+{
+ FILE *f;
+ char *path, *buf;
+ size_t len;
+ int ch;
+ pid_t pgrp;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%lld/cmdline", (long long) pgrp);
+ if ((f = fopen(path, "r")) == NULL) {
+ free(path);
+ return (NULL);
+ }
+ free(path);
+
+ len = 0;
+ buf = NULL;
+ while ((ch = fgetc(f)) != EOF) {
+ if (ch == '\0')
+ break;
+ buf = xrealloc(buf, len + 2);
+ buf[len++] = ch;
+ }
+ if (buf != NULL)
+ buf[len] = '\0';
+
+ fclose(f);
+ return (buf);
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ static char target[MAXPATHLEN + 1];
+ char *path;
+ pid_t pgrp;
+ ssize_t n;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%lld/cwd", (long long) pgrp);
+ n = readlink(path, target, MAXPATHLEN);
+ free(path);
+ if (n > 0) {
+ target[n] = '\0';
+ return (target);
+ }
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-darwin.c b/osdep-darwin.c
new file mode 100644
index 0000000..a2b125a
--- /dev/null
+++ b/osdep-darwin.c
@@ -0,0 +1,108 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <Availability.h>
+#include <libproc.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+char *osdep_get_name(int, char *);
+char *osdep_get_cwd(int);
+struct event_base *osdep_event_init(void);
+
+#ifndef __unused
+#define __unused __attribute__ ((__unused__))
+#endif
+
+char *
+osdep_get_name(int fd, __unused char *tty)
+{
+#ifdef __MAC_10_7
+ struct proc_bsdshortinfo bsdinfo;
+ pid_t pgrp;
+ int ret;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ ret = proc_pidinfo(pgrp, PROC_PIDT_SHORTBSDINFO, 0,
+ &bsdinfo, sizeof bsdinfo);
+ if (ret == sizeof bsdinfo && *bsdinfo.pbsi_comm != '\0')
+ return (strdup(bsdinfo.pbsi_comm));
+ return (NULL);
+#else
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
+ size_t size;
+ struct kinfo_proc kp;
+
+ if ((mib[3] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ size = sizeof kp;
+ if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1)
+ return (NULL);
+ if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0')
+ return (NULL);
+
+ return (strdup(kp.kp_proc.p_comm));
+#endif
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ static char wd[PATH_MAX];
+ struct proc_vnodepathinfo pathinfo;
+ pid_t pgrp;
+ int ret;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ ret = proc_pidinfo(pgrp, PROC_PIDVNODEPATHINFO, 0,
+ &pathinfo, sizeof pathinfo);
+ if (ret == sizeof pathinfo) {
+ strlcpy(wd, pathinfo.pvi_cdir.vip_path, sizeof wd);
+ return (wd);
+ }
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ struct event_base *base;
+
+ /*
+ * On OS X, kqueue and poll are both completely broken and don't
+ * work on anything except socket file descriptors (yes, really).
+ */
+ setenv("EVENT_NOKQUEUE", "1", 1);
+ setenv("EVENT_NOPOLL", "1", 1);
+
+ base = event_init();
+ unsetenv("EVENT_NOKQUEUE");
+ unsetenv("EVENT_NOPOLL");
+ return (base);
+}
diff --git a/osdep-dragonfly.c b/osdep-dragonfly.c
new file mode 100644
index 0000000..02a4d18
--- /dev/null
+++ b/osdep-dragonfly.c
@@ -0,0 +1,132 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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/param.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct kinfo_proc *cmp_procs(struct kinfo_proc *, struct kinfo_proc *);
+char *osdep_get_name(int, char *);
+char *osdep_get_cwd(int);
+struct event_base *osdep_event_init(void);
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#define is_runnable(p) \
+ ((p)->kp_stat == SACTIVE || (p)->kp_stat == SIDL)
+#define is_stopped(p) \
+ ((p)->kp_stat == SSTOP || (p)->kp_stat == SZOMB)
+
+struct kinfo_proc *
+cmp_procs(struct kinfo_proc *p1, struct kinfo_proc *p2)
+{
+ if (is_runnable(p1) && !is_runnable(p2))
+ return (p1);
+ if (!is_runnable(p1) && is_runnable(p2))
+ return (p2);
+
+ if (is_stopped(p1) && !is_stopped(p2))
+ return (p1);
+ if (!is_stopped(p1) && is_stopped(p2))
+ return (p2);
+
+ if (strcmp(p1->kp_comm, p2->kp_comm) < 0)
+ return (p1);
+ if (strcmp(p1->kp_comm, p2->kp_comm) > 0)
+ return (p2);
+
+ if (p1->kp_pid > p2->kp_pid)
+ return (p1);
+ return (p2);
+}
+
+char *
+osdep_get_name(int fd, char *tty)
+{
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PGRP, 0 };
+ struct stat sb;
+ size_t len;
+ struct kinfo_proc *buf, *newbuf, *bestp;
+ u_int i;
+ char *name;
+
+ buf = NULL;
+
+ if (stat(tty, &sb) == -1)
+ return (NULL);
+ if ((mib[3] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+retry:
+ if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1)
+ return (NULL);
+ len = (len * 5) / 4;
+
+ if ((newbuf = realloc(buf, len)) == NULL)
+ goto error;
+ buf = newbuf;
+
+ if (sysctl(mib, nitems(mib), buf, &len, NULL, 0) == -1) {
+ if (errno == ENOMEM)
+ goto retry;
+ goto error;
+ }
+
+ bestp = NULL;
+ for (i = 0; i < len / sizeof (struct kinfo_proc); i++) {
+ if (buf[i].kp_tdev != sb.st_rdev)
+ continue;
+ if (bestp == NULL)
+ bestp = &buf[i];
+ else
+ bestp = cmp_procs(&buf[i], bestp);
+ }
+
+ name = NULL;
+ if (bestp != NULL)
+ name = strdup(bestp->kp_comm);
+
+ free(buf);
+ return (name);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-freebsd.c b/osdep-freebsd.c
new file mode 100644
index 0000000..0f347f9
--- /dev/null
+++ b/osdep-freebsd.c
@@ -0,0 +1,208 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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/param.h>
+#include <sys/proc.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+
+#include "compat.h"
+
+struct kinfo_proc *cmp_procs(struct kinfo_proc *, struct kinfo_proc *);
+char *osdep_get_name(int, char *);
+char *osdep_get_cwd(int);
+struct event_base *osdep_event_init(void);
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#define is_runnable(p) \
+ ((p)->ki_stat == SRUN || (p)->ki_stat == SIDL)
+#define is_stopped(p) \
+ ((p)->ki_stat == SSTOP || (p)->ki_stat == SZOMB)
+
+struct kinfo_proc *
+cmp_procs(struct kinfo_proc *p1, struct kinfo_proc *p2)
+{
+ if (is_runnable(p1) && !is_runnable(p2))
+ return (p1);
+ if (!is_runnable(p1) && is_runnable(p2))
+ return (p2);
+
+ if (is_stopped(p1) && !is_stopped(p2))
+ return (p1);
+ if (!is_stopped(p1) && is_stopped(p2))
+ return (p2);
+
+ if (p1->ki_estcpu > p2->ki_estcpu)
+ return (p1);
+ if (p1->ki_estcpu < p2->ki_estcpu)
+ return (p2);
+
+ if (p1->ki_slptime < p2->ki_slptime)
+ return (p1);
+ if (p1->ki_slptime > p2->ki_slptime)
+ return (p2);
+
+ if (strcmp(p1->ki_comm, p2->ki_comm) < 0)
+ return (p1);
+ if (strcmp(p1->ki_comm, p2->ki_comm) > 0)
+ return (p2);
+
+ if (p1->ki_pid > p2->ki_pid)
+ return (p1);
+ return (p2);
+}
+
+char *
+osdep_get_name(int fd, char *tty)
+{
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PGRP, 0 };
+ struct stat sb;
+ size_t len;
+ struct kinfo_proc *buf, *newbuf, *bestp;
+ u_int i;
+ char *name;
+
+ buf = NULL;
+
+ if (stat(tty, &sb) == -1)
+ return (NULL);
+ if ((mib[3] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+retry:
+ if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1)
+ return (NULL);
+ len = (len * 5) / 4;
+
+ if ((newbuf = realloc(buf, len)) == NULL)
+ goto error;
+ buf = newbuf;
+
+ if (sysctl(mib, nitems(mib), buf, &len, NULL, 0) == -1) {
+ if (errno == ENOMEM)
+ goto retry;
+ goto error;
+ }
+
+ bestp = NULL;
+ for (i = 0; i < len / sizeof (struct kinfo_proc); i++) {
+ if (buf[i].ki_tdev != sb.st_rdev)
+ continue;
+ if (bestp == NULL)
+ bestp = &buf[i];
+ else
+ bestp = cmp_procs(&buf[i], bestp);
+ }
+
+ name = NULL;
+ if (bestp != NULL)
+ name = strdup(bestp->ki_comm);
+
+ free(buf);
+ return (name);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+static char *
+osdep_get_cwd_fallback(int fd)
+{
+ static char wd[PATH_MAX];
+ struct kinfo_file *info = NULL;
+ pid_t pgrp;
+ int nrecords, i;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ if ((info = kinfo_getfile(pgrp, &nrecords)) == NULL)
+ return (NULL);
+
+ for (i = 0; i < nrecords; i++) {
+ if (info[i].kf_fd == KF_FD_TYPE_CWD) {
+ strlcpy(wd, info[i].kf_path, sizeof wd);
+ free(info);
+ return (wd);
+ }
+ }
+
+ free(info);
+ return (NULL);
+}
+
+#ifdef KERN_PROC_CWD
+char *
+osdep_get_cwd(int fd)
+{
+ static struct kinfo_file info;
+ static int fallback;
+ int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, 0 };
+ size_t len = sizeof info;
+
+ if (fallback)
+ return (osdep_get_cwd_fallback(fd));
+
+ if ((name[3] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ if (sysctl(name, 4, &info, &len, NULL, 0) == -1) {
+ if (errno == ENOENT) {
+ fallback = 1;
+ return (osdep_get_cwd_fallback(fd));
+ }
+ return (NULL);
+ }
+ return (info.kf_path);
+}
+#else /* !KERN_PROC_CWD */
+char *
+osdep_get_cwd(int fd)
+{
+ return (osdep_get_cwd_fallback(fd));
+}
+#endif /* KERN_PROC_CWD */
+
+struct event_base *
+osdep_event_init(void)
+{
+ struct event_base *base;
+
+ /*
+ * On some versions of FreeBSD, kqueue doesn't work properly on tty
+ * file descriptors. This is fixed in recent FreeBSD versions.
+ */
+ setenv("EVENT_NOKQUEUE", "1", 1);
+
+ base = event_init();
+ unsetenv("EVENT_NOKQUEUE");
+ return (base);
+}
diff --git a/osdep-haiku.c b/osdep-haiku.c
new file mode 100644
index 0000000..7b1f800
--- /dev/null
+++ b/osdep-haiku.c
@@ -0,0 +1,52 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 <unistd.h>
+#include <kernel/OS.h>
+
+#include "tmux.h"
+
+char *
+osdep_get_name(int fd, __unused char *tty)
+{
+ pid_t tid;
+ team_info tinfo;
+
+ if ((tid = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ if (get_team_info(tid, &tinfo) != B_OK)
+ return (NULL);
+
+ /* Up to the first 64 characters. */
+ return (xstrdup(tinfo.args));
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-hpux.c b/osdep-hpux.c
new file mode 100644
index 0000000..64e3378
--- /dev/null
+++ b/osdep-hpux.c
@@ -0,0 +1,39 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+char *
+osdep_get_name(__unused int fd, __unused char *tty)
+{
+ return (NULL);
+}
+
+char *
+osdep_get_cwd(__unused int fd)
+{
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-linux.c b/osdep-linux.c
new file mode 100644
index 0000000..7dbab1f
--- /dev/null
+++ b/osdep-linux.c
@@ -0,0 +1,102 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+char *
+osdep_get_name(int fd, __unused char *tty)
+{
+ FILE *f;
+ char *path, *buf;
+ size_t len;
+ int ch;
+ pid_t pgrp;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%lld/cmdline", (long long) pgrp);
+ if ((f = fopen(path, "r")) == NULL) {
+ free(path);
+ return (NULL);
+ }
+ free(path);
+
+ len = 0;
+ buf = NULL;
+ while ((ch = fgetc(f)) != EOF) {
+ if (ch == '\0')
+ break;
+ buf = xrealloc(buf, len + 2);
+ buf[len++] = ch;
+ }
+ if (buf != NULL)
+ buf[len] = '\0';
+
+ fclose(f);
+ return (buf);
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ static char target[MAXPATHLEN + 1];
+ char *path;
+ pid_t pgrp, sid;
+ ssize_t n;
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%lld/cwd", (long long) pgrp);
+ n = readlink(path, target, MAXPATHLEN);
+ free(path);
+
+ if (n == -1 && ioctl(fd, TIOCGSID, &sid) != -1) {
+ xasprintf(&path, "/proc/%lld/cwd", (long long) sid);
+ n = readlink(path, target, MAXPATHLEN);
+ free(path);
+ }
+
+ if (n > 0) {
+ target[n] = '\0';
+ return (target);
+ }
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ struct event_base *base;
+
+ /* On Linux, epoll doesn't work on /dev/null (yes, really). */
+ setenv("EVENT_NOEPOLL", "1", 1);
+
+ base = event_init();
+ unsetenv("EVENT_NOEPOLL");
+ return (base);
+}
diff --git a/osdep-netbsd.c b/osdep-netbsd.c
new file mode 100644
index 0000000..6003c03
--- /dev/null
+++ b/osdep-netbsd.c
@@ -0,0 +1,169 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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/param.h>
+#include <sys/proc.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+#define is_runnable(p) \
+ ((p)->p_stat == LSRUN || (p)->p_stat == SIDL)
+#define is_stopped(p) \
+ ((p)->p_stat == SSTOP || (p)->p_stat == SZOMB)
+
+struct kinfo_proc2 *cmp_procs(struct kinfo_proc2 *, struct kinfo_proc2 *);
+
+struct kinfo_proc2 *
+cmp_procs(struct kinfo_proc2 *p1, struct kinfo_proc2 *p2)
+{
+ if (is_runnable(p1) && !is_runnable(p2))
+ return (p1);
+ if (!is_runnable(p1) && is_runnable(p2))
+ return (p2);
+
+ if (is_stopped(p1) && !is_stopped(p2))
+ return (p1);
+ if (!is_stopped(p1) && is_stopped(p2))
+ return (p2);
+
+ if (p1->p_estcpu > p2->p_estcpu)
+ return (p1);
+ if (p1->p_estcpu < p2->p_estcpu)
+ return (p2);
+
+ if (p1->p_slptime < p2->p_slptime)
+ return (p1);
+ if (p1->p_slptime > p2->p_slptime)
+ return (p2);
+
+ if (p1->p_pid > p2->p_pid)
+ return (p1);
+ return (p2);
+}
+
+char *
+osdep_get_name(int fd, __unused char *tty)
+{
+ int mib[6];
+ struct stat sb;
+ size_t len, i;
+ struct kinfo_proc2 *buf, *newbuf, *bestp;
+ char *name;
+
+ if (stat(tty, &sb) == -1)
+ return (NULL);
+ if ((mib[3] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+ buf = NULL;
+ len = sizeof bestp;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC2;
+ mib[2] = KERN_PROC_PGRP;
+ mib[4] = sizeof *buf;
+
+retry:
+ mib[5] = 0;
+
+ if (sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0) == -1)
+ return (NULL);
+
+ if ((newbuf = realloc(buf, len)) == NULL)
+ goto error;
+ buf = newbuf;
+
+ mib[5] = len / (sizeof *buf);
+ if (sysctl(mib, __arraycount(mib), buf, &len, NULL, 0) == -1) {
+ if (errno == ENOMEM)
+ goto retry;
+ goto error;
+ }
+
+ bestp = NULL;
+ for (i = 0; i < len / (sizeof *buf); i++) {
+ if (buf[i].p_tdev != sb.st_rdev)
+ continue;
+ if (bestp == NULL)
+ bestp = &buf[i];
+ else
+ bestp = cmp_procs(&buf[i], bestp);
+ }
+
+ name = NULL;
+ if (bestp != NULL)
+ name = strdup(bestp->p_comm);
+
+ free(buf);
+ return (name);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ static char target[PATH_MAX + 1];
+ pid_t pgrp;
+#ifdef KERN_PROC_CWD
+ int mib[4];
+ size_t len;
+#else
+ char *path;
+ ssize_t n;
+#endif
+
+ if ((pgrp = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+#ifdef KERN_PROC_CWD
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC_ARGS;
+ mib[2] = pgrp;
+ mib[3] = KERN_PROC_CWD;
+ len = sizeof(target);
+ if (sysctl(mib, __arraycount(mib), target, &len, NULL, 0) == 0)
+ return (target);
+#else
+ xasprintf(&path, "/proc/%lld/cwd", (long long) pgrp);
+ n = readlink(path, target, sizeof(target) - 1);
+ free(path);
+ if (n > 0) {
+ target[n] = '\0';
+ return (target);
+ }
+#endif
+
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-openbsd.c b/osdep-openbsd.c
new file mode 100644
index 0000000..b4c8fc5
--- /dev/null
+++ b/osdep-openbsd.c
@@ -0,0 +1,158 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/signal.h>
+#include <sys/proc.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "compat.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#define is_runnable(p) \
+ ((p)->p_stat == SRUN || (p)->p_stat == SIDL || (p)->p_stat == SONPROC)
+#define is_stopped(p) \
+ ((p)->p_stat == SSTOP || (p)->p_stat == SDEAD)
+
+static struct kinfo_proc *cmp_procs(struct kinfo_proc *, struct kinfo_proc *);
+char *osdep_get_name(int, char *);
+char *osdep_get_cwd(int);
+struct event_base *osdep_event_init(void);
+
+static struct kinfo_proc *
+cmp_procs(struct kinfo_proc *p1, struct kinfo_proc *p2)
+{
+ if (is_runnable(p1) && !is_runnable(p2))
+ return (p1);
+ if (!is_runnable(p1) && is_runnable(p2))
+ return (p2);
+
+ if (is_stopped(p1) && !is_stopped(p2))
+ return (p1);
+ if (!is_stopped(p1) && is_stopped(p2))
+ return (p2);
+
+ if (p1->p_estcpu > p2->p_estcpu)
+ return (p1);
+ if (p1->p_estcpu < p2->p_estcpu)
+ return (p2);
+
+ if (p1->p_slptime < p2->p_slptime)
+ return (p1);
+ if (p1->p_slptime > p2->p_slptime)
+ return (p2);
+
+ if ((p1->p_flag & P_SINTR) && !(p2->p_flag & P_SINTR))
+ return (p1);
+ if (!(p1->p_flag & P_SINTR) && (p2->p_flag & P_SINTR))
+ return (p2);
+
+ if (strcmp(p1->p_comm, p2->p_comm) < 0)
+ return (p1);
+ if (strcmp(p1->p_comm, p2->p_comm) > 0)
+ return (p2);
+
+ if (p1->p_pid > p2->p_pid)
+ return (p1);
+ return (p2);
+}
+
+char *
+osdep_get_name(int fd, char *tty)
+{
+ int mib[6] = { CTL_KERN, KERN_PROC, KERN_PROC_PGRP, 0,
+ sizeof(struct kinfo_proc), 0 };
+ struct stat sb;
+ size_t len;
+ struct kinfo_proc *buf, *newbuf, *bestp;
+ u_int i;
+ char *name;
+
+ buf = NULL;
+
+ if (stat(tty, &sb) == -1)
+ return (NULL);
+ if ((mib[3] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+
+retry:
+ if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1)
+ goto error;
+ len = (len * 5) / 4;
+
+ if ((newbuf = realloc(buf, len)) == NULL)
+ goto error;
+ buf = newbuf;
+
+ mib[5] = (int)(len / sizeof(struct kinfo_proc));
+ if (sysctl(mib, nitems(mib), buf, &len, NULL, 0) == -1) {
+ if (errno == ENOMEM)
+ goto retry;
+ goto error;
+ }
+
+ bestp = NULL;
+ for (i = 0; i < len / sizeof (struct kinfo_proc); i++) {
+ if ((dev_t)buf[i].p_tdev != sb.st_rdev)
+ continue;
+ if (bestp == NULL)
+ bestp = &buf[i];
+ else
+ bestp = cmp_procs(&buf[i], bestp);
+ }
+
+ name = NULL;
+ if (bestp != NULL)
+ name = strdup(bestp->p_comm);
+
+ free(buf);
+ return (name);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ int name[] = { CTL_KERN, KERN_PROC_CWD, 0 };
+ static char path[PATH_MAX];
+ size_t pathlen = sizeof path;
+
+ if ((name[2] = tcgetpgrp(fd)) == -1)
+ return (NULL);
+ if (sysctl(name, 3, path, &pathlen, NULL, 0) != 0)
+ return (NULL);
+ return (path);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/osdep-sunos.c b/osdep-sunos.c
new file mode 100644
index 0000000..c3563ca
--- /dev/null
+++ b/osdep-sunos.c
@@ -0,0 +1,112 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Todd Carson <toc@daybefore.net>
+ *
+ * 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/param.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <procfs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+char *
+osdep_get_name(__unused int fd, char *tty)
+{
+ struct psinfo p;
+ struct stat st;
+ char *path;
+ ssize_t bytes;
+ int f;
+ pid_t pgrp;
+
+ if ((f = open(tty, O_RDONLY)) < 0)
+ return (NULL);
+
+ if (fstat(f, &st) != 0 || ioctl(f, TIOCGPGRP, &pgrp) != 0) {
+ close(f);
+ return (NULL);
+ }
+ close(f);
+
+ xasprintf(&path, "/proc/%u/psinfo", (u_int) pgrp);
+ f = open(path, O_RDONLY);
+ free(path);
+ if (f < 0)
+ return (NULL);
+
+ bytes = read(f, &p, sizeof(p));
+ close(f);
+ if (bytes != sizeof(p))
+ return (NULL);
+
+ if (p.pr_ttydev != st.st_rdev)
+ return (NULL);
+
+ return (xstrdup(p.pr_fname));
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ static char target[MAXPATHLEN + 1];
+ char *path;
+ const char *ttypath;
+ ssize_t n;
+ pid_t pgrp;
+ int retval, ttyfd;
+
+ if ((ttypath = ptsname(fd)) == NULL)
+ return (NULL);
+ if ((ttyfd = open(ttypath, O_RDONLY|O_NOCTTY)) == -1)
+ return (NULL);
+
+ retval = ioctl(ttyfd, TIOCGPGRP, &pgrp);
+ close(ttyfd);
+ if (retval == -1)
+ return (NULL);
+
+ xasprintf(&path, "/proc/%u/path/cwd", (u_int) pgrp);
+ n = readlink(path, target, MAXPATHLEN);
+ free(path);
+ if (n > 0) {
+ target[n] = '\0';
+ return (target);
+ }
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ struct event_base *base;
+
+ /*
+ * On Illumos, evports don't seem to work properly. It is not clear if
+ * this a problem in libevent, with the way tmux uses file descriptors,
+ * or with some types of file descriptor. But using poll instead is
+ * fine.
+ */
+ setenv("EVENT_NOEVPORT", "1", 1);
+
+ base = event_init();
+ unsetenv("EVENT_NOEVPORT");
+ return (base);
+}
diff --git a/osdep-unknown.c b/osdep-unknown.c
new file mode 100644
index 0000000..440f619
--- /dev/null
+++ b/osdep-unknown.c
@@ -0,0 +1,39 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 "tmux.h"
+
+char *
+osdep_get_name(__unused int fd, __unused char *tty)
+{
+ return (NULL);
+}
+
+char *
+osdep_get_cwd(int fd)
+{
+ return (NULL);
+}
+
+struct event_base *
+osdep_event_init(void)
+{
+ return (event_init());
+}
diff --git a/paste.c b/paste.c
new file mode 100644
index 0000000..51ae2b1
--- /dev/null
+++ b/paste.c
@@ -0,0 +1,326 @@
+/* $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 <time.h>
+
+#include "tmux.h"
+
+/*
+ * Set of paste buffers. Note that paste buffer data is not necessarily a C
+ * string!
+ */
+
+struct paste_buffer {
+ char *data;
+ size_t size;
+
+ char *name;
+ time_t created;
+ int automatic;
+ u_int order;
+
+ RB_ENTRY(paste_buffer) name_entry;
+ RB_ENTRY(paste_buffer) time_entry;
+};
+
+static u_int paste_next_index;
+static u_int paste_next_order;
+static u_int paste_num_automatic;
+static RB_HEAD(paste_name_tree, paste_buffer) paste_by_name;
+static RB_HEAD(paste_time_tree, paste_buffer) paste_by_time;
+
+static int paste_cmp_names(const struct paste_buffer *,
+ const struct paste_buffer *);
+RB_GENERATE_STATIC(paste_name_tree, paste_buffer, name_entry, paste_cmp_names);
+
+static int paste_cmp_times(const struct paste_buffer *,
+ const struct paste_buffer *);
+RB_GENERATE_STATIC(paste_time_tree, paste_buffer, time_entry, paste_cmp_times);
+
+static int
+paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b)
+{
+ return (strcmp(a->name, b->name));
+}
+
+static int
+paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b)
+{
+ if (a->order > b->order)
+ return (-1);
+ if (a->order < b->order)
+ return (1);
+ return (0);
+}
+
+/* Get paste buffer name. */
+const char *
+paste_buffer_name(struct paste_buffer *pb)
+{
+ return (pb->name);
+}
+
+/* Get paste buffer order. */
+u_int
+paste_buffer_order(struct paste_buffer *pb)
+{
+ return (pb->order);
+}
+
+/* Get paste buffer created. */
+time_t
+paste_buffer_created(struct paste_buffer *pb)
+{
+ return (pb->created);
+}
+
+/* Get paste buffer data. */
+const char *
+paste_buffer_data(struct paste_buffer *pb, size_t *size)
+{
+ if (size != NULL)
+ *size = pb->size;
+ return (pb->data);
+}
+
+/* Walk paste buffers by time. */
+struct paste_buffer *
+paste_walk(struct paste_buffer *pb)
+{
+ if (pb == NULL)
+ return (RB_MIN(paste_time_tree, &paste_by_time));
+ return (RB_NEXT(paste_time_tree, &paste_by_time, pb));
+}
+
+/* Get the most recent automatic buffer. */
+struct paste_buffer *
+paste_get_top(const char **name)
+{
+ struct paste_buffer *pb;
+
+ pb = RB_MIN(paste_time_tree, &paste_by_time);
+ if (pb == NULL)
+ return (NULL);
+ if (name != NULL)
+ *name = pb->name;
+ return (pb);
+}
+
+/* Get a paste buffer by name. */
+struct paste_buffer *
+paste_get_name(const char *name)
+{
+ struct paste_buffer pbfind;
+
+ if (name == NULL || *name == '\0')
+ return (NULL);
+
+ pbfind.name = (char *)name;
+ return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind));
+}
+
+/* Free a paste buffer. */
+void
+paste_free(struct paste_buffer *pb)
+{
+ RB_REMOVE(paste_name_tree, &paste_by_name, pb);
+ RB_REMOVE(paste_time_tree, &paste_by_time, pb);
+ if (pb->automatic)
+ paste_num_automatic--;
+
+ free(pb->data);
+ free(pb->name);
+ free(pb);
+}
+
+/*
+ * Add an automatic buffer, freeing the oldest automatic item if at limit. Note
+ * that the caller is responsible for allocating data.
+ */
+void
+paste_add(const char *prefix, char *data, size_t size)
+{
+ struct paste_buffer *pb, *pb1;
+ u_int limit;
+
+ if (prefix == NULL)
+ prefix = "buffer";
+
+ if (size == 0) {
+ free(data);
+ return;
+ }
+
+ limit = options_get_number(global_options, "buffer-limit");
+ RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) {
+ if (paste_num_automatic < limit)
+ break;
+ if (pb->automatic)
+ paste_free(pb);
+ }
+
+ pb = xmalloc(sizeof *pb);
+
+ pb->name = NULL;
+ do {
+ free(pb->name);
+ xasprintf(&pb->name, "%s%u", prefix, paste_next_index);
+ paste_next_index++;
+ } while (paste_get_name(pb->name) != NULL);
+
+ pb->data = data;
+ pb->size = size;
+
+ pb->automatic = 1;
+ paste_num_automatic++;
+
+ pb->created = time(NULL);
+
+ pb->order = paste_next_order++;
+ RB_INSERT(paste_name_tree, &paste_by_name, pb);
+ RB_INSERT(paste_time_tree, &paste_by_time, pb);
+}
+
+/* Rename a paste buffer. */
+int
+paste_rename(const char *oldname, const char *newname, char **cause)
+{
+ struct paste_buffer *pb, *pb_new;
+
+ if (cause != NULL)
+ *cause = NULL;
+
+ if (oldname == NULL || *oldname == '\0') {
+ if (cause != NULL)
+ *cause = xstrdup("no buffer");
+ return (-1);
+ }
+ if (newname == NULL || *newname == '\0') {
+ if (cause != NULL)
+ *cause = xstrdup("new name is empty");
+ return (-1);
+ }
+
+ pb = paste_get_name(oldname);
+ if (pb == NULL) {
+ if (cause != NULL)
+ xasprintf(cause, "no buffer %s", oldname);
+ return (-1);
+ }
+
+ pb_new = paste_get_name(newname);
+ if (pb_new != NULL) {
+ if (cause != NULL)
+ xasprintf(cause, "buffer %s already exists", newname);
+ return (-1);
+ }
+
+ RB_REMOVE(paste_name_tree, &paste_by_name, pb);
+
+ free(pb->name);
+ pb->name = xstrdup(newname);
+
+ if (pb->automatic)
+ paste_num_automatic--;
+ pb->automatic = 0;
+
+ RB_INSERT(paste_name_tree, &paste_by_name, pb);
+
+ return (0);
+}
+
+/*
+ * Add or replace an item in the store. Note that the caller is responsible for
+ * allocating data.
+ */
+int
+paste_set(char *data, size_t size, const char *name, char **cause)
+{
+ struct paste_buffer *pb, *old;
+
+ if (cause != NULL)
+ *cause = NULL;
+
+ if (size == 0) {
+ free(data);
+ return (0);
+ }
+ if (name == NULL) {
+ paste_add(NULL, data, size);
+ return (0);
+ }
+
+ if (*name == '\0') {
+ if (cause != NULL)
+ *cause = xstrdup("empty buffer name");
+ return (-1);
+ }
+
+ pb = xmalloc(sizeof *pb);
+
+ pb->name = xstrdup(name);
+
+ pb->data = data;
+ pb->size = size;
+
+ pb->automatic = 0;
+ pb->order = paste_next_order++;
+
+ pb->created = time(NULL);
+
+ if ((old = paste_get_name(name)) != NULL)
+ paste_free(old);
+
+ RB_INSERT(paste_name_tree, &paste_by_name, pb);
+ RB_INSERT(paste_time_tree, &paste_by_time, pb);
+
+ return (0);
+}
+
+/* Set paste data without otherwise changing it. */
+void
+paste_replace(struct paste_buffer *pb, char *data, size_t size)
+{
+ free(pb->data);
+ pb->data = data;
+ pb->size = size;
+}
+
+/* Convert start of buffer into a nice string. */
+char *
+paste_make_sample(struct paste_buffer *pb)
+{
+ char *buf;
+ size_t len, used;
+ const int flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
+ const size_t width = 200;
+
+ len = pb->size;
+ if (len > width)
+ len = width;
+ buf = xreallocarray(NULL, len, 4 + 4);
+
+ used = utf8_strvis(buf, pb->data, len, flags);
+ if (pb->size > width || used > width)
+ strlcpy(buf + width, "...", 4);
+ return (buf);
+}
diff --git a/popup.c b/popup.c
new file mode 100644
index 0000000..2e57153
--- /dev/null
+++ b/popup.c
@@ -0,0 +1,815 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+struct popup_data {
+ struct client *c;
+ struct cmdq_item *item;
+ int flags;
+ char *title;
+
+ struct grid_cell border_cell;
+ enum box_lines border_lines;
+
+ struct screen s;
+ struct grid_cell defaults;
+ struct colour_palette palette;
+
+ struct job *job;
+ struct input_ctx *ictx;
+ int status;
+ popup_close_cb cb;
+ void *arg;
+
+ struct menu *menu;
+ struct menu_data *md;
+ int close;
+
+ /* Current position and size. */
+ u_int px;
+ u_int py;
+ u_int sx;
+ u_int sy;
+
+ /* Preferred position and size. */
+ u_int ppx;
+ u_int ppy;
+ u_int psx;
+ u_int psy;
+
+ enum { OFF, MOVE, SIZE } dragging;
+ u_int dx;
+ u_int dy;
+
+ u_int lx;
+ u_int ly;
+ u_int lb;
+};
+
+struct popup_editor {
+ char *path;
+ popup_finish_edit_cb cb;
+ void *arg;
+};
+
+static const struct menu_item popup_menu_items[] = {
+ { "Close", 'q', NULL },
+ { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Fill Space", 'F', NULL },
+ { "Centre", 'C', NULL },
+ { "", KEYC_NONE, NULL },
+ { "To Horizontal Pane", 'h', NULL },
+ { "To Vertical Pane", 'v', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+static const struct menu_item popup_internal_menu_items[] = {
+ { "Close", 'q', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Fill Space", 'F', NULL },
+ { "Centre", 'C', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+static void
+popup_redraw_cb(const struct tty_ctx *ttyctx)
+{
+ struct popup_data *pd = ttyctx->arg;
+
+ pd->c->flags |= CLIENT_REDRAWOVERLAY;
+}
+
+static int
+popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
+{
+ struct popup_data *pd = ttyctx->arg;
+
+ if (c != pd->c)
+ return (0);
+ if (pd->c->flags & CLIENT_REDRAWOVERLAY)
+ return (0);
+
+ ttyctx->bigger = 0;
+ ttyctx->wox = 0;
+ ttyctx->woy = 0;
+ ttyctx->wsx = c->tty.sx;
+ ttyctx->wsy = c->tty.sy;
+
+ if (pd->border_lines == BOX_LINES_NONE) {
+ ttyctx->xoff = ttyctx->rxoff = pd->px;
+ ttyctx->yoff = ttyctx->ryoff = pd->py;
+ } else {
+ ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
+ ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
+ }
+
+ return (1);
+}
+
+static void
+popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
+{
+ struct popup_data *pd = ctx->arg;
+
+ memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
+ ttyctx->palette = &pd->palette;
+ ttyctx->redraw_cb = popup_redraw_cb;
+ ttyctx->set_client_cb = popup_set_client_cb;
+ ttyctx->arg = pd;
+}
+
+static struct screen *
+popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
+{
+ struct popup_data *pd = data;
+
+ if (pd->md != NULL)
+ return (menu_mode_cb(c, pd->md, cx, cy));
+
+ if (pd->border_lines == BOX_LINES_NONE) {
+ *cx = pd->px + pd->s.cx;
+ *cy = pd->py + pd->s.cy;
+ } else {
+ *cx = pd->px + 1 + pd->s.cx;
+ *cy = pd->py + 1 + pd->s.cy;
+ }
+ return (&pd->s);
+}
+
+/* Return parts of the input range which are not obstructed by the popup. */
+static void
+popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx,
+ struct overlay_ranges *r)
+{
+ struct popup_data *pd = data;
+ struct overlay_ranges or[2];
+ u_int i, j, k = 0;
+
+ if (pd->md != NULL) {
+ /* Check each returned range for the menu against the popup. */
+ menu_check_cb(c, pd->md, px, py, nx, r);
+ for (i = 0; i < 2; i++) {
+ server_client_overlay_range(pd->px, pd->py, pd->sx,
+ pd->sy, r->px[i], py, r->nx[i], &or[i]);
+ }
+
+ /*
+ * or has up to OVERLAY_MAX_RANGES non-overlapping ranges,
+ * ordered from left to right. Collect them in the output.
+ */
+ for (i = 0; i < 2; i++) {
+ /* Each or[i] only has 2 ranges. */
+ for (j = 0; j < 2; j++) {
+ if (or[i].nx[j] > 0) {
+ r->px[k] = or[i].px[j];
+ r->nx[k] = or[i].nx[j];
+ k++;
+ }
+ }
+ }
+
+ /* Zero remaining ranges if any. */
+ for (i = k; i < OVERLAY_MAX_RANGES; i++) {
+ r->px[i] = 0;
+ r->nx[i] = 0;
+ }
+
+ return;
+ }
+
+ server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
+ r);
+}
+
+static void
+popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
+{
+ struct popup_data *pd = data;
+ struct tty *tty = &c->tty;
+ struct screen s;
+ struct screen_write_ctx ctx;
+ u_int i, px = pd->px, py = pd->py;
+ struct colour_palette *palette = &pd->palette;
+ struct grid_cell defaults;
+
+ screen_init(&s, pd->sx, pd->sy, 0);
+ screen_write_start(&ctx, &s);
+ screen_write_clearscreen(&ctx, 8);
+
+ if (pd->border_lines == BOX_LINES_NONE) {
+ screen_write_cursormove(&ctx, 0, 0, 0);
+ screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
+ } else if (pd->sx > 2 && pd->sy > 2) {
+ screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
+ &pd->border_cell, pd->title);
+ screen_write_cursormove(&ctx, 1, 1, 0);
+ screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
+ pd->sy - 2);
+ }
+ screen_write_stop(&ctx);
+
+ memcpy(&defaults, &pd->defaults, sizeof defaults);
+ if (defaults.fg == 8)
+ defaults.fg = palette->fg;
+ if (defaults.bg == 8)
+ defaults.bg = palette->bg;
+
+ if (pd->md != NULL) {
+ c->overlay_check = menu_check_cb;
+ c->overlay_data = pd->md;
+ } else {
+ c->overlay_check = NULL;
+ c->overlay_data = NULL;
+ }
+ for (i = 0; i < pd->sy; i++) {
+ tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults,
+ palette);
+ }
+ if (pd->md != NULL) {
+ c->overlay_check = NULL;
+ c->overlay_data = NULL;
+ menu_draw_cb(c, pd->md, rctx);
+ }
+ c->overlay_check = popup_check_cb;
+ c->overlay_data = pd;
+}
+
+static void
+popup_free_cb(struct client *c, void *data)
+{
+ struct popup_data *pd = data;
+ struct cmdq_item *item = pd->item;
+
+ if (pd->md != NULL)
+ menu_free_cb(c, pd->md);
+
+ if (pd->cb != NULL)
+ pd->cb(pd->status, pd->arg);
+
+ if (item != NULL) {
+ if (cmdq_get_client(item) != NULL &&
+ cmdq_get_client(item)->session == NULL)
+ cmdq_get_client(item)->retval = pd->status;
+ cmdq_continue(item);
+ }
+ server_client_unref(pd->c);
+
+ if (pd->job != NULL)
+ job_free(pd->job);
+ input_free(pd->ictx);
+
+ screen_free(&pd->s);
+ colour_palette_free(&pd->palette);
+
+ free(pd->title);
+ free(pd);
+}
+
+static void
+popup_resize_cb(__unused struct client *c, void *data)
+{
+ struct popup_data *pd = data;
+ struct tty *tty = &c->tty;
+
+ if (pd == NULL)
+ return;
+ if (pd->md != NULL)
+ menu_free_cb(c, pd->md);
+
+ /* Adjust position and size. */
+ if (pd->psy > tty->sy)
+ pd->sy = tty->sy;
+ else
+ pd->sy = pd->psy;
+ if (pd->psx > tty->sx)
+ pd->sx = tty->sx;
+ else
+ pd->sx = pd->psx;
+ if (pd->ppy + pd->sy > tty->sy)
+ pd->py = tty->sy - pd->sy;
+ else
+ pd->py = pd->ppy;
+ if (pd->ppx + pd->sx > tty->sx)
+ pd->px = tty->sx - pd->sx;
+ else
+ pd->px = pd->ppx;
+
+ /* Avoid zero size screens. */
+ if (pd->border_lines == BOX_LINES_NONE) {
+ screen_resize(&pd->s, pd->sx, pd->sy, 0);
+ if (pd->job != NULL)
+ job_resize(pd->job, pd->sx, pd->sy );
+ } else if (pd->sx > 2 && pd->sy > 2) {
+ screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
+ if (pd->job != NULL)
+ job_resize(pd->job, pd->sx - 2, pd->sy - 2);
+ }
+}
+
+static void
+popup_make_pane(struct popup_data *pd, enum layout_type type)
+{
+ struct client *c = pd->c;
+ struct session *s = c->session;
+ struct window *w = s->curw->window;
+ struct layout_cell *lc;
+ struct window_pane *wp = w->active, *new_wp;
+ u_int hlimit;
+ const char *shell;
+
+ window_unzoom(w);
+
+ lc = layout_split_pane(wp, type, -1, 0);
+ hlimit = options_get_number(s->options, "history-limit");
+ new_wp = window_add_pane(wp->window, NULL, hlimit, 0);
+ layout_assign_pane(lc, new_wp, 0);
+
+ new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty,
+ sizeof new_wp->tty);
+ pd->job = NULL;
+
+ screen_set_title(&pd->s, new_wp->base.title);
+ screen_free(&new_wp->base);
+ memcpy(&new_wp->base, &pd->s, sizeof wp->base);
+ screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1);
+ screen_init(&pd->s, 1, 1, 0);
+
+ shell = options_get_string(s->options, "default-shell");
+ if (!checkshell(shell))
+ shell = _PATH_BSHELL;
+ new_wp->shell = xstrdup(shell);
+
+ window_pane_set_event(new_wp);
+ window_set_active_pane(w, new_wp, 1);
+ new_wp->flags |= PANE_CHANGED;
+
+ pd->close = 1;
+}
+
+static void
+popup_menu_done(__unused struct menu *menu, __unused u_int choice,
+ key_code key, void *data)
+{
+ struct popup_data *pd = data;
+ struct client *c = pd->c;
+ struct paste_buffer *pb;
+ const char *buf;
+ size_t len;
+
+ pd->md = NULL;
+ pd->menu = NULL;
+ server_redraw_client(pd->c);
+
+ switch (key) {
+ case 'p':
+ pb = paste_get_top(NULL);
+ if (pb != NULL) {
+ buf = paste_buffer_data(pb, &len);
+ bufferevent_write(job_get_event(pd->job), buf, len);
+ }
+ break;
+ case 'F':
+ pd->sx = c->tty.sx;
+ pd->sy = c->tty.sy;
+ pd->px = 0;
+ pd->py = 0;
+ server_redraw_client(c);
+ break;
+ case 'C':
+ pd->px = c->tty.sx / 2 - pd->sx / 2;
+ pd->py = c->tty.sy / 2 - pd->sy / 2;
+ server_redraw_client(c);
+ break;
+ case 'h':
+ popup_make_pane(pd, LAYOUT_LEFTRIGHT);
+ break;
+ case 'v':
+ popup_make_pane(pd, LAYOUT_TOPBOTTOM);
+ break;
+ case 'q':
+ pd->close = 1;
+ break;
+ }
+}
+
+static void
+popup_handle_drag(struct client *c, struct popup_data *pd,
+ struct mouse_event *m)
+{
+ u_int px, py;
+
+ if (!MOUSE_DRAG(m->b))
+ pd->dragging = OFF;
+ else if (pd->dragging == MOVE) {
+ if (m->x < pd->dx)
+ px = 0;
+ else if (m->x - pd->dx + pd->sx > c->tty.sx)
+ px = c->tty.sx - pd->sx;
+ else
+ px = m->x - pd->dx;
+ if (m->y < pd->dy)
+ py = 0;
+ else if (m->y - pd->dy + pd->sy > c->tty.sy)
+ py = c->tty.sy - pd->sy;
+ else
+ py = m->y - pd->dy;
+ pd->px = px;
+ pd->py = py;
+ pd->dx = m->x - pd->px;
+ pd->dy = m->y - pd->py;
+ pd->ppx = px;
+ pd->ppy = py;
+ server_redraw_client(c);
+ } else if (pd->dragging == SIZE) {
+ if (pd->border_lines == BOX_LINES_NONE) {
+ if (m->x < pd->px + 1)
+ return;
+ if (m->y < pd->py + 1)
+ return;
+ } else {
+ if (m->x < pd->px + 3)
+ return;
+ if (m->y < pd->py + 3)
+ return;
+ }
+ pd->sx = m->x - pd->px;
+ pd->sy = m->y - pd->py;
+ pd->psx = pd->sx;
+ pd->psy = pd->sy;
+
+ if (pd->border_lines == BOX_LINES_NONE) {
+ screen_resize(&pd->s, pd->sx, pd->sy, 0);
+ if (pd->job != NULL)
+ job_resize(pd->job, pd->sx, pd->sy);
+ } else {
+ screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
+ if (pd->job != NULL)
+ job_resize(pd->job, pd->sx - 2, pd->sy - 2);
+ }
+ server_redraw_client(c);
+ }
+}
+
+static int
+popup_key_cb(struct client *c, void *data, struct key_event *event)
+{
+ struct popup_data *pd = data;
+ struct mouse_event *m = &event->m;
+ const char *buf;
+ size_t len;
+ u_int px, py, x;
+ enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
+
+ if (pd->md != NULL) {
+ if (menu_key_cb(c, pd->md, event) == 1) {
+ pd->md = NULL;
+ pd->menu = NULL;
+ if (pd->close)
+ server_client_clear_overlay(c);
+ else
+ server_redraw_client(c);
+ }
+ return (0);
+ }
+
+ if (KEYC_IS_MOUSE(event->key)) {
+ if (pd->dragging != OFF) {
+ popup_handle_drag(c, pd, m);
+ goto out;
+ }
+ if (m->x < pd->px ||
+ m->x > pd->px + pd->sx - 1 ||
+ m->y < pd->py ||
+ m->y > pd->py + pd->sy - 1) {
+ if (MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3)
+ goto menu;
+ return (0);
+ }
+ if (pd->border_lines != BOX_LINES_NONE) {
+ if (m->x == pd->px)
+ border = LEFT;
+ else if (m->x == pd->px + pd->sx - 1)
+ border = RIGHT;
+ else if (m->y == pd->py)
+ border = TOP;
+ else if (m->y == pd->py + pd->sy - 1)
+ border = BOTTOM;
+ }
+ if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
+ MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
+ (border == LEFT || border == TOP))
+ goto menu;
+ if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
+ border != NONE) {
+ if (!MOUSE_DRAG(m->b))
+ goto out;
+ if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
+ pd->dragging = MOVE;
+ else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
+ pd->dragging = SIZE;
+ pd->dx = m->lx - pd->px;
+ pd->dy = m->ly - pd->py;
+ goto out;
+ }
+ }
+ if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
+ pd->job == NULL) &&
+ (event->key == '\033' || event->key == '\003'))
+ return (1);
+ if (pd->job != NULL) {
+ if (KEYC_IS_MOUSE(event->key)) {
+ /* Must be inside, checked already. */
+ if (pd->border_lines == BOX_LINES_NONE) {
+ px = m->x - pd->px;
+ py = m->y - pd->py;
+ } else {
+ px = m->x - pd->px - 1;
+ py = m->y - pd->py - 1;
+ }
+ if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
+ return (0);
+ bufferevent_write(job_get_event(pd->job), buf, len);
+ return (0);
+ }
+ input_key(&pd->s, job_get_event(pd->job), event->key);
+ }
+ return (0);
+
+menu:
+ pd->menu = menu_create("");
+ if (pd->flags & POPUP_INTERNAL) {
+ menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
+ NULL);
+ } else
+ menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
+ if (m->x >= (pd->menu->width + 4) / 2)
+ x = m->x - (pd->menu->width + 4) / 2;
+ else
+ x = 0;
+ pd->md = menu_prepare(pd->menu, 0, NULL, x, m->y, c, NULL,
+ popup_menu_done, pd);
+ c->flags |= CLIENT_REDRAWOVERLAY;
+
+out:
+ pd->lx = m->x;
+ pd->ly = m->y;
+ pd->lb = m->b;
+ return (0);
+}
+
+static void
+popup_job_update_cb(struct job *job)
+{
+ struct popup_data *pd = job_get_data(job);
+ struct evbuffer *evb = job_get_event(job)->input;
+ struct client *c = pd->c;
+ struct screen *s = &pd->s;
+ void *data = EVBUFFER_DATA(evb);
+ size_t size = EVBUFFER_LENGTH(evb);
+
+ if (size == 0)
+ return;
+
+ if (pd->md != NULL) {
+ c->overlay_check = menu_check_cb;
+ c->overlay_data = pd->md;
+ } else {
+ c->overlay_check = NULL;
+ c->overlay_data = NULL;
+ }
+ input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
+ c->overlay_check = popup_check_cb;
+ c->overlay_data = pd;
+
+ evbuffer_drain(evb, size);
+}
+
+static void
+popup_job_complete_cb(struct job *job)
+{
+ struct popup_data *pd = job_get_data(job);
+ int status;
+
+ status = job_get_status(pd->job);
+ if (WIFEXITED(status))
+ pd->status = WEXITSTATUS(status);
+ else if (WIFSIGNALED(status))
+ pd->status = WTERMSIG(status);
+ else
+ pd->status = 0;
+ pd->job = NULL;
+
+ if ((pd->flags & POPUP_CLOSEEXIT) ||
+ ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
+ server_client_clear_overlay(pd->c);
+}
+
+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,
+ popup_close_cb cb, void *arg)
+{
+ struct popup_data *pd;
+ u_int jx, jy;
+ struct options *o;
+ struct style sytmp;
+
+ if (s != NULL)
+ o = s->curw->window->options;
+ else
+ o = c->session->curw->window->options;
+
+ if (lines == BOX_LINES_DEFAULT)
+ lines = options_get_number(o, "popup-border-lines");
+ if (lines == BOX_LINES_NONE) {
+ if (sx < 1 || sy < 1)
+ return (-1);
+ jx = sx;
+ jy = sy;
+ } else {
+ if (sx < 3 || sy < 3)
+ return (-1);
+ jx = sx - 2;
+ jy = sy - 2;
+ }
+ if (c->tty.sx < sx || c->tty.sy < sy)
+ return (-1);
+
+ pd = xcalloc(1, sizeof *pd);
+ pd->item = item;
+ pd->flags = flags;
+ if (title != NULL)
+ pd->title = xstrdup(title);
+
+ pd->c = c;
+ pd->c->references++;
+
+ pd->cb = cb;
+ pd->arg = arg;
+ pd->status = 128 + SIGHUP;
+
+ pd->border_lines = lines;
+ memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
+ style_apply(&pd->border_cell, o, "popup-border-style", NULL);
+ if (border_style != NULL) {
+ style_set(&sytmp, &grid_default_cell);
+ if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
+ pd->border_cell.fg = sytmp.gc.fg;
+ pd->border_cell.bg = sytmp.gc.bg;
+ }
+ }
+ pd->border_cell.attr = 0;
+
+ screen_init(&pd->s, jx, jy, 0);
+ colour_palette_init(&pd->palette);
+ colour_palette_from_option(&pd->palette, global_w_options);
+
+ memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
+ style_apply(&pd->defaults, o, "popup-style", NULL);
+ if (style != NULL) {
+ style_set(&sytmp, &grid_default_cell);
+ if (style_parse(&sytmp, &pd->defaults, style) == 0) {
+ pd->defaults.fg = sytmp.gc.fg;
+ pd->defaults.bg = sytmp.gc.bg;
+ }
+ }
+ pd->defaults.attr = 0;
+
+ pd->px = px;
+ pd->py = py;
+ pd->sx = sx;
+ pd->sy = sy;
+
+ pd->ppx = px;
+ pd->ppy = py;
+ pd->psx = sx;
+ pd->psy = sy;
+
+ pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
+ popup_job_update_cb, popup_job_complete_cb, NULL, pd,
+ JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, jx, jy);
+ pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette);
+
+ server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
+ popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
+ return (0);
+}
+
+static void
+popup_editor_free(struct popup_editor *pe)
+{
+ unlink(pe->path);
+ free(pe->path);
+ free(pe);
+}
+
+static void
+popup_editor_close_cb(int status, void *arg)
+{
+ struct popup_editor *pe = arg;
+ FILE *f;
+ char *buf = NULL;
+ off_t len = 0;
+
+ if (status != 0) {
+ pe->cb(NULL, 0, pe->arg);
+ popup_editor_free(pe);
+ return;
+ }
+
+ f = fopen(pe->path, "r");
+ if (f != NULL) {
+ fseeko(f, 0, SEEK_END);
+ len = ftello(f);
+ fseeko(f, 0, SEEK_SET);
+
+ if (len == 0 ||
+ (uintmax_t)len > (uintmax_t)SIZE_MAX ||
+ (buf = malloc(len)) == NULL ||
+ fread(buf, len, 1, f) != 1) {
+ free(buf);
+ buf = NULL;
+ len = 0;
+ }
+ fclose(f);
+ }
+ pe->cb(buf, len, pe->arg); /* callback now owns buffer */
+ popup_editor_free(pe);
+}
+
+int
+popup_editor(struct client *c, const char *buf, size_t len,
+ popup_finish_edit_cb cb, void *arg)
+{
+ struct popup_editor *pe;
+ int fd;
+ FILE *f;
+ char *cmd;
+ char path[] = _PATH_TMP "tmux.XXXXXXXX";
+ const char *editor;
+ u_int px, py, sx, sy;
+
+ editor = options_get_string(global_options, "editor");
+ if (*editor == '\0')
+ return (-1);
+
+ fd = mkstemp(path);
+ if (fd == -1)
+ return (-1);
+ f = fdopen(fd, "w");
+ if (fwrite(buf, len, 1, f) != 1) {
+ fclose(f);
+ return (-1);
+ }
+ fclose(f);
+
+ pe = xcalloc(1, sizeof *pe);
+ pe->path = xstrdup(path);
+ pe->cb = cb;
+ pe->arg = arg;
+
+ sx = c->tty.sx * 9 / 10;
+ sy = c->tty.sy * 9 / 10;
+ px = (c->tty.sx / 2) - (sx / 2);
+ py = (c->tty.sy / 2) - (sy / 2);
+
+ xasprintf(&cmd, "%s %s", editor, path);
+ if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
+ NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
+ NULL, NULL, popup_editor_close_cb, pe) != 0) {
+ popup_editor_free(pe);
+ free(cmd);
+ return (-1);
+ }
+ free(cmd);
+ return (0);
+}
diff --git a/proc.c b/proc.c
new file mode 100644
index 0000000..67ec214
--- /dev/null
+++ b/proc.c
@@ -0,0 +1,392 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/utsname.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if defined(HAVE_NCURSES_H)
+#include <ncurses.h>
+#endif
+
+#include "tmux.h"
+
+struct tmuxproc {
+ const char *name;
+ int exit;
+
+ void (*signalcb)(int);
+
+ struct event ev_sigint;
+ struct event ev_sighup;
+ struct event ev_sigchld;
+ struct event ev_sigcont;
+ struct event ev_sigterm;
+ struct event ev_sigusr1;
+ struct event ev_sigusr2;
+ struct event ev_sigwinch;
+
+ TAILQ_HEAD(, tmuxpeer) peers;
+};
+
+struct tmuxpeer {
+ struct tmuxproc *parent;
+
+ struct imsgbuf ibuf;
+ struct event event;
+ uid_t uid;
+
+ int flags;
+#define PEER_BAD 0x1
+
+ void (*dispatchcb)(struct imsg *, void *);
+ void *arg;
+
+ TAILQ_ENTRY(tmuxpeer) entry;
+};
+
+static int peer_check_version(struct tmuxpeer *, struct imsg *);
+static void proc_update_event(struct tmuxpeer *);
+
+static void
+proc_event_cb(__unused int fd, short events, void *arg)
+{
+ struct tmuxpeer *peer = arg;
+ ssize_t n;
+ struct imsg imsg;
+
+ if (!(peer->flags & PEER_BAD) && (events & EV_READ)) {
+ if (((n = imsg_read(&peer->ibuf)) == -1 && errno != EAGAIN) ||
+ n == 0) {
+ peer->dispatchcb(NULL, peer->arg);
+ return;
+ }
+ for (;;) {
+ if ((n = imsg_get(&peer->ibuf, &imsg)) == -1) {
+ peer->dispatchcb(NULL, peer->arg);
+ return;
+ }
+ if (n == 0)
+ break;
+ log_debug("peer %p message %d", peer, imsg.hdr.type);
+
+ if (peer_check_version(peer, &imsg) != 0) {
+ if (imsg.fd != -1)
+ close(imsg.fd);
+ imsg_free(&imsg);
+ break;
+ }
+
+ peer->dispatchcb(&imsg, peer->arg);
+ imsg_free(&imsg);
+ }
+ }
+
+ if (events & EV_WRITE) {
+ if (msgbuf_write(&peer->ibuf.w) <= 0 && errno != EAGAIN) {
+ peer->dispatchcb(NULL, peer->arg);
+ return;
+ }
+ }
+
+ if ((peer->flags & PEER_BAD) && peer->ibuf.w.queued == 0) {
+ peer->dispatchcb(NULL, peer->arg);
+ return;
+ }
+
+ proc_update_event(peer);
+}
+
+static void
+proc_signal_cb(int signo, __unused short events, void *arg)
+{
+ struct tmuxproc *tp = arg;
+
+ tp->signalcb(signo);
+}
+
+static int
+peer_check_version(struct tmuxpeer *peer, struct imsg *imsg)
+{
+ int version;
+
+ version = imsg->hdr.peerid & 0xff;
+ if (imsg->hdr.type != MSG_VERSION && version != PROTOCOL_VERSION) {
+ log_debug("peer %p bad version %d", peer, version);
+
+ proc_send(peer, MSG_VERSION, -1, NULL, 0);
+ peer->flags |= PEER_BAD;
+
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+proc_update_event(struct tmuxpeer *peer)
+{
+ short events;
+
+ event_del(&peer->event);
+
+ events = EV_READ;
+ if (peer->ibuf.w.queued > 0)
+ events |= EV_WRITE;
+ event_set(&peer->event, peer->ibuf.fd, events, proc_event_cb, peer);
+
+ event_add(&peer->event, NULL);
+}
+
+int
+proc_send(struct tmuxpeer *peer, enum msgtype type, int fd, const void *buf,
+ size_t len)
+{
+ struct imsgbuf *ibuf = &peer->ibuf;
+ void *vp = (void *)buf;
+ int retval;
+
+ if (peer->flags & PEER_BAD)
+ return (-1);
+ log_debug("sending message %d to peer %p (%zu bytes)", type, peer, len);
+
+ retval = imsg_compose(ibuf, type, PROTOCOL_VERSION, -1, fd, vp, len);
+ if (retval != 1)
+ return (-1);
+ proc_update_event(peer);
+ return (0);
+}
+
+struct tmuxproc *
+proc_start(const char *name)
+{
+ struct tmuxproc *tp;
+ struct utsname u;
+
+ log_open(name);
+ setproctitle("%s (%s)", name, socket_path);
+
+ if (uname(&u) < 0)
+ memset(&u, 0, sizeof u);
+
+ 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)"
+#ifdef HAVE_UTF8PROC
+ "; utf8proc %s"
+#endif
+#ifdef NCURSES_VERSION
+ "; ncurses " NCURSES_VERSION
+#endif
+ , event_get_version(), event_get_method()
+#ifdef HAVE_UTF8PROC
+ , utf8proc_version()
+#endif
+ );
+
+ tp = xcalloc(1, sizeof *tp);
+ tp->name = xstrdup(name);
+ TAILQ_INIT(&tp->peers);
+
+ return (tp);
+}
+
+void
+proc_loop(struct tmuxproc *tp, int (*loopcb)(void))
+{
+ log_debug("%s loop enter", tp->name);
+ do
+ event_loop(EVLOOP_ONCE);
+ while (!tp->exit && (loopcb == NULL || !loopcb ()));
+ log_debug("%s loop exit", tp->name);
+}
+
+void
+proc_exit(struct tmuxproc *tp)
+{
+ struct tmuxpeer *peer;
+
+ TAILQ_FOREACH(peer, &tp->peers, entry)
+ imsg_flush(&peer->ibuf);
+ tp->exit = 1;
+}
+
+void
+proc_set_signals(struct tmuxproc *tp, void (*signalcb)(int))
+{
+ struct sigaction sa;
+
+ tp->signalcb = signalcb;
+
+ memset(&sa, 0, sizeof sa);
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_IGN;
+
+ sigaction(SIGPIPE, &sa, NULL);
+ sigaction(SIGTSTP, &sa, NULL);
+ sigaction(SIGTTIN, &sa, NULL);
+ sigaction(SIGTTOU, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+
+ signal_set(&tp->ev_sigint, SIGINT, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigint, NULL);
+ signal_set(&tp->ev_sighup, SIGHUP, proc_signal_cb, tp);
+ signal_add(&tp->ev_sighup, NULL);
+ signal_set(&tp->ev_sigchld, SIGCHLD, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigchld, NULL);
+ signal_set(&tp->ev_sigcont, SIGCONT, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigcont, NULL);
+ signal_set(&tp->ev_sigterm, SIGTERM, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigterm, NULL);
+ signal_set(&tp->ev_sigusr1, SIGUSR1, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigusr1, NULL);
+ signal_set(&tp->ev_sigusr2, SIGUSR2, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigusr2, NULL);
+ signal_set(&tp->ev_sigwinch, SIGWINCH, proc_signal_cb, tp);
+ signal_add(&tp->ev_sigwinch, NULL);
+}
+
+void
+proc_clear_signals(struct tmuxproc *tp, int defaults)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof sa);
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+
+ sigaction(SIGPIPE, &sa, NULL);
+ sigaction(SIGTSTP, &sa, NULL);
+
+ signal_del(&tp->ev_sigint);
+ signal_del(&tp->ev_sighup);
+ signal_del(&tp->ev_sigchld);
+ signal_del(&tp->ev_sigcont);
+ signal_del(&tp->ev_sigterm);
+ signal_del(&tp->ev_sigusr1);
+ signal_del(&tp->ev_sigusr2);
+ signal_del(&tp->ev_sigwinch);
+
+ if (defaults) {
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGCHLD, &sa, NULL);
+ sigaction(SIGCONT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGUSR1, &sa, NULL);
+ sigaction(SIGUSR2, &sa, NULL);
+ sigaction(SIGWINCH, &sa, NULL);
+ }
+}
+
+struct tmuxpeer *
+proc_add_peer(struct tmuxproc *tp, int fd,
+ void (*dispatchcb)(struct imsg *, void *), void *arg)
+{
+ struct tmuxpeer *peer;
+ gid_t gid;
+
+ peer = xcalloc(1, sizeof *peer);
+ peer->parent = tp;
+
+ peer->dispatchcb = dispatchcb;
+ peer->arg = arg;
+
+ imsg_init(&peer->ibuf, fd);
+ event_set(&peer->event, fd, EV_READ, proc_event_cb, peer);
+
+ if (getpeereid(fd, &peer->uid, &gid) != 0)
+ peer->uid = (uid_t)-1;
+
+ log_debug("add peer %p: %d (%p)", peer, fd, arg);
+ TAILQ_INSERT_TAIL(&tp->peers, peer, entry);
+
+ proc_update_event(peer);
+ return (peer);
+}
+
+void
+proc_remove_peer(struct tmuxpeer *peer)
+{
+ TAILQ_REMOVE(&peer->parent->peers, peer, entry);
+ log_debug("remove peer %p", peer);
+
+ event_del(&peer->event);
+ imsg_clear(&peer->ibuf);
+
+ close(peer->ibuf.fd);
+ free(peer);
+}
+
+void
+proc_kill_peer(struct tmuxpeer *peer)
+{
+ peer->flags |= PEER_BAD;
+}
+
+void
+proc_flush_peer(struct tmuxpeer *peer)
+{
+ imsg_flush(&peer->ibuf);
+}
+
+void
+proc_toggle_log(struct tmuxproc *tp)
+{
+ log_toggle(tp->name);
+}
+
+pid_t
+proc_fork_and_daemon(int *fd)
+{
+ pid_t pid;
+ int pair[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0)
+ fatal("socketpair failed");
+ switch (pid = fork()) {
+ case -1:
+ fatal("fork failed");
+ case 0:
+ close(pair[0]);
+ *fd = pair[1];
+ if (daemon(1, 0) != 0)
+ fatal("daemon failed");
+ return (0);
+ default:
+ close(pair[1]);
+ *fd = pair[0];
+ return (pid);
+ }
+}
+
+uid_t
+proc_get_peer_uid(struct tmuxpeer *peer)
+{
+ return (peer->uid);
+}
diff --git a/regsub.c b/regsub.c
new file mode 100644
index 0000000..4039b9b
--- /dev/null
+++ b/regsub.c
@@ -0,0 +1,120 @@
+/* $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 <regex.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static void
+regsub_copy(char **buf, size_t *len, const char *text, size_t start, size_t end)
+{
+ size_t add = end - start;
+
+ *buf = xrealloc(*buf, (*len) + add + 1);
+ memcpy((*buf) + *len, text + start, add);
+ (*len) += add;
+}
+
+static void
+regsub_expand(char **buf, size_t *len, const char *with, const char *text,
+ regmatch_t *m, u_int n)
+{
+ const char *cp;
+ u_int i;
+
+ for (cp = with; *cp != '\0'; cp++) {
+ if (*cp == '\\') {
+ cp++;
+ if (*cp >= '0' && *cp <= '9') {
+ i = *cp - '0';
+ if (i < n && m[i].rm_so != m[i].rm_eo) {
+ regsub_copy(buf, len, text, m[i].rm_so,
+ m[i].rm_eo);
+ continue;
+ }
+ }
+ }
+ *buf = xrealloc(*buf, (*len) + 2);
+ (*buf)[(*len)++] = *cp;
+ }
+}
+
+char *
+regsub(const char *pattern, const char *with, const char *text, int flags)
+{
+ regex_t r;
+ regmatch_t m[10];
+ ssize_t start, end, last, len = 0;
+ int empty = 0;
+ char *buf = NULL;
+
+ if (*text == '\0')
+ return (xstrdup(""));
+ if (regcomp(&r, pattern, flags) != 0)
+ return (NULL);
+
+ start = 0;
+ last = 0;
+ end = strlen(text);
+
+ while (start <= end) {
+ if (regexec(&r, text + start, nitems(m), m, 0) != 0) {
+ regsub_copy(&buf, &len, text, start, end);
+ break;
+ }
+
+ /*
+ * Append any text not part of this match (from the end of the
+ * last match).
+ */
+ regsub_copy(&buf, &len, text, last, m[0].rm_so + start);
+
+ /*
+ * If the last match was empty and this one isn't (it is either
+ * later or has matched text), expand this match. If it is
+ * empty, move on one character and try again from there.
+ */
+ if (empty ||
+ start + m[0].rm_so != last ||
+ m[0].rm_so != m[0].rm_eo) {
+ regsub_expand(&buf, &len, with, text + start, m,
+ nitems(m));
+
+ last = start + m[0].rm_eo;
+ start += m[0].rm_eo;
+ empty = 0;
+ } else {
+ last = start + m[0].rm_eo;
+ start += m[0].rm_eo + 1;
+ empty = 1;
+ }
+
+ /* Stop now if anchored to start. */
+ if (*pattern == '^') {
+ regsub_copy(&buf, &len, text, start, end);
+ break;
+ }
+ }
+ buf[len] = '\0';
+
+ regfree(&r);
+ return (buf);
+}
diff --git a/resize.c b/resize.c
new file mode 100644
index 0000000..457fee0
--- /dev/null
+++ b/resize.c
@@ -0,0 +1,467 @@
+/* $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 <string.h>
+
+#include "tmux.h"
+
+void
+resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
+{
+ int zoomed;
+
+ /* Check size limits. */
+ if (sx < WINDOW_MINIMUM)
+ sx = WINDOW_MINIMUM;
+ if (sx > WINDOW_MAXIMUM)
+ sx = WINDOW_MAXIMUM;
+ if (sy < WINDOW_MINIMUM)
+ sy = WINDOW_MINIMUM;
+ if (sy > WINDOW_MAXIMUM)
+ sy = WINDOW_MAXIMUM;
+
+ /* If the window is zoomed, unzoom. */
+ zoomed = w->flags & WINDOW_ZOOMED;
+ if (zoomed)
+ window_unzoom(w);
+
+ /* Resize the layout first. */
+ layout_resize(w, sx, sy);
+
+ /* Resize the window, it can be no smaller than the layout. */
+ if (sx < w->layout_root->sx)
+ sx = w->layout_root->sx;
+ if (sy < w->layout_root->sy)
+ sy = w->layout_root->sy;
+ window_resize(w, sx, sy, xpixel, ypixel);
+ log_debug("%s: @%u resized to %ux%u; layout %ux%u", __func__, w->id,
+ sx, sy, w->layout_root->sx, w->layout_root->sy);
+
+ /* Restore the window zoom state. */
+ if (zoomed)
+ window_zoom(w->active);
+
+ tty_update_window_offset(w);
+ server_redraw_window(w);
+ notify_window("window-layout-changed", w);
+ notify_window("window-resized", w);
+ w->flags &= ~WINDOW_RESIZE;
+}
+
+static int
+ignore_client_size(struct client *c)
+{
+ struct client *loop;
+
+ if (c->session == NULL)
+ return (1);
+ if (c->flags & CLIENT_NOSIZEFLAGS)
+ return (1);
+ if (c->flags & CLIENT_IGNORESIZE) {
+ /*
+ * Ignore flagged clients if there are any attached clients
+ * that aren't flagged.
+ */
+ TAILQ_FOREACH (loop, &clients, entry) {
+ if (loop->session == NULL)
+ continue;
+ if (loop->flags & CLIENT_NOSIZEFLAGS)
+ continue;
+ if (~loop->flags & CLIENT_IGNORESIZE)
+ return (1);
+ }
+ }
+ if ((c->flags & CLIENT_CONTROL) &&
+ (~c->flags & CLIENT_SIZECHANGED) &&
+ (~c->flags & CLIENT_WINDOWSIZECHANGED))
+ return (1);
+ return (0);
+}
+
+static u_int
+clients_with_window(struct window *w)
+{
+ struct client *loop;
+ u_int n = 0;
+
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (ignore_client_size(loop) || !session_has(loop->session, w))
+ continue;
+ if (++n > 1)
+ break;
+ }
+ return (n);
+}
+
+static int
+clients_calculate_size(int type, int current, struct client *c,
+ struct session *s, struct window *w, int (*skip_client)(struct client *,
+ int, int, struct session *, struct window *), u_int *sx, u_int *sy,
+ u_int *xpixel, u_int *ypixel)
+{
+ struct client *loop;
+ struct client_window *cw;
+ u_int cx, cy, n = 0;
+
+ /*
+ * Start comparing with 0 for largest and UINT_MAX for smallest or
+ * latest.
+ */
+ if (type == WINDOW_SIZE_LARGEST) {
+ *sx = 0;
+ *sy = 0;
+ } else if (type == WINDOW_SIZE_MANUAL) {
+ *sx = w->manual_sx;
+ *sy = w->manual_sy;
+ log_debug("%s: manual size %ux%u", __func__, *sx, *sy);
+ } else {
+ *sx = UINT_MAX;
+ *sy = UINT_MAX;
+ }
+ *xpixel = *ypixel = 0;
+
+ /*
+ * For latest, count the number of clients with this window. We only
+ * care if there is more than one.
+ */
+ if (type == WINDOW_SIZE_LATEST && w != NULL)
+ n = clients_with_window(w);
+
+ /* Skip setting the size if manual */
+ if (type == WINDOW_SIZE_MANUAL)
+ goto skip;
+
+ /* Loop over the clients and work out the size. */
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop != c && ignore_client_size(loop)) {
+ log_debug("%s: ignoring %s (1)", __func__, loop->name);
+ continue;
+ }
+ if (loop != c && skip_client(loop, type, current, s, w)) {
+ log_debug("%s: skipping %s (1)", __func__, loop->name);
+ continue;
+ }
+
+ /*
+ * If there are multiple clients attached, only accept the
+ * latest client; otherwise let the only client be chosen as
+ * for smallest.
+ */
+ if (type == WINDOW_SIZE_LATEST && n > 1 && loop != w->latest) {
+ log_debug("%s: %s is not latest", __func__, loop->name);
+ continue;
+ }
+
+ /*
+ * If the client has a per-window size, use this instead if it is
+ * smaller.
+ */
+ if (w != NULL)
+ cw = server_client_get_client_window(loop, w->id);
+ else
+ cw = NULL;
+
+ /* Work out this client's size. */
+ if (cw != NULL && cw->sx != 0 && cw->sy != 0) {
+ cx = cw->sx;
+ cy = cw->sy;
+ } else {
+ cx = loop->tty.sx;
+ cy = loop->tty.sy - status_line_size(loop);
+ }
+
+ /*
+ * If it is larger or smaller than the best so far, update the
+ * new size.
+ */
+ if (type == WINDOW_SIZE_LARGEST) {
+ if (cx > *sx)
+ *sx = cx;
+ if (cy > *sy)
+ *sy = cy;
+ } else {
+ if (cx < *sx)
+ *sx = cx;
+ if (cy < *sy)
+ *sy = cy;
+ }
+ if (loop->tty.xpixel > *xpixel && loop->tty.ypixel > *ypixel) {
+ *xpixel = loop->tty.xpixel;
+ *ypixel = loop->tty.ypixel;
+ }
+ log_debug("%s: after %s (%ux%u), size is %ux%u", __func__,
+ loop->name, cx, cy, *sx, *sy);
+ }
+ if (*sx != UINT_MAX && *sy != UINT_MAX)
+ log_debug("%s: calculated size %ux%u", __func__, *sx, *sy);
+ else
+ log_debug("%s: no calculated size", __func__);
+
+skip:
+ /*
+ * Do not allow any size to be larger than the per-client window size
+ * if one exists.
+ */
+ if (w != NULL) {
+ TAILQ_FOREACH(loop, &clients, entry) {
+ if (loop != c && ignore_client_size(loop))
+ continue;
+ if (loop != c && skip_client(loop, type, current, s, w))
+ continue;
+
+ /* Look up per-window size if any. */
+ if (~loop->flags & CLIENT_WINDOWSIZECHANGED)
+ continue;
+ cw = server_client_get_client_window(loop, w->id);
+ if (cw == NULL)
+ continue;
+
+ /* Clamp the size. */
+ log_debug("%s: %s size for @%u is %ux%u", __func__,
+ loop->name, w->id, cw->sx, cw->sy);
+ if (cw->sx != 0 && *sx > cw->sx)
+ *sx = cw->sx;
+ if (cw->sy != 0 && *sy > cw->sy)
+ *sy = cw->sy;
+ }
+ }
+ if (*sx != UINT_MAX && *sy != UINT_MAX)
+ log_debug("%s: calculated size %ux%u", __func__, *sx, *sy);
+ else
+ log_debug("%s: no calculated size", __func__);
+
+ /* Return whether a suitable size was found. */
+ if (type == WINDOW_SIZE_MANUAL) {
+ log_debug("%s: type is manual", __func__);
+ return (1);
+ }
+ if (type == WINDOW_SIZE_LARGEST) {
+ log_debug("%s: type is largest", __func__);
+ return (*sx != 0 && *sy != 0);
+ }
+ if (type == WINDOW_SIZE_LATEST)
+ log_debug("%s: type is latest", __func__);
+ else
+ log_debug("%s: type is smallest", __func__);
+ return (*sx != UINT_MAX && *sy != UINT_MAX);
+}
+
+static int
+default_window_size_skip_client(struct client *loop, int type,
+ __unused int current, struct session *s, struct window *w)
+{
+ /*
+ * Latest checks separately, so do not check here. Otherwise only
+ * include clients where the session contains the window or where the
+ * session is the given session.
+ */
+ if (type == WINDOW_SIZE_LATEST)
+ return (0);
+ if (w != NULL && !session_has(loop->session, w))
+ return (1);
+ if (w == NULL && loop->session != s)
+ return (1);
+ return (0);
+}
+
+void
+default_window_size(struct client *c, struct session *s, struct window *w,
+ u_int *sx, u_int *sy, u_int *xpixel, u_int *ypixel, int type)
+{
+ const char *value;
+
+ /* Get type if not provided. */
+ if (type == -1)
+ type = options_get_number(global_w_options, "window-size");
+
+ /*
+ * Latest clients can use the given client if suitable. If there is no
+ * client and no window, use the default size as for manual type.
+ */
+ if (type == WINDOW_SIZE_LATEST && c != NULL && !ignore_client_size(c)) {
+ *sx = c->tty.sx;
+ *sy = c->tty.sy - status_line_size(c);
+ *xpixel = c->tty.xpixel;
+ *ypixel = c->tty.ypixel;
+ log_debug("%s: using %ux%u from %s", __func__, *sx, *sy,
+ c->name);
+ goto done;
+ }
+
+ /*
+ * Ignore the given client if it is a control client - the creating
+ * client should only affect the size if it is not a control client.
+ */
+ if (c != NULL && (c->flags & CLIENT_CONTROL))
+ c = NULL;
+
+ /*
+ * Look for a client to base the size on. If none exists (or the type
+ * is manual), use the default-size option.
+ */
+ if (!clients_calculate_size(type, 0, c, s, w,
+ default_window_size_skip_client, sx, sy, xpixel, ypixel)) {
+ value = options_get_string(s->options, "default-size");
+ if (sscanf(value, "%ux%u", sx, sy) != 2) {
+ *sx = 80;
+ *sy = 24;
+ }
+ log_debug("%s: using %ux%u from default-size", __func__, *sx,
+ *sy);
+ }
+
+done:
+ /* Make sure the limits are enforced. */
+ if (*sx < WINDOW_MINIMUM)
+ *sx = WINDOW_MINIMUM;
+ if (*sx > WINDOW_MAXIMUM)
+ *sx = WINDOW_MAXIMUM;
+ if (*sy < WINDOW_MINIMUM)
+ *sy = WINDOW_MINIMUM;
+ if (*sy > WINDOW_MAXIMUM)
+ *sy = WINDOW_MAXIMUM;
+ log_debug("%s: resulting size is %ux%u", __func__, *sx, *sy);
+}
+
+static int
+recalculate_size_skip_client(struct client *loop, __unused int type,
+ int current, __unused struct session *s, struct window *w)
+{
+ /*
+ * If the current flag is set, then skip any client where this window
+ * is not the current window - this is used for aggressive-resize.
+ * Otherwise skip any session that doesn't contain the window.
+ */
+ if (loop->session->curw == NULL)
+ return (1);
+ if (current)
+ return (loop->session->curw->window != w);
+ return (session_has(loop->session, w) == 0);
+}
+
+void
+recalculate_size(struct window *w, int now)
+{
+ u_int sx, sy, xpixel = 0, ypixel = 0;
+ int type, current, changed;
+
+ /*
+ * Do not attempt to resize windows which have no pane, they must be on
+ * the way to destruction.
+ */
+ if (w->active == NULL)
+ return;
+ log_debug("%s: @%u is %ux%u", __func__, w->id, w->sx, w->sy);
+
+ /*
+ * Type is manual, smallest, largest, latest. Current is the
+ * aggressive-resize option (do not resize based on clients where the
+ * window is not the current window).
+ */
+ type = options_get_number(w->options, "window-size");
+ current = options_get_number(w->options, "aggressive-resize");
+
+ /* Look for a suitable client and get the new size. */
+ changed = clients_calculate_size(type, current, NULL, NULL, w,
+ recalculate_size_skip_client, &sx, &sy, &xpixel, &ypixel);
+
+ /*
+ * Make sure the size has actually changed. If the window has already
+ * got a resize scheduled, then use the new size; otherwise the old.
+ */
+ if (w->flags & WINDOW_RESIZE) {
+ if (!now && changed && w->new_sx == sx && w->new_sy == sy)
+ changed = 0;
+ } else {
+ if (!now && changed && w->sx == sx && w->sy == sy)
+ changed = 0;
+ }
+
+ /*
+ * If the size hasn't changed, update the window offset but not the
+ * size.
+ */
+ if (!changed) {
+ log_debug("%s: @%u no size change", __func__, w->id);
+ tty_update_window_offset(w);
+ return;
+ }
+
+ /*
+ * If the now flag is set or if the window is sized manually, change
+ * the size immediately. Otherwise set the flag and it will be done
+ * later.
+ */
+ log_debug("%s: @%u new size %ux%u", __func__, w->id, sx, sy);
+ if (now || type == WINDOW_SIZE_MANUAL)
+ resize_window(w, sx, sy, xpixel, ypixel);
+ else {
+ w->new_sx = sx;
+ w->new_sy = sy;
+ w->new_xpixel = xpixel;
+ w->new_ypixel = ypixel;
+
+ w->flags |= WINDOW_RESIZE;
+ tty_update_window_offset(w);
+ }
+}
+
+void
+recalculate_sizes(void)
+{
+ recalculate_sizes_now(0);
+}
+
+void
+recalculate_sizes_now(int now)
+{
+ struct session *s;
+ struct client *c;
+ struct window *w;
+
+ /*
+ * Clear attached count and update saved status line information for
+ * each session.
+ */
+ RB_FOREACH(s, sessions, &sessions) {
+ s->attached = 0;
+ status_update_cache(s);
+ }
+
+ /*
+ * Increment attached count and check the status line size for each
+ * client.
+ */
+ TAILQ_FOREACH(c, &clients, entry) {
+ s = c->session;
+ if (s != NULL && !(c->flags & CLIENT_UNATTACHEDFLAGS))
+ s->attached++;
+ if (ignore_client_size(c))
+ continue;
+ if (c->tty.sy <= s->statuslines || (c->flags & CLIENT_CONTROL))
+ c->flags |= CLIENT_STATUSOFF;
+ else
+ c->flags &= ~CLIENT_STATUSOFF;
+ }
+
+ /* Walk each window and adjust the size. */
+ RB_FOREACH(w, windows, &windows)
+ recalculate_size(w, now);
+}
diff --git a/screen-redraw.c b/screen-redraw.c
new file mode 100644
index 0000000..c4906ab
--- /dev/null
+++ b/screen-redraw.c
@@ -0,0 +1,859 @@
+/* $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 void screen_redraw_draw_borders(struct screen_redraw_ctx *);
+static void screen_redraw_draw_panes(struct screen_redraw_ctx *);
+static void screen_redraw_draw_status(struct screen_redraw_ctx *);
+static void screen_redraw_draw_pane(struct screen_redraw_ctx *,
+ struct window_pane *);
+static void screen_redraw_set_context(struct client *,
+ struct screen_redraw_ctx *);
+
+#define START_ISOLATE "\342\201\246"
+#define END_ISOLATE "\342\201\251"
+
+/* Border in relation to a pane. */
+enum screen_redraw_border_type {
+ SCREEN_REDRAW_OUTSIDE,
+ SCREEN_REDRAW_INSIDE,
+ SCREEN_REDRAW_BORDER_LEFT,
+ SCREEN_REDRAW_BORDER_RIGHT,
+ SCREEN_REDRAW_BORDER_TOP,
+ SCREEN_REDRAW_BORDER_BOTTOM
+};
+#define BORDER_MARKERS " +,.-"
+
+/* Get cell border character. */
+static void
+screen_redraw_border_set(struct window *w, struct window_pane *wp,
+ enum pane_lines pane_lines, int cell_type, struct grid_cell *gc)
+{
+ u_int idx;
+
+ if (cell_type == CELL_OUTSIDE && w->fill_character != NULL) {
+ utf8_copy(&gc->data, &w->fill_character[0]);
+ return;
+ }
+
+ switch (pane_lines) {
+ case PANE_LINES_NUMBER:
+ if (cell_type == CELL_OUTSIDE) {
+ gc->attr |= GRID_ATTR_CHARSET;
+ utf8_set(&gc->data, CELL_BORDERS[CELL_OUTSIDE]);
+ break;
+ }
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ if (wp != NULL && window_pane_index(wp, &idx) == 0)
+ utf8_set(&gc->data, '0' + (idx % 10));
+ else
+ utf8_set(&gc->data, '*');
+ break;
+ case PANE_LINES_DOUBLE:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_copy(&gc->data, tty_acs_double_borders(cell_type));
+ break;
+ case PANE_LINES_HEAVY:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
+ break;
+ case PANE_LINES_SIMPLE:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
+ break;
+ default:
+ gc->attr |= GRID_ATTR_CHARSET;
+ utf8_set(&gc->data, CELL_BORDERS[cell_type]);
+ break;
+ }
+}
+
+/* Return if window has only two panes. */
+static int
+screen_redraw_two_panes(struct window *w, int direction)
+{
+ struct window_pane *wp;
+
+ wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
+ if (wp == NULL)
+ return (0); /* one pane */
+ if (TAILQ_NEXT(wp, entry) != NULL)
+ return (0); /* more than two panes */
+ if (direction == 0 && wp->xoff == 0)
+ return (0);
+ if (direction == 1 && wp->yoff == 0)
+ return (0);
+ return (1);
+}
+
+/* Check if cell is on the border of a pane. */
+static enum screen_redraw_border_type
+screen_redraw_pane_border(struct window_pane *wp, u_int px, u_int py,
+ int pane_status)
+{
+ struct options *oo = wp->window->options;
+ int split = 0;
+ u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy;
+
+ /* Inside pane. */
+ if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey)
+ return (SCREEN_REDRAW_INSIDE);
+
+ /* Get pane indicator. */
+ switch (options_get_number(oo, "pane-border-indicators")) {
+ case PANE_BORDER_COLOUR:
+ case PANE_BORDER_BOTH:
+ split = 1;
+ break;
+ }
+
+ /* Left/right borders. */
+ if (pane_status == PANE_STATUS_OFF) {
+ if (screen_redraw_two_panes(wp->window, 0) && split) {
+ if (wp->xoff == 0 && px == wp->sx && py <= wp->sy / 2)
+ return (SCREEN_REDRAW_BORDER_RIGHT);
+ if (wp->xoff != 0 &&
+ px == wp->xoff - 1 &&
+ py > wp->sy / 2)
+ return (SCREEN_REDRAW_BORDER_LEFT);
+ } else {
+ if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) {
+ if (wp->xoff != 0 && px == wp->xoff - 1)
+ return (SCREEN_REDRAW_BORDER_LEFT);
+ if (px == ex)
+ return (SCREEN_REDRAW_BORDER_RIGHT);
+ }
+ }
+ } else {
+ if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) {
+ if (wp->xoff != 0 && px == wp->xoff - 1)
+ return (SCREEN_REDRAW_BORDER_LEFT);
+ if (px == ex)
+ return (SCREEN_REDRAW_BORDER_RIGHT);
+ }
+ }
+
+ /* Top/bottom borders. */
+ if (pane_status == PANE_STATUS_OFF) {
+ if (screen_redraw_two_panes(wp->window, 1) && split) {
+ if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2)
+ return (SCREEN_REDRAW_BORDER_BOTTOM);
+ if (wp->yoff != 0 &&
+ py == wp->yoff - 1 &&
+ px > wp->sx / 2)
+ return (SCREEN_REDRAW_BORDER_TOP);
+ } else {
+ if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) {
+ if (wp->yoff != 0 && py == wp->yoff - 1)
+ return (SCREEN_REDRAW_BORDER_TOP);
+ if (py == ey)
+ return (SCREEN_REDRAW_BORDER_BOTTOM);
+ }
+ }
+ } else if (pane_status == PANE_STATUS_TOP) {
+ if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) {
+ if (wp->yoff != 0 && py == wp->yoff - 1)
+ return (SCREEN_REDRAW_BORDER_TOP);
+ }
+ } else {
+ if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) {
+ if (py == ey)
+ return (SCREEN_REDRAW_BORDER_BOTTOM);
+ }
+ }
+
+ /* Outside pane. */
+ return (SCREEN_REDRAW_OUTSIDE);
+}
+
+/* Check if a cell is on a border. */
+static int
+screen_redraw_cell_border(struct client *c, u_int px, u_int py, int pane_status)
+{
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+
+ /* Outside the window? */
+ if (px > w->sx || py > w->sy)
+ return (0);
+
+ /* On the window border? */
+ if (px == w->sx || py == w->sy)
+ return (1);
+
+ /* Check all the panes. */
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (!window_pane_visible(wp))
+ continue;
+ switch (screen_redraw_pane_border(wp, px, py, pane_status)) {
+ case SCREEN_REDRAW_INSIDE:
+ return (0);
+ case SCREEN_REDRAW_OUTSIDE:
+ break;
+ default:
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/* Work out type of border cell from surrounding cells. */
+static int
+screen_redraw_type_of_cell(struct client *c, u_int px, u_int py,
+ int pane_status)
+{
+ struct window *w = c->session->curw->window;
+ u_int sx = w->sx, sy = w->sy;
+ int borders = 0;
+
+ /* Is this outside the window? */
+ if (px > sx || py > sy)
+ return (CELL_OUTSIDE);
+
+ /*
+ * Construct a bitmask of whether the cells to the left (bit 4), right,
+ * top, and bottom (bit 1) of this cell are borders.
+ */
+ if (px == 0 || screen_redraw_cell_border(c, px - 1, py, pane_status))
+ borders |= 8;
+ if (px <= sx && screen_redraw_cell_border(c, px + 1, py, pane_status))
+ borders |= 4;
+ if (pane_status == PANE_STATUS_TOP) {
+ if (py != 0 &&
+ screen_redraw_cell_border(c, px, py - 1, pane_status))
+ borders |= 2;
+ if (screen_redraw_cell_border(c, px, py + 1, pane_status))
+ borders |= 1;
+ } else if (pane_status == PANE_STATUS_BOTTOM) {
+ if (py == 0 ||
+ screen_redraw_cell_border(c, px, py - 1, pane_status))
+ borders |= 2;
+ if (py != sy - 1 &&
+ screen_redraw_cell_border(c, px, py + 1, pane_status))
+ borders |= 1;
+ } else {
+ if (py == 0 ||
+ screen_redraw_cell_border(c, px, py - 1, pane_status))
+ borders |= 2;
+ if (screen_redraw_cell_border(c, px, py + 1, pane_status))
+ borders |= 1;
+ }
+
+ /*
+ * Figure out what kind of border this cell is. Only one bit set
+ * doesn't make sense (can't have a border cell with no others
+ * connected).
+ */
+ switch (borders) {
+ case 15: /* 1111, left right top bottom */
+ return (CELL_JOIN);
+ case 14: /* 1110, left right top */
+ return (CELL_BOTTOMJOIN);
+ case 13: /* 1101, left right bottom */
+ return (CELL_TOPJOIN);
+ case 12: /* 1100, left right */
+ return (CELL_LEFTRIGHT);
+ case 11: /* 1011, left top bottom */
+ return (CELL_RIGHTJOIN);
+ case 10: /* 1010, left top */
+ return (CELL_BOTTOMRIGHT);
+ case 9: /* 1001, left bottom */
+ return (CELL_TOPRIGHT);
+ case 7: /* 0111, right top bottom */
+ return (CELL_LEFTJOIN);
+ case 6: /* 0110, right top */
+ return (CELL_BOTTOMLEFT);
+ case 5: /* 0101, right bottom */
+ return (CELL_TOPLEFT);
+ case 3: /* 0011, top bottom */
+ return (CELL_TOPBOTTOM);
+ }
+ return (CELL_OUTSIDE);
+}
+
+/* Check if cell inside a pane. */
+static int
+screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status,
+ struct window_pane **wpp)
+{
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp, *active;
+ int border;
+ u_int right, line;
+
+ *wpp = NULL;
+
+ if (px > w->sx || py > w->sy)
+ return (CELL_OUTSIDE);
+ if (px == w->sx || py == w->sy) /* window border */
+ return (screen_redraw_type_of_cell(c, px, py, pane_status));
+
+ if (pane_status != PANE_STATUS_OFF) {
+ active = wp = server_client_get_pane(c);
+ do {
+ if (!window_pane_visible(wp))
+ goto next1;
+
+ if (pane_status == PANE_STATUS_TOP)
+ line = wp->yoff - 1;
+ else
+ line = wp->yoff + wp->sy;
+ right = wp->xoff + 2 + wp->status_size - 1;
+
+ if (py == line && px >= wp->xoff + 2 && px <= right)
+ return (CELL_INSIDE);
+
+ next1:
+ wp = TAILQ_NEXT(wp, entry);
+ if (wp == NULL)
+ wp = TAILQ_FIRST(&w->panes);
+ } while (wp != active);
+ }
+
+ active = wp = server_client_get_pane(c);
+ do {
+ if (!window_pane_visible(wp))
+ goto next2;
+ *wpp = wp;
+
+ /*
+ * If definitely inside, return. If not on border, skip.
+ * Otherwise work out the cell.
+ */
+ border = screen_redraw_pane_border(wp, px, py, pane_status);
+ if (border == SCREEN_REDRAW_INSIDE)
+ return (CELL_INSIDE);
+ if (border == SCREEN_REDRAW_OUTSIDE)
+ goto next2;
+ return (screen_redraw_type_of_cell(c, px, py, pane_status));
+
+ next2:
+ wp = TAILQ_NEXT(wp, entry);
+ if (wp == NULL)
+ wp = TAILQ_FIRST(&w->panes);
+ } while (wp != active);
+
+ return (CELL_OUTSIDE);
+}
+
+/* Check if the border of a particular pane. */
+static int
+screen_redraw_check_is(u_int px, u_int py, int pane_status,
+ struct window_pane *wp)
+{
+ enum screen_redraw_border_type border;
+
+ border = screen_redraw_pane_border(wp, px, py, pane_status);
+ if (border != SCREEN_REDRAW_INSIDE && border != SCREEN_REDRAW_OUTSIDE)
+ return (1);
+ return (0);
+}
+
+/* Update pane status. */
+static int
+screen_redraw_make_pane_status(struct client *c, struct window_pane *wp,
+ struct screen_redraw_ctx *rctx, enum pane_lines pane_lines)
+{
+ struct window *w = wp->window;
+ struct grid_cell gc;
+ const char *fmt;
+ struct format_tree *ft;
+ char *expanded;
+ int pane_status = rctx->pane_status;
+ u_int width, i, cell_type, px, py;
+ struct screen_write_ctx ctx;
+ struct screen old;
+
+ ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS);
+ format_defaults(ft, c, c->session, c->session->curw, wp);
+
+ if (wp == server_client_get_pane(c))
+ style_apply(&gc, w->options, "pane-active-border-style", ft);
+ else
+ style_apply(&gc, w->options, "pane-border-style", ft);
+ fmt = options_get_string(wp->options, "pane-border-format");
+
+ expanded = format_expand_time(ft, fmt);
+ if (wp->sx < 4)
+ wp->status_size = width = 0;
+ else
+ wp->status_size = width = wp->sx - 4;
+
+ memcpy(&old, &wp->status_screen, sizeof old);
+ screen_init(&wp->status_screen, width, 1, 0);
+ wp->status_screen.mode = 0;
+
+ screen_write_start(&ctx, &wp->status_screen);
+
+ for (i = 0; i < width; i++) {
+ px = wp->xoff + 2 + i;
+ if (rctx->pane_status == PANE_STATUS_TOP)
+ py = wp->yoff - 1;
+ else
+ py = wp->yoff + wp->sy;
+ cell_type = screen_redraw_type_of_cell(c, px, py, pane_status);
+ screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc);
+ screen_write_cell(&ctx, &gc);
+ }
+ gc.attr &= ~GRID_ATTR_CHARSET;
+
+ screen_write_cursormove(&ctx, 0, 0, 0);
+ format_draw(&ctx, &gc, width, expanded, NULL, 0);
+ screen_write_stop(&ctx);
+
+ free(expanded);
+ format_free(ft);
+
+ if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
+ screen_free(&old);
+ return (0);
+ }
+ screen_free(&old);
+ return (1);
+}
+
+/* Draw pane status. */
+static void
+screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx)
+{
+ struct client *c = ctx->c;
+ struct window *w = c->session->curw->window;
+ struct tty *tty = &c->tty;
+ struct window_pane *wp;
+ struct screen *s;
+ u_int i, x, width, xoff, yoff, size;
+
+ log_debug("%s: %s @%u", __func__, c->name, w->id);
+
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (!window_pane_visible(wp))
+ continue;
+ s = &wp->status_screen;
+
+ size = wp->status_size;
+ if (ctx->pane_status == PANE_STATUS_TOP)
+ yoff = wp->yoff - 1;
+ else
+ yoff = wp->yoff + wp->sy;
+ xoff = wp->xoff + 2;
+
+ if (xoff + size <= ctx->ox ||
+ xoff >= ctx->ox + ctx->sx ||
+ yoff < ctx->oy ||
+ yoff >= ctx->oy + ctx->sy)
+ continue;
+
+ if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) {
+ /* All visible. */
+ i = 0;
+ x = xoff - ctx->ox;
+ width = size;
+ } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) {
+ /* Both left and right not visible. */
+ i = ctx->ox;
+ x = 0;
+ width = ctx->sx;
+ } else if (xoff < ctx->ox) {
+ /* Left not visible. */
+ i = ctx->ox - xoff;
+ x = 0;
+ width = size - i;
+ } else {
+ /* Right not visible. */
+ i = 0;
+ x = xoff - ctx->ox;
+ width = size - x;
+ }
+
+ if (ctx->statustop)
+ yoff += ctx->statuslines;
+ tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy,
+ &grid_default_cell, NULL);
+ }
+ tty_cursor(tty, 0, 0);
+}
+
+/* Update status line and change flags if unchanged. */
+static int
+screen_redraw_update(struct client *c, int flags)
+{
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+ struct options *wo = w->options;
+ int redraw;
+ enum pane_lines lines;
+ struct screen_redraw_ctx ctx;
+
+ if (c->message_string != NULL)
+ redraw = status_message_redraw(c);
+ else if (c->prompt_string != NULL)
+ redraw = status_prompt_redraw(c);
+ else
+ redraw = status_redraw(c);
+ if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS))
+ flags &= ~CLIENT_REDRAWSTATUS;
+
+ if (c->overlay_draw != NULL)
+ flags |= CLIENT_REDRAWOVERLAY;
+
+ if (options_get_number(wo, "pane-border-status") != PANE_STATUS_OFF) {
+ screen_redraw_set_context(c, &ctx);
+ lines = options_get_number(wo, "pane-border-lines");
+ redraw = 0;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (screen_redraw_make_pane_status(c, wp, &ctx, lines))
+ redraw = 1;
+ }
+ if (redraw)
+ flags |= CLIENT_REDRAWBORDERS;
+ }
+ return (flags);
+}
+
+/* Set up redraw context. */
+static void
+screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx)
+{
+ struct session *s = c->session;
+ struct options *oo = s->options;
+ struct window *w = s->curw->window;
+ struct options *wo = w->options;
+ u_int lines;
+
+ memset(ctx, 0, sizeof *ctx);
+ ctx->c = c;
+
+ lines = status_line_size(c);
+ if (c->message_string != NULL || c->prompt_string != NULL)
+ lines = (lines == 0) ? 1 : lines;
+ if (lines != 0 && options_get_number(oo, "status-position") == 0)
+ ctx->statustop = 1;
+ ctx->statuslines = lines;
+
+ ctx->pane_status = options_get_number(wo, "pane-border-status");
+ ctx->pane_lines = options_get_number(wo, "pane-border-lines");
+
+ tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy);
+
+ log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name,
+ w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines,
+ ctx->statustop);
+}
+
+/* Redraw entire screen. */
+void
+screen_redraw_screen(struct client *c)
+{
+ struct screen_redraw_ctx ctx;
+ int flags;
+
+ if (c->flags & CLIENT_SUSPENDED)
+ return;
+
+ flags = screen_redraw_update(c, c->flags);
+ if ((flags & CLIENT_ALLREDRAWFLAGS) == 0)
+ return;
+
+ screen_redraw_set_context(c, &ctx);
+ tty_sync_start(&c->tty);
+ tty_update_mode(&c->tty, c->tty.mode, NULL);
+
+ if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) {
+ log_debug("%s: redrawing borders", c->name);
+ if (ctx.pane_status != PANE_STATUS_OFF)
+ screen_redraw_draw_pane_status(&ctx);
+ screen_redraw_draw_borders(&ctx);
+ }
+ if (flags & CLIENT_REDRAWWINDOW) {
+ log_debug("%s: redrawing panes", c->name);
+ screen_redraw_draw_panes(&ctx);
+ }
+ if (ctx.statuslines != 0 &&
+ (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) {
+ log_debug("%s: redrawing status", c->name);
+ screen_redraw_draw_status(&ctx);
+ }
+ if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) {
+ log_debug("%s: redrawing overlay", c->name);
+ c->overlay_draw(c, c->overlay_data, &ctx);
+ }
+
+ tty_reset(&c->tty);
+}
+
+/* Redraw a single pane. */
+void
+screen_redraw_pane(struct client *c, struct window_pane *wp)
+{
+ struct screen_redraw_ctx ctx;
+
+ if (!window_pane_visible(wp))
+ return;
+
+ screen_redraw_set_context(c, &ctx);
+ tty_sync_start(&c->tty);
+ tty_update_mode(&c->tty, c->tty.mode, NULL);
+
+ screen_redraw_draw_pane(&ctx, wp);
+
+ tty_reset(&c->tty);
+}
+
+/* Get border cell style. */
+static const struct grid_cell *
+screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x,
+ u_int y, struct window_pane *wp)
+{
+ struct client *c = ctx->c;
+ struct session *s = c->session;
+ struct window *w = s->curw->window;
+ struct window_pane *active = server_client_get_pane(c);
+ struct options *oo = w->options;
+ struct format_tree *ft;
+
+ if (wp->border_gc_set)
+ return (&wp->border_gc);
+ wp->border_gc_set = 1;
+
+ ft = format_create_defaults(NULL, c, s, s->curw, wp);
+ if (screen_redraw_check_is(x, y, ctx->pane_status, active))
+ style_apply(&wp->border_gc, oo, "pane-active-border-style", ft);
+ else
+ style_apply(&wp->border_gc, oo, "pane-border-style", ft);
+ format_free(ft);
+
+ return (&wp->border_gc);
+}
+
+/* Draw a border cell. */
+static void
+screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j)
+{
+ struct client *c = ctx->c;
+ struct session *s = c->session;
+ struct window *w = s->curw->window;
+ struct options *oo = w->options;
+ struct tty *tty = &c->tty;
+ struct format_tree *ft;
+ struct window_pane *wp, *active = server_client_get_pane(c);
+ struct grid_cell gc;
+ const struct grid_cell *tmp;
+ struct overlay_ranges r;
+ u_int cell_type, x = ctx->ox + i, y = ctx->oy + j;
+ int arrows = 0, border;
+ int pane_status = ctx->pane_status, isolates;
+
+ if (c->overlay_check != NULL) {
+ c->overlay_check(c, c->overlay_data, x, y, 1, &r);
+ if (r.nx[0] + r.nx[1] == 0)
+ return;
+ }
+
+ cell_type = screen_redraw_check_cell(c, x, y, pane_status, &wp);
+ if (cell_type == CELL_INSIDE)
+ return;
+
+ if (wp == NULL) {
+ if (!ctx->no_pane_gc_set) {
+ ft = format_create_defaults(NULL, c, s, s->curw, NULL);
+ memcpy(&ctx->no_pane_gc, &grid_default_cell, sizeof gc);
+ style_add(&ctx->no_pane_gc, oo, "pane-border-style",
+ ft);
+ format_free(ft);
+ ctx->no_pane_gc_set = 1;
+ }
+ memcpy(&gc, &ctx->no_pane_gc, sizeof gc);
+ } else {
+ tmp = screen_redraw_draw_borders_style(ctx, x, y, wp);
+ if (tmp == NULL)
+ return;
+ memcpy(&gc, tmp, sizeof gc);
+
+ if (server_is_marked(s, s->curw, marked_pane.wp) &&
+ screen_redraw_check_is(x, y, pane_status, marked_pane.wp))
+ gc.attr ^= GRID_ATTR_REVERSE;
+ }
+ screen_redraw_border_set(w, wp, ctx->pane_lines, cell_type, &gc);
+
+ if (cell_type == CELL_TOPBOTTOM &&
+ (c->flags & CLIENT_UTF8) &&
+ tty_term_has(tty->term, TTYC_BIDI))
+ isolates = 1;
+ else
+ isolates = 0;
+
+ if (ctx->statustop)
+ tty_cursor(tty, i, ctx->statuslines + j);
+ else
+ tty_cursor(tty, i, j);
+ if (isolates)
+ tty_puts(tty, END_ISOLATE);
+
+ switch (options_get_number(oo, "pane-border-indicators")) {
+ case PANE_BORDER_ARROWS:
+ case PANE_BORDER_BOTH:
+ arrows = 1;
+ break;
+ }
+
+ if (wp != NULL && arrows) {
+ border = screen_redraw_pane_border(active, x, y, pane_status);
+ if (((i == wp->xoff + 1 &&
+ (cell_type == CELL_LEFTRIGHT ||
+ (cell_type == CELL_TOPJOIN &&
+ border == SCREEN_REDRAW_BORDER_BOTTOM) ||
+ (cell_type == CELL_BOTTOMJOIN &&
+ border == SCREEN_REDRAW_BORDER_TOP))) ||
+ (j == wp->yoff + 1 &&
+ (cell_type == CELL_TOPBOTTOM ||
+ (cell_type == CELL_LEFTJOIN &&
+ border == SCREEN_REDRAW_BORDER_RIGHT) ||
+ (cell_type == CELL_RIGHTJOIN &&
+ border == SCREEN_REDRAW_BORDER_LEFT)))) &&
+ screen_redraw_check_is(x, y, pane_status, active)) {
+ gc.attr |= GRID_ATTR_CHARSET;
+ utf8_set(&gc.data, BORDER_MARKERS[border]);
+ }
+ }
+
+ tty_cell(tty, &gc, &grid_default_cell, NULL);
+ if (isolates)
+ tty_puts(tty, START_ISOLATE);
+}
+
+/* Draw the borders. */
+static void
+screen_redraw_draw_borders(struct screen_redraw_ctx *ctx)
+{
+ struct client *c = ctx->c;
+ struct session *s = c->session;
+ struct window *w = s->curw->window;
+ struct window_pane *wp;
+ u_int i, j;
+
+ log_debug("%s: %s @%u", __func__, c->name, w->id);
+
+ TAILQ_FOREACH(wp, &w->panes, entry)
+ wp->border_gc_set = 0;
+
+ for (j = 0; j < c->tty.sy - ctx->statuslines; j++) {
+ for (i = 0; i < c->tty.sx; i++)
+ screen_redraw_draw_borders_cell(ctx, i, j);
+ }
+}
+
+/* Draw the panes. */
+static void
+screen_redraw_draw_panes(struct screen_redraw_ctx *ctx)
+{
+ struct client *c = ctx->c;
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+
+ log_debug("%s: %s @%u", __func__, c->name, w->id);
+
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (window_pane_visible(wp))
+ screen_redraw_draw_pane(ctx, wp);
+ }
+}
+
+/* Draw the status line. */
+static void
+screen_redraw_draw_status(struct screen_redraw_ctx *ctx)
+{
+ struct client *c = ctx->c;
+ struct window *w = c->session->curw->window;
+ struct tty *tty = &c->tty;
+ struct screen *s = c->status.active;
+ u_int i, y;
+
+ log_debug("%s: %s @%u", __func__, c->name, w->id);
+
+ if (ctx->statustop)
+ y = 0;
+ else
+ y = c->tty.sy - ctx->statuslines;
+ for (i = 0; i < ctx->statuslines; i++) {
+ tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i,
+ &grid_default_cell, NULL);
+ }
+}
+
+/* Draw one pane. */
+static void
+screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp)
+{
+ struct client *c = ctx->c;
+ struct window *w = c->session->curw->window;
+ struct tty *tty = &c->tty;
+ struct screen *s = wp->screen;
+ struct colour_palette *palette = &wp->palette;
+ struct grid_cell defaults;
+ u_int i, j, top, x, y, width;
+
+ log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id);
+
+ if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx)
+ return;
+ if (ctx->statustop)
+ top = ctx->statuslines;
+ else
+ top = 0;
+ for (j = 0; j < wp->sy; j++) {
+ if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy)
+ continue;
+ y = top + wp->yoff + j - ctx->oy;
+
+ if (wp->xoff >= ctx->ox &&
+ wp->xoff + wp->sx <= ctx->ox + ctx->sx) {
+ /* All visible. */
+ i = 0;
+ x = wp->xoff - ctx->ox;
+ width = wp->sx;
+ } else if (wp->xoff < ctx->ox &&
+ wp->xoff + wp->sx > ctx->ox + ctx->sx) {
+ /* Both left and right not visible. */
+ i = ctx->ox;
+ x = 0;
+ width = ctx->sx;
+ } else if (wp->xoff < ctx->ox) {
+ /* Left not visible. */
+ i = ctx->ox - wp->xoff;
+ x = 0;
+ width = wp->sx - i;
+ } else {
+ /* Right not visible. */
+ i = 0;
+ x = wp->xoff - ctx->ox;
+ width = ctx->sx - x;
+ }
+ log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u",
+ __func__, c->name, wp->id, i, j, x, y, width);
+
+ tty_default_colours(&defaults, wp);
+ tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette);
+ }
+}
diff --git a/screen-write.c b/screen-write.c
new file mode 100644
index 0000000..6b6a750
--- /dev/null
+++ b/screen-write.c
@@ -0,0 +1,2146 @@
+/* $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 screen_write_citem *screen_write_collect_trim(
+ struct screen_write_ctx *, u_int, u_int, u_int, int *);
+static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
+ 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 *);
+
+struct screen_write_citem {
+ u_int x;
+ int wrapped;
+
+ enum { TEXT, CLEAR } type;
+ u_int used;
+ u_int bg;
+
+ struct grid_cell gc;
+
+ TAILQ_ENTRY(screen_write_citem) entry;
+};
+struct screen_write_cline {
+ char *data;
+ TAILQ_HEAD(, screen_write_citem) items;
+};
+TAILQ_HEAD(, screen_write_citem) screen_write_citem_freelist =
+ TAILQ_HEAD_INITIALIZER(screen_write_citem_freelist);
+
+static struct screen_write_citem *
+screen_write_get_citem(void)
+{
+ struct screen_write_citem *ci;
+
+ ci = TAILQ_FIRST(&screen_write_citem_freelist);
+ if (ci != NULL) {
+ TAILQ_REMOVE(&screen_write_citem_freelist, ci, entry);
+ memset(ci, 0, sizeof *ci);
+ return (ci);
+ }
+ return (xcalloc(1, sizeof *ci));
+}
+
+static void
+screen_write_free_citem(struct screen_write_citem *ci)
+{
+ TAILQ_INSERT_TAIL(&screen_write_citem_freelist, ci, entry);
+}
+
+static void
+screen_write_offset_timer(__unused int fd, __unused short events, void *data)
+{
+ struct window *w = data;
+
+ tty_update_window_offset(w);
+}
+
+/* Set cursor position. */
+static void
+screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy)
+{
+ struct window_pane *wp = ctx->wp;
+ struct window *w;
+ struct screen *s = ctx->s;
+ struct timeval tv = { .tv_usec = 10000 };
+
+ if (cx != -1 && (u_int)cx == s->cx && cy != -1 && (u_int)cy == s->cy)
+ return;
+
+ if (cx != -1) {
+ if ((u_int)cx > screen_size_x(s)) /* allow last column */
+ cx = screen_size_x(s) - 1;
+ s->cx = cx;
+ }
+ if (cy != -1) {
+ if ((u_int)cy > screen_size_y(s) - 1)
+ cy = screen_size_y(s) - 1;
+ s->cy = cy;
+ }
+
+ if (wp == NULL)
+ return;
+ w = wp->window;
+
+ if (!event_initialized(&w->offset_timer))
+ evtimer_set(&w->offset_timer, screen_write_offset_timer, w);
+ if (!evtimer_pending(&w->offset_timer, NULL))
+ evtimer_add(&w->offset_timer, &tv);
+}
+
+/* Do a full redraw. */
+static void
+screen_write_redraw_cb(const struct tty_ctx *ttyctx)
+{
+ struct window_pane *wp = ttyctx->arg;
+
+ if (wp != NULL)
+ wp->flags |= PANE_REDRAW;
+}
+
+/* Update context for client. */
+static int
+screen_write_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);
+
+ if (wp->flags & (PANE_REDRAW|PANE_DROP))
+ return (-1);
+ if (c->flags & CLIENT_REDRAWPANES) {
+ /*
+ * Redraw is already deferred to redraw another pane - redraw
+ * this one also when that happens.
+ */
+ log_debug("%s: adding %%%u to deferred redraw", __func__,
+ wp->id);
+ wp->flags |= PANE_REDRAW;
+ return (-1);
+ }
+
+ ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy,
+ &ttyctx->wsx, &ttyctx->wsy);
+
+ ttyctx->xoff = ttyctx->rxoff = wp->xoff;
+ ttyctx->yoff = ttyctx->ryoff = wp->yoff;
+
+ if (status_at_line(c) == 0)
+ ttyctx->yoff += status_line_size(c);
+
+ return (1);
+}
+
+/* Set up context for TTY command. */
+static void
+screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx,
+ int sync)
+{
+ struct screen *s = ctx->s;
+
+ memset(ttyctx, 0, sizeof *ttyctx);
+
+ ttyctx->s = s;
+ ttyctx->sx = screen_size_x(s);
+ ttyctx->sy = screen_size_y(s);
+
+ ttyctx->ocx = s->cx;
+ ttyctx->ocy = s->cy;
+ ttyctx->orlower = s->rlower;
+ ttyctx->orupper = s->rupper;
+
+ memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults);
+ if (ctx->init_ctx_cb != NULL) {
+ ctx->init_ctx_cb(ctx, ttyctx);
+ if (ttyctx->palette != NULL) {
+ if (ttyctx->defaults.fg == 8)
+ ttyctx->defaults.fg = ttyctx->palette->fg;
+ if (ttyctx->defaults.bg == 8)
+ ttyctx->defaults.bg = ttyctx->palette->bg;
+ }
+ } else {
+ ttyctx->redraw_cb = screen_write_redraw_cb;
+ if (ctx->wp != NULL) {
+ tty_default_colours(&ttyctx->defaults, ctx->wp);
+ ttyctx->palette = &ctx->wp->palette;
+ ttyctx->set_client_cb = screen_write_set_client_cb;
+ ttyctx->arg = ctx->wp;
+ }
+ }
+
+ if (~ctx->flags & SCREEN_WRITE_SYNC) {
+ /*
+ * For the active pane or for an overlay (no pane), we want to
+ * only use synchronized updates if requested (commands that
+ * move the cursor); for other panes, always use it, since the
+ * cursor will have to move.
+ */
+ if (ctx->wp != NULL) {
+ if (ctx->wp != ctx->wp->window->active)
+ ttyctx->num = 1;
+ else
+ ttyctx->num = sync;
+ } else
+ ttyctx->num = 0x10|sync;
+ tty_write(tty_cmd_syncstart, ttyctx);
+ ctx->flags |= SCREEN_WRITE_SYNC;
+ }
+}
+
+/* Make write list. */
+void
+screen_write_make_list(struct screen *s)
+{
+ u_int y;
+
+ s->write_list = xcalloc(screen_size_y(s), sizeof *s->write_list);
+ for (y = 0; y < screen_size_y(s); y++)
+ TAILQ_INIT(&s->write_list[y].items);
+}
+
+/* Free write list. */
+void
+screen_write_free_list(struct screen *s)
+{
+ u_int y;
+
+ for (y = 0; y < screen_size_y(s); y++)
+ free(s->write_list[y].data);
+ free(s->write_list);
+}
+
+/* Set up for writing. */
+static void
+screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
+{
+ memset(ctx, 0, sizeof *ctx);
+
+ ctx->s = s;
+
+ if (ctx->s->write_list == NULL)
+ screen_write_make_list(ctx->s);
+ ctx->item = screen_write_get_citem();
+
+ ctx->scrolled = 0;
+ ctx->bg = 8;
+}
+
+/* Initialize writing with a pane. */
+void
+screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
+ struct screen *s)
+{
+ if (s == NULL)
+ s = wp->screen;
+ screen_write_init(ctx, s);
+ ctx->wp = wp;
+
+ if (log_get_level() != 0) {
+ log_debug("%s: size %ux%u, pane %%%u (at %u,%u)",
+ __func__, screen_size_x(ctx->s), screen_size_y(ctx->s),
+ wp->id, wp->xoff, wp->yoff);
+ }
+}
+
+/* Initialize writing with a callback. */
+void
+screen_write_start_callback(struct screen_write_ctx *ctx, struct screen *s,
+ screen_write_init_ctx_cb cb, void *arg)
+{
+ screen_write_init(ctx, s);
+
+ ctx->init_ctx_cb = cb;
+ ctx->arg = arg;
+
+ if (log_get_level() != 0) {
+ log_debug("%s: size %ux%u, with callback", __func__,
+ screen_size_x(ctx->s), screen_size_y(ctx->s));
+ }
+}
+
+/* Initialize writing. */
+void
+screen_write_start(struct screen_write_ctx *ctx, struct screen *s)
+{
+ screen_write_init(ctx, s);
+
+ if (log_get_level() != 0) {
+ log_debug("%s: size %ux%u, no pane", __func__,
+ screen_size_x(ctx->s), screen_size_y(ctx->s));
+ }
+}
+
+/* Finish writing. */
+void
+screen_write_stop(struct screen_write_ctx *ctx)
+{
+ screen_write_collect_end(ctx);
+ screen_write_collect_flush(ctx, 0, __func__);
+
+ screen_write_free_citem(ctx->item);
+}
+
+/* Reset screen state. */
+void
+screen_write_reset(struct screen_write_ctx *ctx)
+{
+ struct screen *s = ctx->s;
+
+ screen_reset_tabs(s);
+ screen_write_scrollregion(ctx, 0, screen_size_y(s) - 1);
+
+ s->mode = MODE_CURSOR | MODE_WRAP;
+
+ screen_write_clearscreen(ctx, 8);
+ screen_write_set_cursor(ctx, 0, 0);
+}
+
+/* Write character. */
+void
+screen_write_putc(struct screen_write_ctx *ctx, const struct grid_cell *gcp,
+ u_char ch)
+{
+ struct grid_cell gc;
+
+ memcpy(&gc, gcp, sizeof gc);
+
+ utf8_set(&gc.data, ch);
+ screen_write_cell(ctx, &gc);
+}
+
+/* Calculate string length. */
+size_t
+screen_write_strlen(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+ struct utf8_data ud;
+ u_char *ptr;
+ size_t left, size = 0;
+ enum utf8_state more;
+
+ va_start(ap, fmt);
+ xvasprintf(&msg, fmt, ap);
+ va_end(ap);
+
+ ptr = msg;
+ while (*ptr != '\0') {
+ if (*ptr > 0x7f && utf8_open(&ud, *ptr) == UTF8_MORE) {
+ ptr++;
+
+ left = strlen(ptr);
+ if (left < (size_t)ud.size - 1)
+ break;
+ while ((more = utf8_append(&ud, *ptr)) == UTF8_MORE)
+ ptr++;
+ ptr++;
+
+ if (more == UTF8_DONE)
+ size += ud.width;
+ } else {
+ if (*ptr > 0x1f && *ptr < 0x7f)
+ size++;
+ ptr++;
+ }
+ }
+
+ free(msg);
+ return (size);
+}
+
+/* Write string wrapped over lines. */
+int
+screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width,
+ u_int lines, int more, const struct grid_cell *gcp, const char *fmt, ...)
+{
+ struct screen *s = ctx->s;
+ va_list ap;
+ char *tmp;
+ u_int cy = s->cy, i, end, next, idx = 0, at, left;
+ struct utf8_data *text;
+ struct grid_cell gc;
+
+ memcpy(&gc, gcp, sizeof gc);
+
+ va_start(ap, fmt);
+ xvasprintf(&tmp, fmt, ap);
+ va_end(ap);
+
+ text = utf8_fromcstr(tmp);
+ free(tmp);
+
+ left = (cx + width) - s->cx;
+ for (;;) {
+ /* Find the end of what can fit on the line. */
+ at = 0;
+ for (end = idx; text[end].size != 0; end++) {
+ if (text[end].size == 1 && text[end].data[0] == '\n')
+ break;
+ if (at + text[end].width > left)
+ break;
+ at += text[end].width;
+ }
+
+ /*
+ * If we're on a space, that's the end. If not, walk back to
+ * try and find one.
+ */
+ if (text[end].size == 0)
+ next = end;
+ else if (text[end].size == 1 && text[end].data[0] == '\n')
+ next = end + 1;
+ else if (text[end].size == 1 && text[end].data[0] == ' ')
+ next = end + 1;
+ else {
+ for (i = end; i > idx; i--) {
+ if (text[i].size == 1 && text[i].data[0] == ' ')
+ break;
+ }
+ if (i != idx) {
+ next = i + 1;
+ end = i;
+ } else
+ next = end;
+ }
+
+ /* Print the line. */
+ for (i = idx; i < end; i++) {
+ utf8_copy(&gc.data, &text[i]);
+ screen_write_cell(ctx, &gc);
+ }
+
+ /* If at the bottom, stop. */
+ idx = next;
+ if (s->cy == cy + lines - 1 || text[idx].size == 0)
+ break;
+
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0);
+ left = width;
+ }
+
+ /*
+ * Fail if on the last line and there is more to come or at the end, or
+ * if the text was not entirely consumed.
+ */
+ if ((s->cy == cy + lines - 1 && (!more || s->cx == cx + width)) ||
+ text[idx].size != 0) {
+ free(text);
+ return (0);
+ }
+ free(text);
+
+ /*
+ * If no more to come, move to the next line. Otherwise, leave on
+ * the same line (except if at the end).
+ */
+ if (!more || s->cx == cx + width)
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0);
+ return (1);
+}
+
+/* Write simple string (no maximum length). */
+void
+screen_write_puts(struct screen_write_ctx *ctx, const struct grid_cell *gcp,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ screen_write_vnputs(ctx, -1, gcp, fmt, ap);
+ va_end(ap);
+}
+
+/* Write string with length limit (-1 for unlimited). */
+void
+screen_write_nputs(struct screen_write_ctx *ctx, ssize_t maxlen,
+ const struct grid_cell *gcp, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ screen_write_vnputs(ctx, maxlen, gcp, fmt, ap);
+ va_end(ap);
+}
+
+void
+screen_write_vnputs(struct screen_write_ctx *ctx, ssize_t maxlen,
+ const struct grid_cell *gcp, const char *fmt, va_list ap)
+{
+ struct grid_cell gc;
+ struct utf8_data *ud = &gc.data;
+ char *msg;
+ u_char *ptr;
+ size_t left, size = 0;
+ enum utf8_state more;
+
+ memcpy(&gc, gcp, sizeof gc);
+ xvasprintf(&msg, fmt, ap);
+
+ ptr = msg;
+ while (*ptr != '\0') {
+ if (*ptr > 0x7f && utf8_open(ud, *ptr) == UTF8_MORE) {
+ ptr++;
+
+ left = strlen(ptr);
+ if (left < (size_t)ud->size - 1)
+ break;
+ while ((more = utf8_append(ud, *ptr)) == UTF8_MORE)
+ ptr++;
+ ptr++;
+
+ if (more != UTF8_DONE)
+ continue;
+ if (maxlen > 0 && size + ud->width > (size_t)maxlen) {
+ while (size < (size_t)maxlen) {
+ screen_write_putc(ctx, &gc, ' ');
+ size++;
+ }
+ break;
+ }
+ size += ud->width;
+ screen_write_cell(ctx, &gc);
+ } else {
+ if (maxlen > 0 && size + 1 > (size_t)maxlen)
+ break;
+
+ if (*ptr == '\001')
+ gc.attr ^= GRID_ATTR_CHARSET;
+ else if (*ptr == '\n') {
+ screen_write_linefeed(ctx, 0, 8);
+ screen_write_carriagereturn(ctx);
+ } else if (*ptr > 0x1f && *ptr < 0x7f) {
+ size++;
+ screen_write_putc(ctx, &gc, *ptr);
+ }
+ ptr++;
+ }
+ }
+
+ free(msg);
+}
+
+/*
+ * Copy from another screen but without the selection stuff. Assumes the target
+ * region is already big enough.
+ */
+void
+screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src,
+ u_int px, u_int py, u_int nx, u_int ny)
+{
+ struct screen *s = ctx->s;
+ struct grid *gd = src->grid;
+ struct grid_cell gc;
+ u_int xx, yy, cx, cy;
+
+ if (nx == 0 || ny == 0)
+ return;
+
+ cy = s->cy;
+ for (yy = py; yy < py + ny; yy++) {
+ if (yy >= gd->hsize + gd->sy)
+ break;
+ cx = s->cx;
+ for (xx = px; xx < px + nx; xx++) {
+ if (xx >= grid_get_line(gd, yy)->cellsize)
+ break;
+ grid_get_cell(gd, xx, yy, &gc);
+ if (xx + gc.data.width > px + nx)
+ break;
+ grid_view_set_cell(ctx->s->grid, cx, cy, &gc);
+ cx++;
+ }
+ cy++;
+ }
+}
+
+/* Draw a horizontal line on screen. */
+void
+screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right)
+{
+ struct screen *s = ctx->s;
+ struct grid_cell gc;
+ u_int cx, cy, i;
+
+ cx = s->cx;
+ cy = s->cy;
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.attr |= GRID_ATTR_CHARSET;
+
+ screen_write_putc(ctx, &gc, left ? 't' : 'q');
+ for (i = 1; i < nx - 1; i++)
+ screen_write_putc(ctx, &gc, 'q');
+ screen_write_putc(ctx, &gc, right ? 'u' : 'q');
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* Draw a vertical line on screen. */
+void
+screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom)
+{
+ struct screen *s = ctx->s;
+ struct grid_cell gc;
+ u_int cx, cy, i;
+
+ cx = s->cx;
+ cy = s->cy;
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.attr |= GRID_ATTR_CHARSET;
+
+ screen_write_putc(ctx, &gc, top ? 'w' : 'x');
+ for (i = 1; i < ny - 1; i++) {
+ screen_write_set_cursor(ctx, cx, cy + i);
+ screen_write_putc(ctx, &gc, 'x');
+ }
+ screen_write_set_cursor(ctx, cx, cy + ny - 1);
+ screen_write_putc(ctx, &gc, bottom ? 'v' : 'x');
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* 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)
+{
+ struct screen *s = ctx->s;
+ struct grid_cell default_gc;
+ const struct grid_cell *gc = &default_gc;
+ u_int cx, cy, i, j;
+ const char *name;
+
+ cx = s->cx;
+ cy = s->cy;
+
+ memcpy(&default_gc, &grid_default_cell, sizeof default_gc);
+
+ screen_write_box(ctx, menu->width + 4, menu->count + 2,
+ BOX_LINES_DEFAULT, &default_gc, menu->title);
+
+ for (i = 0; i < menu->count; i++) {
+ name = menu->items[i].name;
+ 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_set_cursor(ctx, cx, cy);
+}
+
+static void
+screen_write_box_border_set(enum box_lines box_lines, int cell_type,
+ struct grid_cell *gc)
+{
+ switch (box_lines) {
+ case BOX_LINES_NONE:
+ break;
+ case BOX_LINES_DOUBLE:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_copy(&gc->data, tty_acs_double_borders(cell_type));
+ break;
+ case BOX_LINES_HEAVY:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
+ break;
+ case BOX_LINES_ROUNDED:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type));
+ break;
+ case BOX_LINES_SIMPLE:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
+ break;
+ case BOX_LINES_PADDED:
+ gc->attr &= ~GRID_ATTR_CHARSET;
+ utf8_set(&gc->data, PADDED_BORDERS[cell_type]);
+ break;
+ case BOX_LINES_SINGLE:
+ case BOX_LINES_DEFAULT:
+ gc->attr |= GRID_ATTR_CHARSET;
+ utf8_set(&gc->data, CELL_BORDERS[cell_type]);
+ break;
+ }
+}
+
+/* Draw a box on screen. */
+void
+screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny,
+ enum box_lines lines, const struct grid_cell *gcp, const char *title)
+{
+ struct screen *s = ctx->s;
+ struct grid_cell gc;
+ u_int cx, cy, i;
+
+ cx = s->cx;
+ cy = s->cy;
+
+ if (gcp != NULL)
+ memcpy(&gc, gcp, sizeof gc);
+ else
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+
+ gc.attr |= GRID_ATTR_CHARSET;
+ gc.flags |= GRID_FLAG_NOPALETTE;
+
+ /* Draw top border */
+ screen_write_box_border_set(lines, CELL_TOPLEFT, &gc);
+ screen_write_cell(ctx, &gc);
+ screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc);
+ for (i = 1; i < nx - 1; i++)
+ screen_write_cell(ctx, &gc);
+ screen_write_box_border_set(lines, CELL_TOPRIGHT, &gc);
+ screen_write_cell(ctx, &gc);
+
+ /* Draw bottom border */
+ screen_write_set_cursor(ctx, cx, cy + ny - 1);
+ screen_write_box_border_set(lines, CELL_BOTTOMLEFT, &gc);
+ screen_write_cell(ctx, &gc);
+ screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc);
+ for (i = 1; i < nx - 1; i++)
+ screen_write_cell(ctx, &gc);
+ screen_write_box_border_set(lines, CELL_BOTTOMRIGHT, &gc);
+ screen_write_cell(ctx, &gc);
+
+ /* Draw sides */
+ screen_write_box_border_set(lines, CELL_TOPBOTTOM, &gc);
+ for (i = 1; i < ny - 1; i++) {
+ /* left side */
+ screen_write_set_cursor(ctx, cx, cy + i);
+ screen_write_cell(ctx, &gc);
+ /* right side */
+ screen_write_set_cursor(ctx, cx + nx - 1, cy + i);
+ screen_write_cell(ctx, &gc);
+ }
+
+ if (title != NULL) {
+ gc.attr &= ~GRID_ATTR_CHARSET;
+ screen_write_cursormove(ctx, cx + 2, cy, 0);
+ format_draw(ctx, &gc, nx - 4, title, NULL, 0);
+ }
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/*
+ * Write a preview version of a window. Assumes target area is big enough and
+ * already cleared.
+ */
+void
+screen_write_preview(struct screen_write_ctx *ctx, struct screen *src, u_int nx,
+ u_int ny)
+{
+ struct screen *s = ctx->s;
+ struct grid_cell gc;
+ u_int cx, cy, px, py;
+
+ cx = s->cx;
+ cy = s->cy;
+
+ /*
+ * If the cursor is on, pick the area around the cursor, otherwise use
+ * the top left.
+ */
+ if (src->mode & MODE_CURSOR) {
+ px = src->cx;
+ if (px < nx / 3)
+ px = 0;
+ else
+ px = px - nx / 3;
+ if (px + nx > screen_size_x(src)) {
+ if (nx > screen_size_x(src))
+ px = 0;
+ else
+ px = screen_size_x(src) - nx;
+ }
+ py = src->cy;
+ if (py < ny / 3)
+ py = 0;
+ else
+ py = py - ny / 3;
+ if (py + ny > screen_size_y(src)) {
+ if (ny > screen_size_y(src))
+ py = 0;
+ else
+ py = screen_size_y(src) - ny;
+ }
+ } else {
+ px = 0;
+ py = 0;
+ }
+
+ screen_write_fast_copy(ctx, src, px, src->grid->hsize + py, nx, ny);
+
+ if (src->mode & MODE_CURSOR) {
+ grid_view_get_cell(src->grid, src->cx, src->cy, &gc);
+ gc.attr |= GRID_ATTR_REVERSE;
+ screen_write_set_cursor(ctx, cx + (src->cx - px),
+ cy + (src->cy - py));
+ screen_write_cell(ctx, &gc);
+ }
+}
+
+/* Set a mode. */
+void
+screen_write_mode_set(struct screen_write_ctx *ctx, int mode)
+{
+ struct screen *s = ctx->s;
+
+ s->mode |= mode;
+
+ if (log_get_level() != 0)
+ log_debug("%s: %s", __func__, screen_mode_to_string(mode));
+}
+
+/* Clear a mode. */
+void
+screen_write_mode_clear(struct screen_write_ctx *ctx, int mode)
+{
+ struct screen *s = ctx->s;
+
+ s->mode &= ~mode;
+
+ if (log_get_level() != 0)
+ log_debug("%s: %s", __func__, screen_mode_to_string(mode));
+}
+
+/* Cursor up by ny. */
+void
+screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny)
+{
+ struct screen *s = ctx->s;
+ u_int cx = s->cx, cy = s->cy;
+
+ if (ny == 0)
+ ny = 1;
+
+ if (cy < s->rupper) {
+ /* Above region. */
+ if (ny > cy)
+ ny = cy;
+ } else {
+ /* Below region. */
+ if (ny > cy - s->rupper)
+ ny = cy - s->rupper;
+ }
+ if (cx == screen_size_x(s))
+ cx--;
+
+ cy -= ny;
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* Cursor down by ny. */
+void
+screen_write_cursordown(struct screen_write_ctx *ctx, u_int ny)
+{
+ struct screen *s = ctx->s;
+ u_int cx = s->cx, cy = s->cy;
+
+ if (ny == 0)
+ ny = 1;
+
+ if (cy > s->rlower) {
+ /* Below region. */
+ if (ny > screen_size_y(s) - 1 - cy)
+ ny = screen_size_y(s) - 1 - cy;
+ } else {
+ /* Above region. */
+ if (ny > s->rlower - cy)
+ ny = s->rlower - cy;
+ }
+ if (cx == screen_size_x(s))
+ cx--;
+ else if (ny == 0)
+ return;
+
+ cy += ny;
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* Cursor right by nx. */
+void
+screen_write_cursorright(struct screen_write_ctx *ctx, u_int nx)
+{
+ struct screen *s = ctx->s;
+ u_int cx = s->cx, cy = s->cy;
+
+ if (nx == 0)
+ nx = 1;
+
+ if (nx > screen_size_x(s) - 1 - cx)
+ nx = screen_size_x(s) - 1 - cx;
+ if (nx == 0)
+ return;
+
+ cx += nx;
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* Cursor left by nx. */
+void
+screen_write_cursorleft(struct screen_write_ctx *ctx, u_int nx)
+{
+ struct screen *s = ctx->s;
+ u_int cx = s->cx, cy = s->cy;
+
+ if (nx == 0)
+ nx = 1;
+
+ if (nx > cx)
+ nx = cx;
+ if (nx == 0)
+ return;
+
+ cx -= nx;
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* Backspace; cursor left unless at start of wrapped line when can move up. */
+void
+screen_write_backspace(struct screen_write_ctx *ctx)
+{
+ struct screen *s = ctx->s;
+ struct grid_line *gl;
+ u_int cx = s->cx, cy = s->cy;
+
+ if (cx == 0) {
+ if (cy == 0)
+ return;
+ gl = grid_get_line(s->grid, s->grid->hsize + cy - 1);
+ if (gl->flags & GRID_LINE_WRAPPED) {
+ cy--;
+ cx = screen_size_x(s) - 1;
+ }
+ } else
+ cx--;
+
+ screen_write_set_cursor(ctx, cx, cy);
+}
+
+/* VT100 alignment test. */
+void
+screen_write_alignmenttest(struct screen_write_ctx *ctx)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+ struct grid_cell gc;
+ u_int xx, yy;
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ utf8_set(&gc.data, 'E');
+
+ 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);
+ }
+
+ screen_write_set_cursor(ctx, 0, 0);
+
+ s->rupper = 0;
+ s->rlower = screen_size_y(s) - 1;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+
+ screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1);
+ tty_write(tty_cmd_alignmenttest, &ttyctx);
+}
+
+/* Insert nx characters. */
+void
+screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+
+ if (nx == 0)
+ nx = 1;
+
+ if (nx > screen_size_x(s) - s->cx)
+ nx = screen_size_x(s) - s->cx;
+ if (nx == 0)
+ return;
+
+ if (s->cx > screen_size_x(s) - 1)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 0);
+ ttyctx.bg = bg;
+
+ grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = nx;
+ tty_write(tty_cmd_insertcharacter, &ttyctx);
+}
+
+/* Delete nx characters. */
+void
+screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+
+ if (nx == 0)
+ nx = 1;
+
+ if (nx > screen_size_x(s) - s->cx)
+ nx = screen_size_x(s) - s->cx;
+ if (nx == 0)
+ return;
+
+ if (s->cx > screen_size_x(s) - 1)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 0);
+ ttyctx.bg = bg;
+
+ grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = nx;
+ tty_write(tty_cmd_deletecharacter, &ttyctx);
+}
+
+/* Clear nx characters. */
+void
+screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+
+ if (nx == 0)
+ nx = 1;
+
+ if (nx > screen_size_x(s) - s->cx)
+ nx = screen_size_x(s) - s->cx;
+ if (nx == 0)
+ return;
+
+ if (s->cx > screen_size_x(s) - 1)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 0);
+ ttyctx.bg = bg;
+
+ grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = nx;
+ tty_write(tty_cmd_clearcharacter, &ttyctx);
+}
+
+/* Insert ny lines. */
+void
+screen_write_insertline(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;
+
+ if (ny == 0)
+ ny = 1;
+
+ 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 == 0)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ grid_view_insert_lines(gd, s->cy, ny, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = ny;
+ tty_write(tty_cmd_insertline, &ttyctx);
+ return;
+ }
+
+ if (ny > s->rlower + 1 - s->cy)
+ ny = s->rlower + 1 - s->cy;
+ if (ny == 0)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ if (s->cy < s->rupper || s->cy > s->rlower)
+ grid_view_insert_lines(gd, s->cy, ny, bg);
+ else
+ grid_view_insert_lines_region(gd, s->rlower, s->cy, ny, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+
+ ttyctx.num = ny;
+ tty_write(tty_cmd_insertline, &ttyctx);
+}
+
+/* Delete ny lines. */
+void
+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;
+
+ if (ny == 0)
+ ny = 1;
+
+ 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 == 0)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ grid_view_delete_lines(gd, s->cy, ny, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = ny;
+ tty_write(tty_cmd_deleteline, &ttyctx);
+ return;
+ }
+
+ if (ny > s->rlower + 1 - s->cy)
+ ny = s->rlower + 1 - s->cy;
+ if (ny == 0)
+ return;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ if (s->cy < s->rupper || s->cy > s->rlower)
+ grid_view_delete_lines(gd, s->cy, ny, bg);
+ else
+ grid_view_delete_lines_region(gd, s->rlower, s->cy, ny, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = ny;
+ tty_write(tty_cmd_deleteline, &ttyctx);
+}
+
+/* Clear line at cursor. */
+void
+screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct grid_line *gl;
+ u_int sx = screen_size_x(s);
+ struct screen_write_citem *ci = ctx->item;
+
+ gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
+ if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
+ return;
+
+ grid_view_clear(s->grid, 0, s->cy, sx, 1, bg);
+
+ screen_write_collect_clear(ctx, s->cy, 1);
+ ci->x = 0;
+ ci->used = sx;
+ ci->type = CLEAR;
+ ci->bg = bg;
+ TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry);
+ ctx->item = screen_write_get_citem();
+}
+
+/* Clear to end of line from cursor. */
+void
+screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct grid_line *gl;
+ u_int sx = screen_size_x(s);
+ struct screen_write_citem *ci = ctx->item, *before;
+
+ if (s->cx == 0) {
+ screen_write_clearline(ctx, bg);
+ return;
+ }
+
+ gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
+ if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
+ return;
+
+ 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);
+ ci->x = s->cx;
+ ci->used = sx - s->cx;
+ ci->type = CLEAR;
+ ci->bg = bg;
+ if (before == NULL)
+ TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry);
+ else
+ TAILQ_INSERT_BEFORE(before, ci, entry);
+ ctx->item = screen_write_get_citem();
+}
+
+/* Clear to start of line from cursor. */
+void
+screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ u_int sx = screen_size_x(s);
+ struct screen_write_citem *ci = ctx->item, *before;
+
+ if (s->cx >= sx - 1) {
+ screen_write_clearline(ctx, bg);
+ return;
+ }
+
+ if (s->cx > sx - 1)
+ grid_view_clear(s->grid, 0, s->cy, sx, 1, bg);
+ else
+ grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1, bg);
+
+ before = screen_write_collect_trim(ctx, s->cy, 0, s->cx + 1, NULL);
+ ci->x = 0;
+ ci->used = s->cx + 1;
+ ci->type = CLEAR;
+ ci->bg = bg;
+ if (before == NULL)
+ TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry);
+ else
+ TAILQ_INSERT_BEFORE(before, ci, entry);
+ ctx->item = screen_write_get_citem();
+}
+
+/* Move cursor to px,py. */
+void
+screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py,
+ int origin)
+{
+ struct screen *s = ctx->s;
+
+ if (origin && py != -1 && (s->mode & MODE_ORIGIN)) {
+ if ((u_int)py > s->rlower - s->rupper)
+ py = s->rlower;
+ else
+ py += s->rupper;
+ }
+
+ if (px != -1 && (u_int)px > screen_size_x(s) - 1)
+ px = screen_size_x(s) - 1;
+ if (py != -1 && (u_int)py > screen_size_y(s) - 1)
+ py = screen_size_y(s) - 1;
+
+ log_debug("%s: from %u,%u to %u,%u", __func__, s->cx, s->cy, px, py);
+ screen_write_set_cursor(ctx, px, py);
+}
+
+/* Reverse index (up with scroll). */
+void
+screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+
+ if (s->cy == s->rupper) {
+ grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
+ screen_write_collect_flush(ctx, 0, __func__);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ tty_write(tty_cmd_reverseindex, &ttyctx);
+ } else if (s->cy > 0)
+ screen_write_set_cursor(ctx, -1, s->cy - 1);
+
+}
+
+/* Set scroll region. */
+void
+screen_write_scrollregion(struct screen_write_ctx *ctx, u_int rupper,
+ u_int rlower)
+{
+ struct screen *s = ctx->s;
+
+ if (rupper > screen_size_y(s) - 1)
+ rupper = screen_size_y(s) - 1;
+ if (rlower > screen_size_y(s) - 1)
+ rlower = screen_size_y(s) - 1;
+ if (rupper >= rlower) /* cannot be one line */
+ return;
+
+ screen_write_collect_flush(ctx, 0, __func__);
+
+ /* Cursor moves to top-left. */
+ screen_write_set_cursor(ctx, 0, 0);
+
+ s->rupper = rupper;
+ s->rlower = rlower;
+}
+
+/* Line feed. */
+void
+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;
+
+ 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);
+
+ if (bg != ctx->bg) {
+ screen_write_collect_flush(ctx, 1, __func__);
+ ctx->bg = bg;
+ }
+
+ if (s->cy == s->rlower) {
+ grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
+ screen_write_collect_scroll(ctx, bg);
+ ctx->scrolled++;
+ } else if (s->cy < screen_size_y(s) - 1)
+ screen_write_set_cursor(ctx, -1, s->cy + 1);
+}
+
+/* Scroll up. */
+void
+screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct grid *gd = s->grid;
+ u_int i;
+
+ if (lines == 0)
+ lines = 1;
+ else if (lines > s->rlower - s->rupper + 1)
+ lines = s->rlower - s->rupper + 1;
+
+ if (bg != ctx->bg) {
+ screen_write_collect_flush(ctx, 1, __func__);
+ ctx->bg = bg;
+ }
+
+ for (i = 0; i < lines; i++) {
+ grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
+ screen_write_collect_scroll(ctx, bg);
+ }
+ ctx->scrolled += lines;
+}
+
+/* Scroll down. */
+void
+screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct grid *gd = s->grid;
+ struct tty_ctx ttyctx;
+ u_int i;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ if (lines == 0)
+ lines = 1;
+ else if (lines > s->rlower - s->rupper + 1)
+ lines = s->rlower - s->rupper + 1;
+
+ for (i = 0; i < lines; i++)
+ grid_view_scroll_region_down(gd, s->rupper, s->rlower, bg);
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = lines;
+ tty_write(tty_cmd_scrolldown, &ttyctx);
+}
+
+/* Carriage return (cursor to start of line). */
+void
+screen_write_carriagereturn(struct screen_write_ctx *ctx)
+{
+ screen_write_set_cursor(ctx, 0, -1);
+}
+
+/* Clear to end of screen from cursor. */
+void
+screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct grid *gd = s->grid;
+ struct tty_ctx ttyctx;
+ u_int sx = screen_size_x(s), sy = screen_size_y(s);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ /* Scroll into history if it is enabled and clearing entire screen. */
+ if (s->cx == 0 &&
+ s->cy == 0 &&
+ (gd->flags & GRID_HISTORY) &&
+ ctx->wp != NULL &&
+ options_get_number(ctx->wp->options, "scroll-on-clear"))
+ grid_view_clear_history(gd, bg);
+ else {
+ if (s->cx <= sx - 1)
+ grid_view_clear(gd, s->cx, s->cy, sx - s->cx, 1, bg);
+ grid_view_clear(gd, 0, s->cy + 1, sx, sy - (s->cy + 1), bg);
+ }
+
+ screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1));
+ screen_write_collect_flush(ctx, 0, __func__);
+ tty_write(tty_cmd_clearendofscreen, &ttyctx);
+}
+
+/* Clear to start of screen. */
+void
+screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+ u_int sx = screen_size_x(s);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ if (s->cy > 0)
+ grid_view_clear(s->grid, 0, 0, sx, s->cy, bg);
+ if (s->cx > sx - 1)
+ grid_view_clear(s->grid, 0, s->cy, sx, 1, bg);
+ else
+ grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1, bg);
+
+ screen_write_collect_clear(ctx, 0, s->cy);
+ screen_write_collect_flush(ctx, 0, __func__);
+ tty_write(tty_cmd_clearstartofscreen, &ttyctx);
+}
+
+/* Clear entire screen. */
+void
+screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct tty_ctx ttyctx;
+ u_int sx = screen_size_x(s), sy = screen_size_y(s);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = bg;
+
+ /* Scroll into history if it is enabled. */
+ if ((s->grid->flags & GRID_HISTORY) &&
+ ctx->wp != NULL &&
+ options_get_number(ctx->wp->options, "scroll-on-clear"))
+ grid_view_clear_history(s->grid, bg);
+ else
+ grid_view_clear(s->grid, 0, 0, sx, sy, bg);
+
+ screen_write_collect_clear(ctx, 0, sy);
+ tty_write(tty_cmd_clearscreen, &ttyctx);
+}
+
+/* Clear entire history. */
+void
+screen_write_clearhistory(struct screen_write_ctx *ctx)
+{
+ grid_clear_history(ctx->s->grid);
+}
+
+/* Force a full redraw. */
+void
+screen_write_fullredraw(struct screen_write_ctx *ctx)
+{
+ struct tty_ctx ttyctx;
+
+ screen_write_collect_flush(ctx, 0, __func__);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.redraw_cb(&ttyctx);
+}
+
+/* Trim collected items. */
+static struct screen_write_citem *
+screen_write_collect_trim(struct screen_write_ctx *ctx, u_int y, u_int x,
+ u_int used, int *wrapped)
+{
+ struct screen_write_cline *cl = &ctx->s->write_list[y];
+ struct screen_write_citem *ci, *ci2, *tmp, *before = NULL;
+ u_int sx = x, ex = x + used - 1;
+ u_int csx, cex;
+
+ if (TAILQ_EMPTY(&cl->items))
+ return (NULL);
+ TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) {
+ csx = ci->x;
+ cex = ci->x + ci->used - 1;
+
+ /* Item is entirely before. */
+ if (cex < sx) {
+ log_debug("%s: %p %u-%u before %u-%u", __func__, ci,
+ csx, cex, sx, ex);
+ continue;
+ }
+
+ /* Item is entirely after. */
+ if (csx > ex) {
+ log_debug("%s: %p %u-%u after %u-%u", __func__, ci,
+ csx, cex, sx, ex);
+ before = ci;
+ break;
+ }
+
+ /* Item is entirely inside. */
+ if (csx >= sx && cex <= ex) {
+ log_debug("%s: %p %u-%u inside %u-%u", __func__, ci,
+ csx, cex, sx, ex);
+ TAILQ_REMOVE(&cl->items, ci, entry);
+ screen_write_free_citem(ci);
+ if (csx == 0 && ci->wrapped && wrapped != NULL)
+ *wrapped = 1;
+ continue;
+ }
+
+ /* Item under the start. */
+ if (csx < sx && cex >= sx && cex <= ex) {
+ log_debug("%s: %p %u-%u start %u-%u", __func__, ci,
+ csx, cex, sx, ex);
+ ci->used = sx - csx;
+ log_debug("%s: %p now %u-%u", __func__, ci, ci->x,
+ ci->x + ci->used + 1);
+ continue;
+ }
+
+ /* Item covers the end. */
+ if (cex > ex && csx >= sx && csx <= ex) {
+ log_debug("%s: %p %u-%u end %u-%u", __func__, ci,
+ csx, cex, sx, ex);
+ ci->x = ex + 1;
+ ci->used = cex - ex;
+ log_debug("%s: %p now %u-%u", __func__, ci, ci->x,
+ ci->x + ci->used + 1);
+ before = ci;
+ break;
+ }
+
+ /* Item must cover both sides. */
+ log_debug("%s: %p %u-%u under %u-%u", __func__, ci,
+ csx, cex, sx, ex);
+ ci2 = screen_write_get_citem();
+ ci2->type = ci->type;
+ ci2->bg = ci->bg;
+ memcpy(&ci2->gc, &ci->gc, sizeof ci2->gc);
+ TAILQ_INSERT_AFTER(&cl->items, ci, ci2, entry);
+
+ ci->used = sx - csx;
+ ci2->x = ex + 1;
+ ci2->used = cex - ex;
+
+ log_debug("%s: %p now %u-%u (%p) and %u-%u (%p)", __func__, ci,
+ ci->x, ci->x + ci->used - 1, ci, ci2->x,
+ ci2->x + ci2->used - 1, ci2);
+ before = ci2;
+ break;
+ }
+ return (before);
+}
+
+/* Clear collected lines. */
+static void
+screen_write_collect_clear(struct screen_write_ctx *ctx, u_int y, u_int n)
+{
+ struct screen_write_cline *cl;
+ u_int i;
+
+ for (i = y; i < y + n; i++) {
+ cl = &ctx->s->write_list[i];
+ TAILQ_CONCAT(&screen_write_citem_freelist, &cl->items, entry);
+ }
+}
+
+/* Scroll collected lines up. */
+static void
+screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg)
+{
+ struct screen *s = ctx->s;
+ struct screen_write_cline *cl;
+ u_int y;
+ char *saved;
+ struct screen_write_citem *ci;
+
+ log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy,
+ s->rupper, s->rlower);
+
+ screen_write_collect_clear(ctx, s->rupper, 1);
+ saved = ctx->s->write_list[s->rupper].data;
+ for (y = s->rupper; y < s->rlower; y++) {
+ cl = &ctx->s->write_list[y + 1];
+ TAILQ_CONCAT(&ctx->s->write_list[y].items, &cl->items, entry);
+ ctx->s->write_list[y].data = cl->data;
+ }
+ ctx->s->write_list[s->rlower].data = saved;
+
+ ci = screen_write_get_citem();
+ ci->x = 0;
+ ci->used = screen_size_x(s);
+ ci->type = CLEAR;
+ ci->bg = bg;
+ TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry);
+}
+
+/* Flush collected lines. */
+static void
+screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only,
+ const char *from)
+{
+ struct screen *s = ctx->s;
+ struct screen_write_citem *ci, *tmp;
+ struct screen_write_cline *cl;
+ u_int y, cx, cy, last, items = 0;
+ struct tty_ctx ttyctx;
+
+ if (ctx->scrolled != 0) {
+ log_debug("%s: scrolled %u (region %u-%u)", __func__,
+ ctx->scrolled, s->rupper, s->rlower);
+ if (ctx->scrolled > s->rlower - s->rupper + 1)
+ ctx->scrolled = s->rlower - s->rupper + 1;
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.num = ctx->scrolled;
+ ttyctx.bg = ctx->bg;
+ tty_write(tty_cmd_scrollup, &ttyctx);
+ }
+ ctx->scrolled = 0;
+ ctx->bg = 8;
+
+ if (scroll_only)
+ return;
+
+ cx = s->cx; cy = s->cy;
+ for (y = 0; y < screen_size_y(s); y++) {
+ cl = &ctx->s->write_list[y];
+ last = UINT_MAX;
+ TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) {
+ if (last != UINT_MAX && ci->x <= last) {
+ fatalx("collect list not in order: %u <= %u",
+ ci->x, last);
+ }
+ screen_write_set_cursor(ctx, ci->x, y);
+ if (ci->type == CLEAR) {
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.bg = ci->bg;
+ ttyctx.num = ci->used;
+ tty_write(tty_cmd_clearcharacter, &ttyctx);
+ } else {
+ screen_write_initctx(ctx, &ttyctx, 0);
+ ttyctx.cell = &ci->gc;
+ ttyctx.wrapped = ci->wrapped;
+ ttyctx.ptr = cl->data + ci->x;
+ ttyctx.num = ci->used;
+ tty_write(tty_cmd_cells, &ttyctx);
+ }
+ items++;
+
+ TAILQ_REMOVE(&cl->items, ci, entry);
+ screen_write_free_citem(ci);
+ last = ci->x;
+ }
+ }
+ s->cx = cx; s->cy = cy;
+
+ log_debug("%s: flushed %u items (%s)", __func__, items, from);
+}
+
+/* Finish and store collected cells. */
+void
+screen_write_collect_end(struct screen_write_ctx *ctx)
+{
+ struct screen *s = ctx->s;
+ struct screen_write_citem *ci = ctx->item, *before;
+ struct screen_write_cline *cl = &s->write_list[s->cy];
+ struct grid_cell gc;
+ u_int xx;
+ int wrapped = ci->wrapped;
+
+ if (ci->used == 0)
+ return;
+
+ before = screen_write_collect_trim(ctx, s->cy, s->cx, ci->used,
+ &wrapped);
+ ci->x = s->cx;
+ ci->wrapped = wrapped;
+ if (before == NULL)
+ TAILQ_INSERT_TAIL(&cl->items, ci, entry);
+ else
+ TAILQ_INSERT_BEFORE(before, ci, entry);
+ ctx->item = screen_write_get_citem();
+
+ log_debug("%s: %u %.*s (at %u,%u)", __func__, ci->used,
+ (int)ci->used, cl->data + ci->x, s->cx, s->cy);
+
+ if (s->cx != 0) {
+ for (xx = s->cx; xx > 0; xx--) {
+ grid_view_get_cell(s->grid, xx, s->cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ break;
+ grid_view_set_cell(s->grid, xx, s->cy,
+ &grid_default_cell);
+ }
+ if (gc.data.width > 1) {
+ grid_view_set_cell(s->grid, xx, s->cy,
+ &grid_default_cell);
+ }
+ }
+
+ 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);
+
+ for (xx = s->cx; xx < screen_size_x(s); xx++) {
+ grid_view_get_cell(s->grid, xx, s->cy, &gc);
+ if (~gc.flags & GRID_FLAG_PADDING)
+ break;
+ grid_view_set_cell(s->grid, xx, s->cy, &grid_default_cell);
+ }
+}
+
+/* Write cell data, collecting if necessary. */
+void
+screen_write_collect_add(struct screen_write_ctx *ctx,
+ const struct grid_cell *gc)
+{
+ struct screen *s = ctx->s;
+ struct screen_write_citem *ci;
+ u_int sx = screen_size_x(s);
+ int collect;
+
+ /*
+ * Don't need to check that the attributes and whatnot are still the
+ * same - input_parse will end the collection when anything that isn't
+ * a plain character is encountered.
+ */
+
+ collect = 1;
+ if (gc->data.width != 1 || gc->data.size != 1 || *gc->data.data >= 0x7f)
+ collect = 0;
+ else if (gc->attr & GRID_ATTR_CHARSET)
+ collect = 0;
+ else if (~s->mode & MODE_WRAP)
+ collect = 0;
+ else if (s->mode & MODE_INSERT)
+ collect = 0;
+ else if (s->sel != NULL)
+ collect = 0;
+ if (!collect) {
+ screen_write_collect_end(ctx);
+ screen_write_collect_flush(ctx, 0, __func__);
+ screen_write_cell(ctx, gc);
+ return;
+ }
+
+ if (s->cx > sx - 1 || ctx->item->used > sx - 1 - s->cx)
+ screen_write_collect_end(ctx);
+ ci = ctx->item; /* may have changed */
+
+ if (s->cx > sx - 1) {
+ log_debug("%s: wrapped at %u,%u", __func__, s->cx, s->cy);
+ ci->wrapped = 1;
+ screen_write_linefeed(ctx, 1, 8);
+ screen_write_set_cursor(ctx, 0, -1);
+ }
+
+ if (ci->used == 0)
+ memcpy(&ci->gc, gc, sizeof ci->gc);
+ if (ctx->s->write_list[s->cy].data == NULL)
+ ctx->s->write_list[s->cy].data = xmalloc(screen_size_x(ctx->s));
+ ctx->s->write_list[s->cy].data[s->cx + ci->used++] = gc->data.data[0];
+}
+
+/* Write cell data. */
+void
+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;
+ 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;
+ 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__);
+
+ /* If this character doesn't fit, ignore it. */
+ if ((~s->mode & MODE_WRAP) &&
+ width > 1 &&
+ (width > sx || (s->cx != sx && s->cx > sx - width)))
+ return;
+
+ /* If in insert mode, make space for the cells. */
+ if (s->mode & MODE_INSERT) {
+ grid_view_insert_cells(s->grid, s->cx, s->cy, width, 8);
+ skip = 0;
+ }
+
+ /* Check this will fit on the current line and wrap if not. */
+ if ((s->mode & MODE_WRAP) && s->cx > sx - width) {
+ log_debug("%s: wrapped at %u,%u", __func__, s->cx, s->cy);
+ screen_write_linefeed(ctx, 1, 8);
+ screen_write_set_cursor(ctx, 0, -1);
+ screen_write_collect_flush(ctx, 1, __func__);
+ }
+
+ /* Sanity check cursor position. */
+ if (s->cx > sx - width || s->cy > sy - 1)
+ return;
+ screen_write_initctx(ctx, &ttyctx, 0);
+
+ /* Handle overwriting of UTF-8 characters. */
+ gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
+ if (gl->flags & GRID_LINE_EXTENDED) {
+ grid_view_get_cell(gd, s->cx, s->cy, &now_gc);
+ if (screen_write_overwrite(ctx, &now_gc, width))
+ skip = 0;
+ }
+
+ /*
+ * If the new character is UTF-8 wide, fill in padding cells. Have
+ * already ensured there is enough room.
+ */
+ for (xx = s->cx + 1; xx < s->cx + width; xx++) {
+ log_debug("%s: new padding at %u,%u", __func__, xx, s->cy);
+ grid_view_set_padding(gd, xx, s->cy);
+ skip = 0;
+ }
+
+ /* If no change, do not draw. */
+ if (skip) {
+ if (s->cx >= gl->cellsize)
+ skip = grid_cells_equal(gc, &grid_default_cell);
+ else {
+ gce = &gl->celldata[s->cx];
+ if (gce->flags & GRID_FLAG_EXTENDED)
+ skip = 0;
+ else if (gc->flags != gce->flags)
+ skip = 0;
+ else if (gc->attr != gce->data.attr)
+ skip = 0;
+ else if (gc->fg != gce->data.fg)
+ skip = 0;
+ else if (gc->bg != gce->data.bg)
+ skip = 0;
+ else if (gc->data.width != 1)
+ skip = 0;
+ else if (gc->data.size != 1)
+ skip = 0;
+ else if (gce->data.data != gc->data.data[0])
+ skip = 0;
+ }
+ }
+
+ /* Update the selected flag and set the cell. */
+ selected = screen_check_selection(s, s->cx, s->cy);
+ if (selected && (~gc->flags & GRID_FLAG_SELECTED)) {
+ memcpy(&tmp_gc, gc, sizeof tmp_gc);
+ tmp_gc.flags |= GRID_FLAG_SELECTED;
+ grid_view_set_cell(gd, s->cx, s->cy, &tmp_gc);
+ } else if (!selected && (gc->flags & GRID_FLAG_SELECTED)) {
+ memcpy(&tmp_gc, gc, sizeof tmp_gc);
+ tmp_gc.flags &= ~GRID_FLAG_SELECTED;
+ grid_view_set_cell(gd, s->cx, s->cy, &tmp_gc);
+ } else if (!skip)
+ grid_view_set_cell(gd, s->cx, s->cy, gc);
+ if (selected)
+ skip = 0;
+
+ /*
+ * 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)
+ screen_write_set_cursor(ctx, s->cx + width, -1);
+ else
+ screen_write_set_cursor(ctx, sx - last, -1);
+
+ /* Create space for character in insert mode. */
+ if (s->mode & MODE_INSERT) {
+ screen_write_collect_flush(ctx, 0, __func__);
+ ttyctx.num = width;
+ tty_write(tty_cmd_insertcharacter, &ttyctx);
+ }
+
+ /* Write to the screen. */
+ if (!skip) {
+ if (selected) {
+ screen_select_cell(s, &tmp_gc, gc);
+ ttyctx.cell = &tmp_gc;
+ } else
+ ttyctx.cell = gc;
+ tty_write(tty_cmd_cell, &ttyctx);
+ }
+}
+
+/* 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)
+{
+ 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);
+
+ /* Empty data is out. */
+ if (ud->size == 0)
+ fatalx("UTF-8 data empty");
+
+ /* 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;
+ }
+ 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);
+
+ 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);
+
+ /* Append the data. */
+ memcpy(gc.data.data + gc.data.size, ud->data, ud->size);
+ gc.data.size += ud->size;
+
+ /* Set the new cell. */
+ grid_view_set_cell(gd, *xx, s->cy, &gc);
+
+ return (&gc);
+}
+
+/*
+ * UTF-8 wide characters are a bit of an annoyance. They take up more than one
+ * cell on the screen, so following cells must not be drawn by marking them as
+ * padding.
+ *
+ * So far, so good. The problem is, when overwriting a padding cell, or a UTF-8
+ * character, it is necessary to also overwrite any other cells which covered
+ * by the same character.
+ */
+static int
+screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc,
+ u_int width)
+{
+ struct screen *s = ctx->s;
+ struct grid *gd = s->grid;
+ struct grid_cell tmp_gc;
+ u_int xx;
+ int done = 0;
+
+ if (gc->flags & GRID_FLAG_PADDING) {
+ /*
+ * A padding cell, so clear any following and leading padding
+ * cells back to the character. Don't overwrite the current
+ * cell as that happens later anyway.
+ */
+ xx = s->cx + 1;
+ while (--xx > 0) {
+ grid_view_get_cell(gd, xx, s->cy, &tmp_gc);
+ if (~tmp_gc.flags & GRID_FLAG_PADDING)
+ break;
+ log_debug("%s: padding at %u,%u", __func__, xx, s->cy);
+ grid_view_set_cell(gd, xx, s->cy, &grid_default_cell);
+ }
+
+ /* Overwrite the character at the start of this padding. */
+ log_debug("%s: character at %u,%u", __func__, xx, s->cy);
+ grid_view_set_cell(gd, xx, s->cy, &grid_default_cell);
+ done = 1;
+ }
+
+ /*
+ * Overwrite any padding cells that belong to any UTF-8 characters
+ * we'll be overwriting with the current character.
+ */
+ if (width != 1 ||
+ gc->data.width != 1 ||
+ gc->flags & GRID_FLAG_PADDING) {
+ xx = s->cx + width - 1;
+ while (++xx < screen_size_x(s)) {
+ grid_view_get_cell(gd, xx, s->cy, &tmp_gc);
+ if (~tmp_gc.flags & GRID_FLAG_PADDING)
+ break;
+ log_debug("%s: overwrite at %u,%u", __func__, xx,
+ s->cy);
+ grid_view_set_cell(gd, xx, s->cy, &grid_default_cell);
+ done = 1;
+ }
+ }
+
+ return (done);
+}
+
+/* Set external clipboard. */
+void
+screen_write_setselection(struct screen_write_ctx *ctx, u_char *str, u_int len)
+{
+ struct tty_ctx ttyctx;
+
+ screen_write_initctx(ctx, &ttyctx, 0);
+ ttyctx.ptr = str;
+ ttyctx.num = len;
+
+ tty_write(tty_cmd_setselection, &ttyctx);
+}
+
+/* Write unmodified string. */
+void
+screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len)
+{
+ struct tty_ctx ttyctx;
+
+ screen_write_initctx(ctx, &ttyctx, 0);
+ ttyctx.ptr = str;
+ ttyctx.num = len;
+
+ tty_write(tty_cmd_rawstring, &ttyctx);
+}
+
+/* Turn alternate screen on. */
+void
+screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,
+ int cursor)
+{
+ struct tty_ctx ttyctx;
+ struct window_pane *wp = ctx->wp;
+
+ if (wp != NULL && !options_get_number(wp->options, "alternate-screen"))
+ return;
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ screen_alternate_on(ctx->s, gc, cursor);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.redraw_cb(&ttyctx);
+}
+
+/* Turn alternate screen off. */
+void
+screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc,
+ int cursor)
+{
+ struct tty_ctx ttyctx;
+ struct window_pane *wp = ctx->wp;
+
+ if (wp != NULL && !options_get_number(wp->options, "alternate-screen"))
+ return;
+
+ screen_write_collect_flush(ctx, 0, __func__);
+ screen_alternate_off(ctx->s, gc, cursor);
+
+ screen_write_initctx(ctx, &ttyctx, 1);
+ ttyctx.redraw_cb(&ttyctx);
+}
diff --git a/screen.c b/screen.c
new file mode 100644
index 0000000..eceef64
--- /dev/null
+++ b/screen.c
@@ -0,0 +1,705 @@
+/* $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 <unistd.h>
+
+#include "tmux.h"
+
+/* Selected area in screen. */
+struct screen_sel {
+ int hidden;
+ int rectangle;
+ int modekeys;
+
+ u_int sx;
+ u_int sy;
+
+ u_int ex;
+ u_int ey;
+
+ struct grid_cell cell;
+};
+
+/* Entry on title stack. */
+struct screen_title_entry {
+ char *text;
+
+ TAILQ_ENTRY(screen_title_entry) entry;
+};
+TAILQ_HEAD(screen_titles, screen_title_entry);
+
+static void screen_resize_y(struct screen *, u_int, int, u_int *);
+static void screen_reflow(struct screen *, u_int, u_int *, u_int *, int);
+
+/* Free titles stack. */
+static void
+screen_free_titles(struct screen *s)
+{
+ struct screen_title_entry *title_entry;
+
+ if (s->titles == NULL)
+ return;
+
+ while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) {
+ TAILQ_REMOVE(s->titles, title_entry, entry);
+ free(title_entry->text);
+ free(title_entry);
+ }
+
+ free(s->titles);
+ s->titles = NULL;
+}
+
+/* Create a new screen. */
+void
+screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
+{
+ s->grid = grid_create(sx, sy, hlimit);
+ s->saved_grid = NULL;
+
+ s->title = xstrdup("");
+ s->titles = NULL;
+ s->path = NULL;
+
+ s->cstyle = SCREEN_CURSOR_DEFAULT;
+ s->default_cstyle = SCREEN_CURSOR_DEFAULT;
+ s->default_mode = 0;
+ s->ccolour = -1;
+ s->default_ccolour = -1;
+ s->tabs = NULL;
+ s->sel = NULL;
+
+ s->write_list = NULL;
+
+ screen_reinit(s);
+}
+
+/* Reinitialise screen. */
+void
+screen_reinit(struct screen *s)
+{
+ s->cx = 0;
+ s->cy = 0;
+
+ s->rupper = 0;
+ s->rlower = screen_size_y(s) - 1;
+
+ s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF);
+ if (options_get_number(global_options, "extended-keys") == 2)
+ s->mode |= MODE_KEXTENDED;
+
+ if (s->saved_grid != NULL)
+ screen_alternate_off(s, NULL, 0);
+ s->saved_cx = UINT_MAX;
+ s->saved_cy = UINT_MAX;
+
+ screen_reset_tabs(s);
+
+ grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8);
+
+ screen_clear_selection(s);
+ screen_free_titles(s);
+}
+
+/* Destroy a screen. */
+void
+screen_free(struct screen *s)
+{
+ free(s->sel);
+ free(s->tabs);
+ free(s->path);
+ free(s->title);
+
+ if (s->write_list != NULL)
+ screen_write_free_list(s);
+
+ if (s->saved_grid != NULL)
+ grid_destroy(s->saved_grid);
+ grid_destroy(s->grid);
+
+ screen_free_titles(s);
+}
+
+/* Reset tabs to default, eight spaces apart. */
+void
+screen_reset_tabs(struct screen *s)
+{
+ u_int i;
+
+ free(s->tabs);
+
+ if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL)
+ fatal("bit_alloc failed");
+ for (i = 8; i < screen_size_x(s); i += 8)
+ bit_set(s->tabs, i);
+}
+
+/* Set screen cursor style and mode. */
+void
+screen_set_cursor_style(u_int style, enum screen_cursor_style *cstyle,
+ int *mode)
+{
+ switch (style) {
+ case 0:
+ *cstyle = SCREEN_CURSOR_DEFAULT;
+ break;
+ case 1:
+ *cstyle = SCREEN_CURSOR_BLOCK;
+ *mode |= MODE_CURSOR_BLINKING;
+ break;
+ case 2:
+ *cstyle = SCREEN_CURSOR_BLOCK;
+ *mode &= ~MODE_CURSOR_BLINKING;
+ break;
+ case 3:
+ *cstyle = SCREEN_CURSOR_UNDERLINE;
+ *mode |= MODE_CURSOR_BLINKING;
+ break;
+ case 4:
+ *cstyle = SCREEN_CURSOR_UNDERLINE;
+ *mode &= ~MODE_CURSOR_BLINKING;
+ break;
+ case 5:
+ *cstyle = SCREEN_CURSOR_BAR;
+ *mode |= MODE_CURSOR_BLINKING;
+ break;
+ case 6:
+ *cstyle = SCREEN_CURSOR_BAR;
+ *mode &= ~MODE_CURSOR_BLINKING;
+ break;
+ }
+}
+
+/* Set screen cursor colour. */
+void
+screen_set_cursor_colour(struct screen *s, int colour)
+{
+ s->ccolour = colour;
+}
+
+/* Set screen title. */
+int
+screen_set_title(struct screen *s, const char *title)
+{
+ if (!utf8_isvalid(title))
+ return (0);
+ free(s->title);
+ s->title = xstrdup(title);
+ return (1);
+}
+
+/* Set screen path. */
+void
+screen_set_path(struct screen *s, const char *path)
+{
+ free(s->path);
+ utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
+}
+
+/* Push the current title onto the stack. */
+void
+screen_push_title(struct screen *s)
+{
+ struct screen_title_entry *title_entry;
+
+ if (s->titles == NULL) {
+ s->titles = xmalloc(sizeof *s->titles);
+ TAILQ_INIT(s->titles);
+ }
+ title_entry = xmalloc(sizeof *title_entry);
+ title_entry->text = xstrdup(s->title);
+ TAILQ_INSERT_HEAD(s->titles, title_entry, entry);
+}
+
+/*
+ * Pop a title from the stack and set it as the screen title. If the stack is
+ * empty, do nothing.
+ */
+void
+screen_pop_title(struct screen *s)
+{
+ struct screen_title_entry *title_entry;
+
+ if (s->titles == NULL)
+ return;
+
+ title_entry = TAILQ_FIRST(s->titles);
+ if (title_entry != NULL) {
+ screen_set_title(s, title_entry->text);
+
+ TAILQ_REMOVE(s->titles, title_entry, entry);
+ free(title_entry->text);
+ free(title_entry);
+ }
+}
+
+/* Resize screen with options. */
+void
+screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
+ int eat_empty, int cursor)
+{
+ u_int cx = s->cx, cy = s->grid->hsize + s->cy;
+
+ if (s->write_list != NULL)
+ screen_write_free_list(s);
+
+ log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)",
+ __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy,
+ cx, cy);
+
+ if (sx < 1)
+ sx = 1;
+ if (sy < 1)
+ sy = 1;
+
+ if (sx != screen_size_x(s)) {
+ s->grid->sx = sx;
+ screen_reset_tabs(s);
+ } else
+ reflow = 0;
+
+ if (sy != screen_size_y(s))
+ screen_resize_y(s, sy, eat_empty, &cy);
+
+ if (reflow)
+ screen_reflow(s, sx, &cx, &cy, cursor);
+
+ if (cy >= s->grid->hsize) {
+ s->cx = cx;
+ s->cy = cy - s->grid->hsize;
+ } else {
+ s->cx = 0;
+ s->cy = 0;
+ }
+
+ log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx,
+ s->cy, cx, cy);
+
+ if (s->write_list != NULL)
+ screen_write_make_list(s);
+}
+
+/* Resize screen. */
+void
+screen_resize(struct screen *s, u_int sx, u_int sy, int reflow)
+{
+ screen_resize_cursor(s, sx, sy, reflow, 1, 1);
+}
+
+static void
+screen_resize_y(struct screen *s, u_int sy, int eat_empty, u_int *cy)
+{
+ struct grid *gd = s->grid;
+ u_int needed, available, oldy, i;
+
+ if (sy == 0)
+ fatalx("zero size");
+ oldy = screen_size_y(s);
+
+ /*
+ * When resizing:
+ *
+ * If the height is decreasing, delete lines from the bottom until
+ * hitting the cursor, then push lines from the top into the history.
+ *
+ * When increasing, pull as many lines as possible from scrolled
+ * history (not explicitly cleared from view) to the top, then fill the
+ * remaining with blanks at the bottom.
+ */
+
+ /* Size decreasing. */
+ if (sy < oldy) {
+ needed = oldy - sy;
+
+ /* Delete as many lines as possible from the bottom. */
+ if (eat_empty) {
+ available = oldy - 1 - s->cy;
+ if (available > 0) {
+ if (available > needed)
+ available = needed;
+ grid_view_delete_lines(gd, oldy - available,
+ available, 8);
+ }
+ needed -= available;
+ }
+
+ /*
+ * Now just increase the history size, if possible, to take
+ * over the lines which are left. If history is off, delete
+ * lines from the top.
+ */
+ available = s->cy;
+ if (gd->flags & GRID_HISTORY) {
+ gd->hscrolled += needed;
+ gd->hsize += needed;
+ } else if (needed > 0 && available > 0) {
+ if (available > needed)
+ available = needed;
+ grid_view_delete_lines(gd, 0, available, 8);
+ (*cy) -= available;
+ }
+ }
+
+ /* Resize line array. */
+ grid_adjust_lines(gd, gd->hsize + sy);
+
+ /* Size increasing. */
+ if (sy > oldy) {
+ needed = sy - oldy;
+
+ /*
+ * Try to pull as much as possible out of scrolled history, if
+ * is is enabled.
+ */
+ available = gd->hscrolled;
+ if (gd->flags & GRID_HISTORY && available > 0) {
+ if (available > needed)
+ available = needed;
+ gd->hscrolled -= available;
+ gd->hsize -= available;
+ } else
+ available = 0;
+ needed -= available;
+
+ /* Then fill the rest in with blanks. */
+ for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++)
+ grid_empty_line(gd, i, 8);
+ }
+
+ /* Set the new size, and reset the scroll region. */
+ gd->sy = sy;
+ s->rupper = 0;
+ s->rlower = screen_size_y(s) - 1;
+}
+
+/* Set selection. */
+void
+screen_set_selection(struct screen *s, u_int sx, u_int sy,
+ u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc)
+{
+ if (s->sel == NULL)
+ s->sel = xcalloc(1, sizeof *s->sel);
+
+ memcpy(&s->sel->cell, gc, sizeof s->sel->cell);
+ s->sel->hidden = 0;
+ s->sel->rectangle = rectangle;
+ s->sel->modekeys = modekeys;
+
+ s->sel->sx = sx;
+ s->sel->sy = sy;
+ s->sel->ex = ex;
+ s->sel->ey = ey;
+}
+
+/* Clear selection. */
+void
+screen_clear_selection(struct screen *s)
+{
+ free(s->sel);
+ s->sel = NULL;
+}
+
+/* Hide selection. */
+void
+screen_hide_selection(struct screen *s)
+{
+ if (s->sel != NULL)
+ s->sel->hidden = 1;
+}
+
+/* Check if cell in selection. */
+int
+screen_check_selection(struct screen *s, u_int px, u_int py)
+{
+ struct screen_sel *sel = s->sel;
+ u_int xx;
+
+ if (sel == NULL || sel->hidden)
+ return (0);
+
+ if (sel->rectangle) {
+ if (sel->sy < sel->ey) {
+ /* start line < end line -- downward selection. */
+ if (py < sel->sy || py > sel->ey)
+ return (0);
+ } else if (sel->sy > sel->ey) {
+ /* start line > end line -- upward selection. */
+ if (py > sel->sy || py < sel->ey)
+ return (0);
+ } else {
+ /* starting line == ending line. */
+ if (py != sel->sy)
+ return (0);
+ }
+
+ /*
+ * Need to include the selection start row, but not the cursor
+ * row, which means the selection changes depending on which
+ * one is on the left.
+ */
+ if (sel->ex < sel->sx) {
+ /* Cursor (ex) is on the left. */
+ if (px < sel->ex)
+ return (0);
+
+ if (px > sel->sx)
+ return (0);
+ } else {
+ /* Selection start (sx) is on the left. */
+ if (px < sel->sx)
+ return (0);
+
+ if (px > sel->ex)
+ return (0);
+ }
+ } else {
+ /*
+ * Like emacs, keep the top-left-most character, and drop the
+ * bottom-right-most, regardless of copy direction.
+ */
+ if (sel->sy < sel->ey) {
+ /* starting line < ending line -- downward selection. */
+ if (py < sel->sy || py > sel->ey)
+ return (0);
+
+ if (py == sel->sy && px < sel->sx)
+ return (0);
+
+ if (sel->modekeys == MODEKEY_EMACS)
+ xx = (sel->ex == 0 ? 0 : sel->ex - 1);
+ else
+ xx = sel->ex;
+ if (py == sel->ey && px > xx)
+ return (0);
+ } else if (sel->sy > sel->ey) {
+ /* starting line > ending line -- upward selection. */
+ if (py > sel->sy || py < sel->ey)
+ return (0);
+
+ if (py == sel->ey && px < sel->ex)
+ return (0);
+
+ if (sel->modekeys == MODEKEY_EMACS)
+ xx = sel->sx - 1;
+ else
+ xx = sel->sx;
+ if (py == sel->sy && (sel->sx == 0 || px > xx))
+ return (0);
+ } else {
+ /* starting line == ending line. */
+ if (py != sel->sy)
+ return (0);
+
+ if (sel->ex < sel->sx) {
+ /* cursor (ex) is on the left */
+ if (sel->modekeys == MODEKEY_EMACS)
+ xx = sel->sx - 1;
+ else
+ xx = sel->sx;
+ if (px > xx || px < sel->ex)
+ return (0);
+ } else {
+ /* selection start (sx) is on the left */
+ if (sel->modekeys == MODEKEY_EMACS)
+ xx = (sel->ex == 0 ? 0 : sel->ex - 1);
+ else
+ xx = sel->ex;
+ if (px < sel->sx || px > xx)
+ return (0);
+ }
+ }
+ }
+
+ return (1);
+}
+
+/* Get selected grid cell. */
+void
+screen_select_cell(struct screen *s, struct grid_cell *dst,
+ const struct grid_cell *src)
+{
+ if (s->sel == NULL || s->sel->hidden)
+ return;
+
+ memcpy(dst, &s->sel->cell, sizeof *dst);
+
+ utf8_copy(&dst->data, &src->data);
+ dst->attr = dst->attr & ~GRID_ATTR_CHARSET;
+ dst->attr |= src->attr & GRID_ATTR_CHARSET;
+ dst->flags = src->flags;
+}
+
+/* Reflow wrapped lines. */
+static void
+screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor)
+{
+ u_int wx, wy;
+
+ if (cursor) {
+ grid_wrap_position(s->grid, *cx, *cy, &wx, &wy);
+ log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx,
+ wy);
+ }
+
+ grid_reflow(s->grid, new_x);
+
+ if (cursor) {
+ grid_unwrap_position(s->grid, cx, cy, wx, wy);
+ log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy);
+ }
+ else {
+ *cx = 0;
+ *cy = s->grid->hsize;
+ }
+}
+
+/*
+ * Enter alternative screen mode. A copy of the visible screen is saved and the
+ * history is not updated.
+ */
+void
+screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
+{
+ u_int sx, sy;
+
+ if (s->saved_grid != NULL)
+ return;
+ sx = screen_size_x(s);
+ sy = screen_size_y(s);
+
+ s->saved_grid = grid_create(sx, sy, 0);
+ grid_duplicate_lines(s->saved_grid, 0, s->grid, screen_hsize(s), sy);
+ if (cursor) {
+ s->saved_cx = s->cx;
+ s->saved_cy = s->cy;
+ }
+ memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
+
+ grid_view_clear(s->grid, 0, 0, sx, sy, 8);
+
+ s->saved_flags = s->grid->flags;
+ s->grid->flags &= ~GRID_HISTORY;
+}
+
+/* Exit alternate screen mode and restore the copied grid. */
+void
+screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
+{
+ u_int sx = screen_size_x(s), sy = screen_size_y(s);
+
+ /*
+ * If the current size is different, temporarily resize to the old size
+ * before copying back.
+ */
+ if (s->saved_grid != NULL)
+ screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 1);
+
+ /*
+ * Restore the cursor position and cell. This happens even if not
+ * currently in the alternate screen.
+ */
+ if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) {
+ s->cx = s->saved_cx;
+ s->cy = s->saved_cy;
+ if (gc != NULL)
+ memcpy(gc, &s->saved_cell, sizeof *gc);
+ }
+
+ /* If not in the alternate screen, do nothing more. */
+ if (s->saved_grid == NULL) {
+ if (s->cx > screen_size_x(s) - 1)
+ s->cx = screen_size_x(s) - 1;
+ if (s->cy > screen_size_y(s) - 1)
+ s->cy = screen_size_y(s) - 1;
+ return;
+ }
+
+ /* Restore the saved grid. */
+ grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0,
+ s->saved_grid->sy);
+
+ /*
+ * Turn history back on (so resize can use it) and then resize back to
+ * the current size.
+ */
+ if (s->saved_flags & GRID_HISTORY)
+ s->grid->flags |= GRID_HISTORY;
+ screen_resize(s, sx, sy, 1);
+
+ grid_destroy(s->saved_grid);
+ s->saved_grid = NULL;
+
+ if (s->cx > screen_size_x(s) - 1)
+ s->cx = screen_size_x(s) - 1;
+ if (s->cy > screen_size_y(s) - 1)
+ s->cy = screen_size_y(s) - 1;
+}
+
+/* Get mode as a string. */
+const char *
+screen_mode_to_string(int mode)
+{
+ static char tmp[1024];
+
+ if (mode == 0)
+ return ("NONE");
+ if (mode == ALL_MODES)
+ return ("ALL");
+
+ *tmp = '\0';
+ if (mode & MODE_CURSOR)
+ strlcat(tmp, "CURSOR,", sizeof tmp);
+ if (mode & MODE_INSERT)
+ strlcat(tmp, "INSERT,", sizeof tmp);
+ if (mode & MODE_KCURSOR)
+ strlcat(tmp, "KCURSOR,", sizeof tmp);
+ if (mode & MODE_KKEYPAD)
+ strlcat(tmp, "KKEYPAD,", sizeof tmp);
+ if (mode & MODE_WRAP)
+ strlcat(tmp, "WRAP,", sizeof tmp);
+ if (mode & MODE_MOUSE_STANDARD)
+ strlcat(tmp, "MOUSE_STANDARD,", sizeof tmp);
+ if (mode & MODE_MOUSE_BUTTON)
+ strlcat(tmp, "MOUSE_BUTTON,", sizeof tmp);
+ if (mode & MODE_CURSOR_BLINKING)
+ strlcat(tmp, "CURSOR_BLINKING,", sizeof tmp);
+ if (mode & MODE_CURSOR_VERY_VISIBLE)
+ strlcat(tmp, "CURSOR_VERY_VISIBLE,", sizeof tmp);
+ if (mode & MODE_MOUSE_UTF8)
+ strlcat(tmp, "UTF8,", sizeof tmp);
+ if (mode & MODE_MOUSE_SGR)
+ strlcat(tmp, "SGR,", sizeof tmp);
+ if (mode & MODE_BRACKETPASTE)
+ strlcat(tmp, "BRACKETPASTE,", sizeof tmp);
+ if (mode & MODE_FOCUSON)
+ strlcat(tmp, "FOCUSON,", sizeof tmp);
+ if (mode & MODE_MOUSE_ALL)
+ strlcat(tmp, "MOUSE_ALL,", sizeof tmp);
+ if (mode & MODE_ORIGIN)
+ strlcat(tmp, "ORIGIN,", sizeof tmp);
+ if (mode & MODE_CRLF)
+ strlcat(tmp, "CRLF,", sizeof tmp);
+ if (mode & MODE_KEXTENDED)
+ strlcat(tmp, "KEXTENDED,", sizeof tmp);
+ tmp[strlen(tmp) - 1] = '\0';
+ return (tmp);
+}
diff --git a/server-acl.c b/server-acl.c
new file mode 100644
index 0000000..26f2490
--- /dev/null
+++ b/server-acl.c
@@ -0,0 +1,186 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2021 Holland Schutte, Jayson Morberg
+ * Copyright (c) 2021 Dallas Lyons <dallasdlyons@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+struct server_acl_user {
+ uid_t uid;
+
+ int flags;
+#define SERVER_ACL_READONLY 0x1
+
+ RB_ENTRY(server_acl_user) entry;
+};
+
+static int
+server_acl_cmp(struct server_acl_user *user1, struct server_acl_user *user2)
+{
+ if (user1->uid < user2->uid)
+ return (-1);
+ return (user1->uid > user2->uid);
+}
+
+RB_HEAD(server_acl_entries, server_acl_user) server_acl_entries;
+RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp);
+
+/* Initialize server_acl tree. */
+void
+server_acl_init(void)
+{
+ RB_INIT(&server_acl_entries);
+
+ if (getuid() != 0)
+ server_acl_user_allow(0);
+ server_acl_user_allow(getuid());
+}
+
+/* Find user entry. */
+struct server_acl_user*
+server_acl_user_find(uid_t uid)
+{
+ struct server_acl_user find = { .uid = uid };
+
+ return (RB_FIND(server_acl_entries, &server_acl_entries, &find));
+}
+
+/* Display the tree. */
+void
+server_acl_display(struct cmdq_item *item)
+{
+ struct server_acl_user *loop;
+ struct passwd *pw;
+ const char *name;
+
+ RB_FOREACH(loop, server_acl_entries, &server_acl_entries) {
+ if (loop->uid == 0)
+ continue;
+ if ((pw = getpwuid(loop->uid)) != NULL)
+ name = pw->pw_name;
+ else
+ name = "unknown";
+ if (loop->flags == SERVER_ACL_READONLY)
+ cmdq_print(item, "%s (R)", name);
+ else
+ cmdq_print(item, "%s (W)", name);
+ }
+}
+
+/* Allow a user. */
+void
+server_acl_user_allow(uid_t uid)
+{
+ struct server_acl_user *user;
+
+ user = server_acl_user_find(uid);
+ if (user == NULL) {
+ user = xcalloc(1, sizeof *user);
+ user->uid = uid;
+ RB_INSERT(server_acl_entries, &server_acl_entries, user);
+ }
+}
+
+/* Deny a user (remove from the tree). */
+void
+server_acl_user_deny(uid_t uid)
+{
+ struct server_acl_user *user;
+
+ user = server_acl_user_find(uid);
+ if (user != NULL) {
+ RB_REMOVE(server_acl_entries, &server_acl_entries, user);
+ free(user);
+ }
+}
+
+/* Allow this user write access. */
+void
+server_acl_user_allow_write(uid_t uid)
+{
+ struct server_acl_user *user;
+ struct client *c;
+
+ user = server_acl_user_find(uid);
+ if (user == NULL)
+ return;
+ user->flags &= ~SERVER_ACL_READONLY;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ uid = proc_get_peer_uid(c->peer);
+ if (uid != (uid_t)-1 && uid == user->uid)
+ c->flags &= ~CLIENT_READONLY;
+ }
+}
+
+/* Deny this user write access. */
+void
+server_acl_user_deny_write(uid_t uid)
+{
+ struct server_acl_user *user;
+ struct client *c;
+
+ user = server_acl_user_find(uid);
+ if (user == NULL)
+ return;
+ user->flags |= SERVER_ACL_READONLY;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ uid = proc_get_peer_uid(c->peer);
+ if (uid != (uid_t)-1 && uid == user->uid)
+ c->flags |= CLIENT_READONLY;
+ }
+}
+
+/*
+ * Check if the client's UID exists in the ACL list and if so, set as read only
+ * if needed. Return false if the user does not exist.
+ */
+int
+server_acl_join(struct client *c)
+{
+ struct server_acl_user *user;
+ uid_t uid;
+
+ uid = proc_get_peer_uid(c->peer);
+ if (uid == (uid_t)-1)
+ return (0);
+
+ user = server_acl_user_find(uid);
+ if (user == NULL)
+ return (0);
+ if (user->flags & SERVER_ACL_READONLY)
+ c->flags |= CLIENT_READONLY;
+ return (1);
+}
+
+/* Get UID for user entry. */
+uid_t
+server_acl_get_uid(struct server_acl_user *user)
+{
+ return (user->uid);
+}
diff --git a/server-client.c b/server-client.c
new file mode 100644
index 0000000..8144bcd
--- /dev/null
+++ b/server-client.c
@@ -0,0 +1,3212 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static void server_client_free(int, short, void *);
+static void server_client_check_pane_resize(struct window_pane *);
+static void server_client_check_pane_buffer(struct window_pane *);
+static void server_client_check_window_resize(struct window *);
+static key_code server_client_check_mouse(struct client *, struct key_event *);
+static void server_client_repeat_timer(int, short, void *);
+static void server_client_click_timer(int, short, void *);
+static void server_client_check_exit(struct client *);
+static void server_client_check_redraw(struct client *);
+static void server_client_check_modes(struct client *);
+static void server_client_set_title(struct client *);
+static void server_client_set_path(struct client *);
+static void server_client_reset_state(struct client *);
+static int server_client_assume_paste(struct session *);
+static void server_client_update_latest(struct client *);
+
+static void server_client_dispatch(struct imsg *, void *);
+static void server_client_dispatch_command(struct client *, struct imsg *);
+static void server_client_dispatch_identify(struct client *, struct imsg *);
+static void server_client_dispatch_shell(struct client *);
+
+/* Compare client windows. */
+static int
+server_client_window_cmp(struct client_window *cw1,
+ struct client_window *cw2)
+{
+ if (cw1->window < cw2->window)
+ return (-1);
+ if (cw1->window > cw2->window)
+ return (1);
+ return (0);
+}
+RB_GENERATE(client_windows, client_window, entry, server_client_window_cmp);
+
+/* Number of attached clients. */
+u_int
+server_client_how_many(void)
+{
+ struct client *c;
+ u_int n;
+
+ n = 0;
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL && (~c->flags & CLIENT_UNATTACHEDFLAGS))
+ n++;
+ }
+ return (n);
+}
+
+/* Overlay timer callback. */
+static void
+server_client_overlay_timer(__unused int fd, __unused short events, void *data)
+{
+ server_client_clear_overlay(data);
+}
+
+/* Set an overlay on client. */
+void
+server_client_set_overlay(struct client *c, u_int delay,
+ overlay_check_cb checkcb, overlay_mode_cb modecb,
+ overlay_draw_cb drawcb, overlay_key_cb keycb, overlay_free_cb freecb,
+ overlay_resize_cb resizecb, void *data)
+{
+ struct timeval tv;
+
+ if (c->overlay_draw != NULL)
+ server_client_clear_overlay(c);
+
+ tv.tv_sec = delay / 1000;
+ tv.tv_usec = (delay % 1000) * 1000L;
+
+ if (event_initialized(&c->overlay_timer))
+ evtimer_del(&c->overlay_timer);
+ evtimer_set(&c->overlay_timer, server_client_overlay_timer, c);
+ if (delay != 0)
+ evtimer_add(&c->overlay_timer, &tv);
+
+ c->overlay_check = checkcb;
+ c->overlay_mode = modecb;
+ c->overlay_draw = drawcb;
+ c->overlay_key = keycb;
+ c->overlay_free = freecb;
+ c->overlay_resize = resizecb;
+ c->overlay_data = data;
+
+ if (c->overlay_check == NULL)
+ c->tty.flags |= TTY_FREEZE;
+ if (c->overlay_mode == NULL)
+ c->tty.flags |= TTY_NOCURSOR;
+ server_redraw_client(c);
+}
+
+/* Clear overlay mode on client. */
+void
+server_client_clear_overlay(struct client *c)
+{
+ if (c->overlay_draw == NULL)
+ return;
+
+ if (event_initialized(&c->overlay_timer))
+ evtimer_del(&c->overlay_timer);
+
+ if (c->overlay_free != NULL)
+ c->overlay_free(c, c->overlay_data);
+
+ c->overlay_check = NULL;
+ c->overlay_mode = NULL;
+ c->overlay_draw = NULL;
+ c->overlay_key = NULL;
+ c->overlay_free = NULL;
+ c->overlay_data = NULL;
+
+ c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
+ server_redraw_client(c);
+}
+
+/*
+ * Given overlay position and dimensions, return parts of the input range which
+ * are visible.
+ */
+void
+server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px,
+ u_int py, u_int nx, struct overlay_ranges *r)
+{
+ u_int ox, onx;
+
+ /* Return up to 2 ranges. */
+ r->px[2] = 0;
+ r->nx[2] = 0;
+
+ /* Trivial case of no overlap in the y direction. */
+ if (py < y || py > y + sy - 1) {
+ r->px[0] = px;
+ r->nx[0] = nx;
+ r->px[1] = 0;
+ r->nx[1] = 0;
+ return;
+ }
+
+ /* Visible bit to the left of the popup. */
+ if (px < x) {
+ r->px[0] = px;
+ r->nx[0] = x - px;
+ if (r->nx[0] > nx)
+ r->nx[0] = nx;
+ } else {
+ r->px[0] = 0;
+ r->nx[0] = 0;
+ }
+
+ /* Visible bit to the right of the popup. */
+ ox = x + sx;
+ if (px > ox)
+ ox = px;
+ onx = px + nx;
+ if (onx > ox) {
+ r->px[1] = ox;
+ r->nx[1] = onx - ox;
+ } else {
+ r->px[1] = 0;
+ r->nx[1] = 0;
+ }
+}
+
+/* Check if this client is inside this server. */
+int
+server_client_check_nested(struct client *c)
+{
+ struct environ_entry *envent;
+ struct window_pane *wp;
+
+ envent = environ_find(c->environ, "TMUX");
+ if (envent == NULL || *envent->value == '\0')
+ return (0);
+
+ RB_FOREACH(wp, window_pane_tree, &all_window_panes) {
+ if (strcmp(wp->tty, c->ttyname) == 0)
+ return (1);
+ }
+ return (0);
+}
+
+/* Set client key table. */
+void
+server_client_set_key_table(struct client *c, const char *name)
+{
+ if (name == NULL)
+ name = server_client_get_key_table(c);
+
+ key_bindings_unref_table(c->keytable);
+ c->keytable = key_bindings_get_table(name, 1);
+ c->keytable->references++;
+}
+
+/* Get default key table. */
+const char *
+server_client_get_key_table(struct client *c)
+{
+ struct session *s = c->session;
+ const char *name;
+
+ if (s == NULL)
+ return ("root");
+
+ name = options_get_string(s->options, "key-table");
+ if (*name == '\0')
+ return ("root");
+ return (name);
+}
+
+/* Is this table the default key table? */
+static int
+server_client_is_default_key_table(struct client *c, struct key_table *table)
+{
+ return (strcmp(table->name, server_client_get_key_table(c)) == 0);
+}
+
+/* Create a new client. */
+struct client *
+server_client_create(int fd)
+{
+ struct client *c;
+
+ setblocking(fd, 0);
+
+ c = xcalloc(1, sizeof *c);
+ c->references = 1;
+ c->peer = proc_add_peer(server_proc, fd, server_client_dispatch, c);
+
+ if (gettimeofday(&c->creation_time, NULL) != 0)
+ fatal("gettimeofday failed");
+ memcpy(&c->activity_time, &c->creation_time, sizeof c->activity_time);
+
+ c->environ = environ_create();
+
+ c->fd = -1;
+ c->out_fd = -1;
+
+ c->queue = cmdq_new();
+ RB_INIT(&c->windows);
+ RB_INIT(&c->files);
+
+ c->tty.sx = 80;
+ c->tty.sy = 24;
+
+ status_init(c);
+ c->flags |= CLIENT_FOCUSED;
+
+ c->keytable = key_bindings_get_table("root", 1);
+ c->keytable->references++;
+
+ evtimer_set(&c->repeat_timer, server_client_repeat_timer, c);
+ evtimer_set(&c->click_timer, server_client_click_timer, c);
+
+ TAILQ_INSERT_TAIL(&clients, c, entry);
+ log_debug("new client %p", c);
+ return (c);
+}
+
+/* Open client terminal if needed. */
+int
+server_client_open(struct client *c, char **cause)
+{
+ const char *ttynam = _PATH_TTY;
+
+ if (c->flags & CLIENT_CONTROL)
+ return (0);
+
+ if (strcmp(c->ttyname, ttynam) == 0||
+ ((isatty(STDIN_FILENO) &&
+ (ttynam = ttyname(STDIN_FILENO)) != NULL &&
+ strcmp(c->ttyname, ttynam) == 0) ||
+ (isatty(STDOUT_FILENO) &&
+ (ttynam = ttyname(STDOUT_FILENO)) != NULL &&
+ strcmp(c->ttyname, ttynam) == 0) ||
+ (isatty(STDERR_FILENO) &&
+ (ttynam = ttyname(STDERR_FILENO)) != NULL &&
+ strcmp(c->ttyname, ttynam) == 0))) {
+ xasprintf(cause, "can't use %s", c->ttyname);
+ return (-1);
+ }
+
+ if (!(c->flags & CLIENT_TERMINAL)) {
+ *cause = xstrdup("not a terminal");
+ return (-1);
+ }
+
+ if (tty_open(&c->tty, cause) != 0)
+ return (-1);
+
+ return (0);
+}
+
+/* Lost an attached client. */
+static void
+server_client_attached_lost(struct client *c)
+{
+ struct session *s;
+ struct window *w;
+ struct client *loop;
+ struct client *found;
+
+ log_debug("lost attached client %p", c);
+
+ /*
+ * By this point the session in the client has been cleared so walk all
+ * windows to find any with this client as the latest.
+ */
+ RB_FOREACH(w, windows, &windows) {
+ if (w->latest != c)
+ continue;
+
+ found = NULL;
+ TAILQ_FOREACH(loop, &clients, entry) {
+ s = loop->session;
+ if (loop == c || s == NULL || s->curw->window != w)
+ continue;
+ if (found == NULL || timercmp(&loop->activity_time,
+ &found->activity_time, >))
+ found = loop;
+ }
+ if (found != NULL)
+ server_client_update_latest(found);
+ }
+}
+
+/* Set client session. */
+void
+server_client_set_session(struct client *c, struct session *s)
+{
+ struct session *old = c->session;
+
+ if (s != NULL && c->session != NULL && c->session != s)
+ c->last_session = c->session;
+ else if (s == NULL)
+ c->last_session = NULL;
+ c->session = s;
+ c->flags |= CLIENT_FOCUSED;
+
+ if (old != NULL && old->curw != NULL)
+ window_update_focus(old->curw->window);
+ if (s != NULL) {
+ recalculate_sizes();
+ window_update_focus(s->curw->window);
+ session_update_activity(s, NULL);
+ gettimeofday(&s->last_attached_time, NULL);
+ s->curw->flags &= ~WINLINK_ALERTFLAGS;
+ s->curw->window->latest = c;
+ alerts_check_session(s);
+ tty_update_client_offset(c);
+ status_timer_start(c);
+ notify_client("client-session-changed", c);
+ server_redraw_client(c);
+ }
+
+ server_check_unattached();
+ server_update_socket();
+}
+
+/* Lost a client. */
+void
+server_client_lost(struct client *c)
+{
+ struct client_file *cf, *cf1;
+ struct client_window *cw, *cw1;
+
+ c->flags |= CLIENT_DEAD;
+
+ server_client_clear_overlay(c);
+ status_prompt_clear(c);
+ status_message_clear(c);
+
+ RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) {
+ cf->error = EINTR;
+ file_fire_done(cf);
+ }
+ RB_FOREACH_SAFE(cw, client_windows, &c->windows, cw1) {
+ RB_REMOVE(client_windows, &c->windows, cw);
+ free(cw);
+ }
+
+ TAILQ_REMOVE(&clients, c, entry);
+ log_debug("lost client %p", c);
+
+ if (c->flags & CLIENT_ATTACHED) {
+ server_client_attached_lost(c);
+ notify_client("client-detached", c);
+ }
+
+ if (c->flags & CLIENT_CONTROL)
+ control_stop(c);
+ if (c->flags & CLIENT_TERMINAL)
+ tty_free(&c->tty);
+ free(c->ttyname);
+ free(c->clipboard_panes);
+
+ free(c->term_name);
+ free(c->term_type);
+ tty_term_free_list(c->term_caps, c->term_ncaps);
+
+ status_free(c);
+
+ free(c->title);
+ free((void *)c->cwd);
+
+ evtimer_del(&c->repeat_timer);
+ evtimer_del(&c->click_timer);
+
+ key_bindings_unref_table(c->keytable);
+
+ free(c->message_string);
+ if (event_initialized(&c->message_timer))
+ evtimer_del(&c->message_timer);
+
+ free(c->prompt_saved);
+ free(c->prompt_string);
+ free(c->prompt_buffer);
+
+ format_lost_client(c);
+ environ_free(c->environ);
+
+ proc_remove_peer(c->peer);
+ c->peer = NULL;
+
+ if (c->out_fd != -1)
+ close(c->out_fd);
+ if (c->fd != -1) {
+ close(c->fd);
+ c->fd = -1;
+ }
+ server_client_unref(c);
+
+ server_add_accept(0); /* may be more file descriptors now */
+
+ recalculate_sizes();
+ server_check_unattached();
+ server_update_socket();
+}
+
+/* Remove reference from a client. */
+void
+server_client_unref(struct client *c)
+{
+ log_debug("unref client %p (%d references)", c, c->references);
+
+ c->references--;
+ if (c->references == 0)
+ event_once(-1, EV_TIMEOUT, server_client_free, c, NULL);
+}
+
+/* Free dead client. */
+static void
+server_client_free(__unused int fd, __unused short events, void *arg)
+{
+ struct client *c = arg;
+
+ log_debug("free client %p (%d references)", c, c->references);
+
+ cmdq_free(c->queue);
+
+ if (c->references == 0) {
+ free((void *)c->name);
+ free(c);
+ }
+}
+
+/* Suspend a client. */
+void
+server_client_suspend(struct client *c)
+{
+ struct session *s = c->session;
+
+ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
+ return;
+
+ tty_stop_tty(&c->tty);
+ c->flags |= CLIENT_SUSPENDED;
+ proc_send(c->peer, MSG_SUSPEND, -1, NULL, 0);
+}
+
+/* Detach a client. */
+void
+server_client_detach(struct client *c, enum msgtype msgtype)
+{
+ struct session *s = c->session;
+
+ if (s == NULL || (c->flags & CLIENT_NODETACHFLAGS))
+ return;
+
+ c->flags |= CLIENT_EXIT;
+
+ c->exit_type = CLIENT_EXIT_DETACH;
+ c->exit_msgtype = msgtype;
+ c->exit_session = xstrdup(s->name);
+}
+
+/* Execute command to replace a client. */
+void
+server_client_exec(struct client *c, const char *cmd)
+{
+ struct session *s = c->session;
+ char *msg;
+ const char *shell;
+ size_t cmdsize, shellsize;
+
+ if (*cmd == '\0')
+ return;
+ cmdsize = strlen(cmd) + 1;
+
+ if (s != NULL)
+ shell = options_get_string(s->options, "default-shell");
+ else
+ shell = options_get_string(global_s_options, "default-shell");
+ if (!checkshell(shell))
+ shell = _PATH_BSHELL;
+ shellsize = strlen(shell) + 1;
+
+ msg = xmalloc(cmdsize + shellsize);
+ memcpy(msg, cmd, cmdsize);
+ memcpy(msg + cmdsize, shell, shellsize);
+
+ proc_send(c->peer, MSG_EXEC, -1, msg, cmdsize + shellsize);
+ free(msg);
+}
+
+/* Check for mouse keys. */
+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;
+ u_int x, y, b, sx, sy, px, py;
+ int ignore = 0;
+ key_code key;
+ struct timeval tv;
+ struct style_range *sr;
+ enum { NOTYPE,
+ MOVE,
+ DOWN,
+ UP,
+ DRAG,
+ WHEEL,
+ SECOND,
+ DOUBLE,
+ TRIPLE } type = NOTYPE;
+ enum { NOWHERE,
+ PANE,
+ STATUS,
+ STATUS_LEFT,
+ STATUS_RIGHT,
+ STATUS_DEFAULT,
+ BORDER } where = NOWHERE;
+
+ log_debug("%s mouse %02x at %u,%u (last %u,%u) (%d)", c->name, m->b,
+ m->x, m->y, m->lx, m->ly, c->tty.mouse_drag_flag);
+
+ /* What type of event is this? */
+ if (event->key == KEYC_DOUBLECLICK) {
+ type = DOUBLE;
+ x = m->x, y = m->y, b = m->b;
+ ignore = 1;
+ log_debug("double-click at %u,%u", x, y);
+ } else if ((m->sgr_type != ' ' &&
+ MOUSE_DRAG(m->sgr_b) &&
+ MOUSE_RELEASE(m->sgr_b)) ||
+ (m->sgr_type == ' ' &&
+ MOUSE_DRAG(m->b) &&
+ MOUSE_RELEASE(m->b) &&
+ MOUSE_RELEASE(m->lb))) {
+ type = MOVE;
+ x = m->x, y = m->y, b = 0;
+ log_debug("move at %u,%u", x, y);
+ } else if (MOUSE_DRAG(m->b)) {
+ type = DRAG;
+ if (c->tty.mouse_drag_flag) {
+ x = m->x, y = m->y, b = m->b;
+ if (x == m->lx && y == m->ly)
+ return (KEYC_UNKNOWN);
+ log_debug("drag update at %u,%u", x, y);
+ } else {
+ x = m->lx, y = m->ly, b = m->lb;
+ log_debug("drag start at %u,%u", x, y);
+ }
+ } else if (MOUSE_WHEEL(m->b)) {
+ type = WHEEL;
+ x = m->x, y = m->y, b = m->b;
+ log_debug("wheel at %u,%u", x, y);
+ } else if (MOUSE_RELEASE(m->b)) {
+ type = UP;
+ x = m->x, y = m->y, b = m->lb;
+ log_debug("up at %u,%u", x, y);
+ } else {
+ if (c->flags & CLIENT_DOUBLECLICK) {
+ evtimer_del(&c->click_timer);
+ c->flags &= ~CLIENT_DOUBLECLICK;
+ if (m->b == c->click_button) {
+ type = SECOND;
+ x = m->x, y = m->y, b = m->b;
+ log_debug("second-click at %u,%u", x, y);
+ c->flags |= CLIENT_TRIPLECLICK;
+ }
+ } else if (c->flags & CLIENT_TRIPLECLICK) {
+ evtimer_del(&c->click_timer);
+ c->flags &= ~CLIENT_TRIPLECLICK;
+ if (m->b == c->click_button) {
+ type = TRIPLE;
+ x = m->x, y = m->y, b = m->b;
+ log_debug("triple-click at %u,%u", x, y);
+ goto have_event;
+ }
+ } else {
+ type = DOWN;
+ x = m->x, y = m->y, b = m->b;
+ log_debug("down at %u,%u", x, y);
+ c->flags |= CLIENT_DOUBLECLICK;
+ }
+
+ if (KEYC_CLICK_TIMEOUT != 0) {
+ memcpy(&c->click_event, m, sizeof c->click_event);
+ c->click_button = m->b;
+
+ log_debug("click timer started");
+ tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000;
+ tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L;
+ evtimer_del(&c->click_timer);
+ evtimer_add(&c->click_timer, &tv);
+ }
+ }
+
+have_event:
+ if (type == NOTYPE)
+ return (KEYC_UNKNOWN);
+
+ /* Save the session. */
+ m->s = s->id;
+ m->w = -1;
+ m->ignore = ignore;
+
+ /* Is this on the status line? */
+ m->statusat = status_at_line(c);
+ m->statuslines = status_line_size(c);
+ if (m->statusat != -1 &&
+ y >= (u_int)m->statusat &&
+ y < m->statusat + m->statuslines) {
+ sr = status_get_range(c, x, y - m->statusat);
+ if (sr == NULL) {
+ where = STATUS_DEFAULT;
+ } else {
+ switch (sr->type) {
+ case STYLE_RANGE_NONE:
+ return (KEYC_UNKNOWN);
+ case STYLE_RANGE_LEFT:
+ where = STATUS_LEFT;
+ break;
+ case STYLE_RANGE_RIGHT:
+ where = STATUS_RIGHT;
+ break;
+ case STYLE_RANGE_WINDOW:
+ wl = winlink_find_by_index(&s->windows,
+ sr->argument);
+ if (wl == NULL)
+ return (KEYC_UNKNOWN);
+ m->w = wl->window->id;
+
+ where = STATUS;
+ break;
+ }
+ }
+ }
+
+ /* Not on status line. Adjust position and check for border or pane. */
+ if (where == NOWHERE) {
+ px = x;
+ if (m->statusat == 0 && y >= m->statuslines)
+ py = y - m->statuslines;
+ else if (m->statusat > 0 && y >= (u_int)m->statusat)
+ py = m->statusat - 1;
+ else
+ py = y;
+
+ tty_window_offset(&c->tty, &m->ox, &m->oy, &sx, &sy);
+ log_debug("mouse window @%u at %u,%u (%ux%u)",
+ s->curw->window->id, m->ox, m->oy, sx, sy);
+ if (px > sx || py > sy)
+ return (KEYC_UNKNOWN);
+ px = px + m->ox;
+ py = py + m->oy;
+
+ /* Try the pane borders if not zoomed. */
+ if (~s->curw->window->flags & WINDOW_ZOOMED) {
+ TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
+ if ((wp->xoff + wp->sx == px &&
+ wp->yoff <= 1 + py &&
+ wp->yoff + wp->sy >= py) ||
+ (wp->yoff + wp->sy == py &&
+ wp->xoff <= 1 + px &&
+ wp->xoff + wp->sx >= px))
+ break;
+ }
+ if (wp != NULL)
+ where = BORDER;
+ }
+
+ /* Otherwise try inside the pane. */
+ if (where == NOWHERE) {
+ wp = window_get_active_at(s->curw->window, px, py);
+ if (wp != NULL)
+ where = PANE;
+ else
+ return (KEYC_UNKNOWN);
+ }
+ if (where == PANE)
+ log_debug("mouse %u,%u on pane %%%u", x, y, wp->id);
+ else if (where == BORDER)
+ log_debug("mouse on pane %%%u border", wp->id);
+ m->wp = wp->id;
+ m->w = wp->window->id;
+ } else
+ m->wp = -1;
+
+ /* Stop dragging if needed. */
+ if (type != DRAG && type != WHEEL && c->tty.mouse_drag_flag != 0) {
+ if (c->tty.mouse_drag_release != NULL)
+ c->tty.mouse_drag_release(c, m);
+
+ c->tty.mouse_drag_update = NULL;
+ c->tty.mouse_drag_release = NULL;
+
+ /*
+ * End a mouse drag by passing a MouseDragEnd key corresponding
+ * to the button that started the drag.
+ */
+ switch (c->tty.mouse_drag_flag - 1) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND1_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND2_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND3_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND6_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND7_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND8_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND9_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND10_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND10_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND10_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND10_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND10_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND10_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAGEND11_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAGEND11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAGEND11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAGEND11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAGEND11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAGEND11_BORDER;
+ break;
+ default:
+ key = KEYC_MOUSE;
+ break;
+ }
+ c->tty.mouse_drag_flag = 0;
+ goto out;
+ }
+
+ /* Convert to a key binding. */
+ key = KEYC_UNKNOWN;
+ switch (type) {
+ case NOTYPE:
+ break;
+ case MOVE:
+ if (where == PANE)
+ key = KEYC_MOUSEMOVE_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEMOVE_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEMOVE_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEMOVE_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEMOVE_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEMOVE_BORDER;
+ break;
+ case DRAG:
+ if (c->tty.mouse_drag_update != NULL)
+ key = KEYC_DRAGGING;
+ else {
+ switch (MOUSE_BUTTONS(b)) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG1_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG2_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG3_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG6_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG7_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG8_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG9_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG10_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG10_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG10_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG10_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG10_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG10_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_MOUSEDRAG11_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDRAG11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDRAG11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDRAG11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDRAG11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDRAG11_BORDER;
+ break;
+ }
+ }
+
+ /*
+ * Begin a drag by setting the flag to a non-zero value that
+ * corresponds to the mouse button in use.
+ */
+ c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1;
+ break;
+ case WHEEL:
+ if (MOUSE_BUTTONS(b) == MOUSE_WHEEL_UP) {
+ if (where == PANE)
+ key = KEYC_WHEELUP_PANE;
+ if (where == STATUS)
+ key = KEYC_WHEELUP_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_WHEELUP_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_WHEELUP_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_WHEELUP_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_WHEELUP_BORDER;
+ } else {
+ if (where == PANE)
+ key = KEYC_WHEELDOWN_PANE;
+ if (where == STATUS)
+ key = KEYC_WHEELDOWN_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_WHEELDOWN_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_WHEELDOWN_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_WHEELDOWN_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_WHEELDOWN_BORDER;
+ }
+ break;
+ case UP:
+ switch (MOUSE_BUTTONS(b)) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_MOUSEUP1_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_MOUSEUP2_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_MOUSEUP3_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_MOUSEUP6_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_MOUSEUP7_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_MOUSEUP8_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_MOUSEUP9_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_MOUSEUP1_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP1_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_MOUSEUP11_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEUP11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEUP11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEUP11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEUP11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEUP11_BORDER;
+ break;
+ }
+ break;
+ case DOWN:
+ switch (MOUSE_BUTTONS(b)) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN1_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN2_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN3_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN6_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN7_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN8_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN9_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN10_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN10_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN10_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN10_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN10_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN10_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_MOUSEDOWN11_PANE;
+ if (where == STATUS)
+ key = KEYC_MOUSEDOWN11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_MOUSEDOWN11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_MOUSEDOWN11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_MOUSEDOWN11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_MOUSEDOWN11_BORDER;
+ break;
+ }
+ break;
+ case SECOND:
+ switch (MOUSE_BUTTONS(b)) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK1_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK2_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK3_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK6_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK7_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK8_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK9_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK10_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK10_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK10_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK10_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK10_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK10_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_SECONDCLICK11_PANE;
+ if (where == STATUS)
+ key = KEYC_SECONDCLICK11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_SECONDCLICK11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_SECONDCLICK11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_SECONDCLICK11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_SECONDCLICK11_BORDER;
+ break;
+ }
+ break;
+ case DOUBLE:
+ switch (MOUSE_BUTTONS(b)) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK1_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK2_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK3_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK6_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK7_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK8_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK9_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK10_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK10_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK10_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK10_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK10_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK10_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_DOUBLECLICK11_PANE;
+ if (where == STATUS)
+ key = KEYC_DOUBLECLICK11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_DOUBLECLICK11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_DOUBLECLICK11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_DOUBLECLICK11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_DOUBLECLICK11_BORDER;
+ break;
+ }
+ break;
+ case TRIPLE:
+ switch (MOUSE_BUTTONS(b)) {
+ case MOUSE_BUTTON_1:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK1_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK1_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK1_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK1_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK1_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK1_BORDER;
+ break;
+ case MOUSE_BUTTON_2:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK2_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK2_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK2_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK2_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK2_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK2_BORDER;
+ break;
+ case MOUSE_BUTTON_3:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK3_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK3_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK3_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK3_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK3_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK3_BORDER;
+ break;
+ case MOUSE_BUTTON_6:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK6_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK6_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK6_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK6_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK6_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK6_BORDER;
+ break;
+ case MOUSE_BUTTON_7:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK7_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK7_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK7_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK7_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK7_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK7_BORDER;
+ break;
+ case MOUSE_BUTTON_8:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK8_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK8_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK8_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK8_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK8_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK8_BORDER;
+ break;
+ case MOUSE_BUTTON_9:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK9_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK9_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK9_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK9_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK9_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK9_BORDER;
+ break;
+ case MOUSE_BUTTON_10:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK10_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK10_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK10_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK10_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK10_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK10_BORDER;
+ break;
+ case MOUSE_BUTTON_11:
+ if (where == PANE)
+ key = KEYC_TRIPLECLICK11_PANE;
+ if (where == STATUS)
+ key = KEYC_TRIPLECLICK11_STATUS;
+ if (where == STATUS_LEFT)
+ key = KEYC_TRIPLECLICK11_STATUS_LEFT;
+ if (where == STATUS_RIGHT)
+ key = KEYC_TRIPLECLICK11_STATUS_RIGHT;
+ if (where == STATUS_DEFAULT)
+ key = KEYC_TRIPLECLICK11_STATUS_DEFAULT;
+ if (where == BORDER)
+ key = KEYC_TRIPLECLICK11_BORDER;
+ break;
+ }
+ break;
+ }
+ if (key == KEYC_UNKNOWN)
+ return (KEYC_UNKNOWN);
+
+out:
+ /* Apply modifiers if any. */
+ if (b & MOUSE_MASK_META)
+ key |= KEYC_META;
+ if (b & MOUSE_MASK_CTRL)
+ key |= KEYC_CTRL;
+ if (b & MOUSE_MASK_SHIFT)
+ key |= KEYC_SHIFT;
+
+ if (log_get_level() != 0)
+ log_debug("mouse key is %s", key_string_lookup_key (key, 1));
+ return (key);
+}
+
+/* Is this fast enough to probably be a paste? */
+static int
+server_client_assume_paste(struct session *s)
+{
+ struct timeval tv;
+ int t;
+
+ if ((t = options_get_number(s->options, "assume-paste-time")) == 0)
+ return (0);
+
+ timersub(&s->activity_time, &s->last_activity_time, &tv);
+ if (tv.tv_sec == 0 && tv.tv_usec < t * 1000) {
+ log_debug("session %s pasting (flag %d)", s->name,
+ !!(s->flags & SESSION_PASTING));
+ if (s->flags & SESSION_PASTING)
+ return (1);
+ s->flags |= SESSION_PASTING;
+ return (0);
+ }
+ log_debug("session %s not pasting", s->name);
+ s->flags &= ~SESSION_PASTING;
+ return (0);
+}
+
+/* Has the latest client changed? */
+static void
+server_client_update_latest(struct client *c)
+{
+ struct window *w;
+
+ if (c->session == NULL)
+ return;
+ w = c->session->curw->window;
+
+ if (w->latest == c)
+ return;
+ w->latest = c;
+
+ if (options_get_number(w->options, "window-size") == WINDOW_SIZE_LATEST)
+ recalculate_size(w, 0);
+
+ notify_client("client-active", c);
+}
+
+/*
+ * Handle data key input from client. This owns and can modify the key event it
+ * is given and is responsible for freeing it.
+ */
+static enum cmd_retval
+server_client_key_callback(struct cmdq_item *item, void *data)
+{
+ struct client *c = cmdq_get_client(item);
+ struct key_event *event = data;
+ key_code key = event->key;
+ struct mouse_event *m = &event->m;
+ struct session *s = c->session;
+ struct winlink *wl;
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+ struct timeval tv;
+ struct key_table *table, *first;
+ struct key_binding *bd;
+ int xtimeout, flags;
+ struct cmd_find_state fs;
+ key_code key0;
+
+ /* Check the client is good to accept input. */
+ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
+ goto out;
+ wl = s->curw;
+
+ /* Update the activity timer. */
+ if (gettimeofday(&c->activity_time, NULL) != 0)
+ fatal("gettimeofday failed");
+ session_update_activity(s, &c->activity_time);
+
+ /* Check for mouse keys. */
+ m->valid = 0;
+ if (key == KEYC_MOUSE || key == KEYC_DOUBLECLICK) {
+ if (c->flags & CLIENT_READONLY)
+ goto out;
+ key = server_client_check_mouse(c, event);
+ if (key == KEYC_UNKNOWN)
+ goto out;
+
+ m->valid = 1;
+ m->key = key;
+
+ /*
+ * Mouse drag is in progress, so fire the callback (now that
+ * the mouse event is valid).
+ */
+ if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
+ c->tty.mouse_drag_update(c, m);
+ goto out;
+ }
+ event->key = key;
+ }
+
+ /* Find affected pane. */
+ if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0)
+ cmd_find_from_client(&fs, c, 0);
+ wp = fs.wp;
+
+ /* Forward mouse keys if disabled. */
+ if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse"))
+ goto forward_key;
+
+ /* Treat everything as a regular key when pasting is detected. */
+ if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s))
+ goto forward_key;
+
+ /*
+ * Work out the current key table. If the pane is in a mode, use
+ * the mode table instead of the default key table.
+ */
+ if (server_client_is_default_key_table(c, c->keytable) &&
+ wp != NULL &&
+ (wme = TAILQ_FIRST(&wp->modes)) != NULL &&
+ wme->mode->key_table != NULL)
+ table = key_bindings_get_table(wme->mode->key_table(wme), 1);
+ else
+ table = c->keytable;
+ first = table;
+
+table_changed:
+ /*
+ * The prefix always takes precedence and forces a switch to the prefix
+ * table, unless we are already there.
+ */
+ 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")) &&
+ strcmp(table->name, "prefix") != 0) {
+ server_client_set_key_table(c, "prefix");
+ server_status_client(c);
+ goto out;
+ }
+ flags = c->flags;
+
+try_again:
+ /* Log key table. */
+ if (wp == NULL)
+ log_debug("key table %s (no pane)", table->name);
+ else
+ log_debug("key table %s (pane %%%u)", table->name, wp->id);
+ if (c->flags & CLIENT_REPEAT)
+ log_debug("currently repeating");
+
+ /* Try to see if there is a key binding in the current table. */
+ bd = key_bindings_get(table, key0);
+ if (bd != NULL) {
+ /*
+ * Key was matched in this table. If currently repeating but a
+ * non-repeating binding was found, stop repeating and try
+ * again in the root table.
+ */
+ if ((c->flags & CLIENT_REPEAT) &&
+ (~bd->flags & KEY_BINDING_REPEAT)) {
+ log_debug("found in key table %s (not repeating)",
+ table->name);
+ server_client_set_key_table(c, NULL);
+ first = table = c->keytable;
+ c->flags &= ~CLIENT_REPEAT;
+ server_status_client(c);
+ goto table_changed;
+ }
+ log_debug("found in key table %s", table->name);
+
+ /*
+ * Take a reference to this table to make sure the key binding
+ * doesn't disappear.
+ */
+ table->references++;
+
+ /*
+ * If this is a repeating key, start the timer. Otherwise reset
+ * the client back to the root table.
+ */
+ xtimeout = options_get_number(s->options, "repeat-time");
+ if (xtimeout != 0 && (bd->flags & KEY_BINDING_REPEAT)) {
+ c->flags |= CLIENT_REPEAT;
+
+ tv.tv_sec = xtimeout / 1000;
+ tv.tv_usec = (xtimeout % 1000) * 1000L;
+ evtimer_del(&c->repeat_timer);
+ evtimer_add(&c->repeat_timer, &tv);
+ } else {
+ c->flags &= ~CLIENT_REPEAT;
+ server_client_set_key_table(c, NULL);
+ }
+ server_status_client(c);
+
+ /* Execute the key binding. */
+ key_bindings_dispatch(bd, item, c, event, &fs);
+ key_bindings_unref_table(table);
+ goto out;
+ }
+
+ /*
+ * No match, try the ANY key.
+ */
+ if (key0 != KEYC_ANY) {
+ key0 = KEYC_ANY;
+ goto try_again;
+ }
+
+ /*
+ * No match in this table. If not in the root table or if repeating,
+ * switch the client back to the root table and try again.
+ */
+ log_debug("not found in key table %s", table->name);
+ if (!server_client_is_default_key_table(c, table) ||
+ (c->flags & CLIENT_REPEAT)) {
+ log_debug("trying in root table");
+ server_client_set_key_table(c, NULL);
+ table = c->keytable;
+ if (c->flags & CLIENT_REPEAT)
+ first = table;
+ c->flags &= ~CLIENT_REPEAT;
+ server_status_client(c);
+ goto table_changed;
+ }
+
+ /*
+ * No match in the root table either. If this wasn't the first table
+ * tried, don't pass the key to the pane.
+ */
+ if (first != table && (~flags & CLIENT_REPEAT)) {
+ server_client_set_key_table(c, NULL);
+ server_status_client(c);
+ goto out;
+ }
+
+forward_key:
+ if (c->flags & CLIENT_READONLY)
+ goto out;
+ if (wp != NULL)
+ window_pane_key(wp, c, s, wl, key, m);
+
+out:
+ if (s != NULL && key != KEYC_FOCUS_OUT)
+ server_client_update_latest(c);
+ free(event);
+ return (CMD_RETURN_NORMAL);
+}
+
+/* Handle a key event. */
+int
+server_client_handle_key(struct client *c, struct key_event *event)
+{
+ struct session *s = c->session;
+ struct cmdq_item *item;
+
+ /* Check the client is good to accept input. */
+ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
+ return (0);
+
+ /*
+ * Key presses in overlay mode and the command prompt are a special
+ * case. The queue might be blocked so they need to be processed
+ * immediately rather than queued.
+ */
+ if (~c->flags & CLIENT_READONLY) {
+ if (c->message_string != NULL) {
+ if (c->message_ignore_keys)
+ return (0);
+ status_message_clear(c);
+ }
+ if (c->overlay_key != NULL) {
+ switch (c->overlay_key(c, c->overlay_data, event)) {
+ case 0:
+ return (0);
+ case 1:
+ server_client_clear_overlay(c);
+ return (0);
+ }
+ }
+ server_client_clear_overlay(c);
+ if (c->prompt_string != NULL) {
+ if (status_prompt_key(c, event->key) == 0)
+ return (0);
+ }
+ }
+
+ /*
+ * Add the key to the queue so it happens after any commands queued by
+ * previous keys.
+ */
+ item = cmdq_get_callback(server_client_key_callback, event);
+ cmdq_append(c, item);
+ return (1);
+}
+
+/* Client functions that need to happen every loop. */
+void
+server_client_loop(void)
+{
+ struct client *c;
+ struct window *w;
+ struct window_pane *wp;
+
+ /* Check for window resize. This is done before redrawing. */
+ RB_FOREACH(w, windows, &windows)
+ server_client_check_window_resize(w);
+
+ /* Check clients. */
+ TAILQ_FOREACH(c, &clients, entry) {
+ server_client_check_exit(c);
+ if (c->session != NULL) {
+ server_client_check_modes(c);
+ server_client_check_redraw(c);
+ server_client_reset_state(c);
+ }
+ }
+
+ /*
+ * Any windows will have been redrawn as part of clients, so clear
+ * their flags now.
+ */
+ RB_FOREACH(w, windows, &windows) {
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp->fd != -1) {
+ server_client_check_pane_resize(wp);
+ server_client_check_pane_buffer(wp);
+ }
+ wp->flags &= ~PANE_REDRAW;
+ }
+ check_window_name(w);
+ }
+}
+
+/* Check if window needs to be resized. */
+static void
+server_client_check_window_resize(struct window *w)
+{
+ struct winlink *wl;
+
+ if (~w->flags & WINDOW_RESIZE)
+ return;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->session->attached != 0 && wl->session->curw == wl)
+ break;
+ }
+ if (wl == NULL)
+ return;
+
+ log_debug("%s: resizing window @%u", __func__, w->id);
+ resize_window(w, w->new_sx, w->new_sy, w->new_xpixel, w->new_ypixel);
+}
+
+/* Resize timer event. */
+static void
+server_client_resize_timer(__unused int fd, __unused short events, void *data)
+{
+ struct window_pane *wp = data;
+
+ log_debug("%s: %%%u resize timer expired", __func__, wp->id);
+ evtimer_del(&wp->resize_timer);
+}
+
+/* Check if pane should be resized. */
+static void
+server_client_check_pane_resize(struct window_pane *wp)
+{
+ struct window_pane_resize *r;
+ struct window_pane_resize *r1;
+ struct window_pane_resize *first;
+ struct window_pane_resize *last;
+ struct timeval tv = { .tv_usec = 250000 };
+
+ if (TAILQ_EMPTY(&wp->resize_queue))
+ return;
+
+ if (!event_initialized(&wp->resize_timer))
+ evtimer_set(&wp->resize_timer, server_client_resize_timer, wp);
+ if (evtimer_pending(&wp->resize_timer, NULL))
+ return;
+
+ log_debug("%s: %%%u needs to be resized", __func__, wp->id);
+ TAILQ_FOREACH(r, &wp->resize_queue, entry) {
+ log_debug("queued resize: %ux%u -> %ux%u", r->osx, r->osy,
+ r->sx, r->sy);
+ }
+
+ /*
+ * There are three cases that matter:
+ *
+ * - Only one resize. It can just be applied.
+ *
+ * - Multiple resizes and the ending size is different from the
+ * starting size. We can discard all resizes except the most recent.
+ *
+ * - Multiple resizes and the ending size is the same as the starting
+ * size. We must resize at least twice to force the application to
+ * redraw. So apply the first and leave the last on the queue for
+ * next time.
+ */
+ first = TAILQ_FIRST(&wp->resize_queue);
+ last = TAILQ_LAST(&wp->resize_queue, window_pane_resizes);
+ if (first == last) {
+ /* Only one resize. */
+ window_pane_send_resize(wp, first->sx, first->sy);
+ TAILQ_REMOVE(&wp->resize_queue, first, entry);
+ free(first);
+ } else if (last->sx != first->osx || last->sy != first->osy) {
+ /* Multiple resizes ending up with a different size. */
+ window_pane_send_resize(wp, last->sx, last->sy);
+ TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) {
+ TAILQ_REMOVE(&wp->resize_queue, r, entry);
+ free(r);
+ }
+ } else {
+ /*
+ * Multiple resizes ending up with the same size. There will
+ * not be more than one to the same size in succession so we
+ * can just use the last-but-one on the list and leave the last
+ * for later. We reduce the time until the next check to avoid
+ * a long delay between the resizes.
+ */
+ r = TAILQ_PREV(last, window_pane_resizes, entry);
+ window_pane_send_resize(wp, r->sx, r->sy);
+ TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) {
+ if (r == last)
+ break;
+ TAILQ_REMOVE(&wp->resize_queue, r, entry);
+ free(r);
+ }
+ tv.tv_usec = 10000;
+ }
+ evtimer_add(&wp->resize_timer, &tv);
+}
+
+/* Check pane buffer size. */
+static void
+server_client_check_pane_buffer(struct window_pane *wp)
+{
+ struct evbuffer *evb = wp->event->input;
+ size_t minimum;
+ struct client *c;
+ struct window_pane_offset *wpo;
+ int off = 1, flag;
+ u_int attached_clients = 0;
+ size_t new_size;
+
+ /*
+ * Work out the minimum used size. This is the most that can be removed
+ * from the buffer.
+ */
+ minimum = wp->offset.used;
+ if (wp->pipe_fd != -1 && wp->pipe_offset.used < minimum)
+ minimum = wp->pipe_offset.used;
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == NULL)
+ continue;
+ attached_clients++;
+
+ if (~c->flags & CLIENT_CONTROL) {
+ off = 0;
+ continue;
+ }
+ wpo = control_pane_offset(c, wp, &flag);
+ if (wpo == NULL) {
+ off = 0;
+ continue;
+ }
+ if (!flag)
+ off = 0;
+
+ window_pane_get_new_data(wp, wpo, &new_size);
+ log_debug("%s: %s has %zu bytes used and %zu left for %%%u",
+ __func__, c->name, wpo->used - wp->base_offset, new_size,
+ wp->id);
+ if (wpo->used < minimum)
+ minimum = wpo->used;
+ }
+ if (attached_clients == 0)
+ off = 0;
+ minimum -= wp->base_offset;
+ if (minimum == 0)
+ goto out;
+
+ /* Drain the buffer. */
+ log_debug("%s: %%%u has %zu minimum (of %zu) bytes used", __func__,
+ wp->id, minimum, EVBUFFER_LENGTH(evb));
+ evbuffer_drain(evb, minimum);
+
+ /*
+ * Adjust the base offset. If it would roll over, all the offsets into
+ * the buffer need to be adjusted.
+ */
+ if (wp->base_offset > SIZE_MAX - minimum) {
+ log_debug("%s: %%%u base offset has wrapped", __func__, wp->id);
+ wp->offset.used -= wp->base_offset;
+ if (wp->pipe_fd != -1)
+ wp->pipe_offset.used -= wp->base_offset;
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == NULL || (~c->flags & CLIENT_CONTROL))
+ continue;
+ wpo = control_pane_offset(c, wp, &flag);
+ if (wpo != NULL && !flag)
+ wpo->used -= wp->base_offset;
+ }
+ wp->base_offset = minimum;
+ } else
+ wp->base_offset += minimum;
+
+out:
+ /*
+ * If there is data remaining, and there are no clients able to consume
+ * it, do not read any more. This is true when there are attached
+ * clients, all of which are control clients which are not able to
+ * accept any more data.
+ */
+ log_debug("%s: pane %%%u is %s", __func__, wp->id, off ? "off" : "on");
+ if (off)
+ bufferevent_disable(wp->event, EV_READ);
+ else
+ bufferevent_enable(wp->event, EV_READ);
+}
+
+/*
+ * Update cursor position and mode settings. The scroll region and attributes
+ * are cleared when idle (waiting for an event) as this is the most likely time
+ * a user may interrupt tmux, for example with ~^Z in ssh(1). This is a
+ * compromise between excessive resets and likelihood of an interrupt.
+ *
+ * tty_region/tty_reset/tty_update_mode already take care of not resetting
+ * things that are already in their default state.
+ */
+static void
+server_client_reset_state(struct client *c)
+{
+ struct tty *tty = &c->tty;
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp = server_client_get_pane(c), *loop;
+ struct screen *s = NULL;
+ struct options *oo = c->session->options;
+ int mode = 0, cursor, flags, n;
+ u_int cx = 0, cy = 0, ox, oy, sx, sy;
+
+ if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
+ return;
+
+ /* Disable the block flag. */
+ flags = (tty->flags & TTY_BLOCK);
+ tty->flags &= ~TTY_BLOCK;
+
+ /* Get mode from overlay if any, else from screen. */
+ if (c->overlay_draw != NULL) {
+ if (c->overlay_mode != NULL)
+ s = c->overlay_mode(c, c->overlay_data, &cx, &cy);
+ } else
+ s = wp->screen;
+ if (s != NULL)
+ mode = s->mode;
+ if (log_get_level() != 0) {
+ log_debug("%s: client %s mode %s", __func__, c->name,
+ screen_mode_to_string(mode));
+ }
+
+ /* Reset region and margin. */
+ tty_region_off(tty);
+ tty_margin_off(tty);
+
+ /* Move cursor to pane cursor and offset. */
+ if (c->prompt_string != NULL) {
+ n = options_get_number(c->session->options, "status-position");
+ if (n == 0)
+ cy = 0;
+ else {
+ n = status_line_size(c);
+ if (n == 0)
+ cy = tty->sy - 1;
+ else
+ cy = tty->sy - n;
+ }
+ cx = c->prompt_cursor;
+ mode &= ~MODE_CURSOR;
+ } else if (c->overlay_draw == NULL) {
+ cursor = 0;
+ tty_window_offset(tty, &ox, &oy, &sx, &sy);
+ if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx &&
+ wp->yoff + s->cy >= oy && wp->yoff + s->cy <= oy + sy) {
+ cursor = 1;
+
+ cx = wp->xoff + s->cx - ox;
+ cy = wp->yoff + s->cy - oy;
+
+ if (status_at_line(c) == 0)
+ cy += status_line_size(c);
+ }
+ if (!cursor)
+ mode &= ~MODE_CURSOR;
+ }
+ log_debug("%s: cursor to %u,%u", __func__, cx, cy);
+ tty_cursor(tty, cx, cy);
+
+ /*
+ * Set mouse mode if requested. To support dragging, always use button
+ * mode.
+ */
+ if (options_get_number(oo, "mouse")) {
+ if (c->overlay_draw == NULL) {
+ mode &= ~ALL_MOUSE_MODES;
+ TAILQ_FOREACH(loop, &w->panes, entry) {
+ if (loop->screen->mode & MODE_MOUSE_ALL)
+ mode |= MODE_MOUSE_ALL;
+ }
+ }
+ if (~mode & MODE_MOUSE_ALL)
+ mode |= MODE_MOUSE_BUTTON;
+ }
+
+ /* Clear bracketed paste mode if at the prompt. */
+ if (c->overlay_draw == NULL && c->prompt_string != NULL)
+ mode &= ~MODE_BRACKETPASTE;
+
+ /* Set the terminal mode and reset attributes. */
+ tty_update_mode(tty, mode, s);
+ tty_reset(tty);
+
+ /* All writing must be done, send a sync end (if it was started). */
+ tty_sync_end(tty);
+ tty->flags |= flags;
+}
+
+/* Repeat time callback. */
+static void
+server_client_repeat_timer(__unused int fd, __unused short events, void *data)
+{
+ struct client *c = data;
+
+ if (c->flags & CLIENT_REPEAT) {
+ server_client_set_key_table(c, NULL);
+ c->flags &= ~CLIENT_REPEAT;
+ server_status_client(c);
+ }
+}
+
+/* Double-click callback. */
+static void
+server_client_click_timer(__unused int fd, __unused short events, void *data)
+{
+ struct client *c = data;
+ struct key_event *event;
+
+ log_debug("click timer expired");
+
+ if (c->flags & CLIENT_TRIPLECLICK) {
+ /*
+ * Waiting for a third click that hasn't happened, so this must
+ * have been a double click.
+ */
+ event = xmalloc(sizeof *event);
+ event->key = KEYC_DOUBLECLICK;
+ memcpy(&event->m, &c->click_event, sizeof event->m);
+ if (!server_client_handle_key(c, event))
+ free(event);
+ }
+ c->flags &= ~(CLIENT_DOUBLECLICK|CLIENT_TRIPLECLICK);
+}
+
+/* Check if client should be exited. */
+static void
+server_client_check_exit(struct client *c)
+{
+ struct client_file *cf;
+ const char *name = c->exit_session;
+ char *data;
+ size_t size, msize;
+
+ if (c->flags & (CLIENT_DEAD|CLIENT_EXITED))
+ return;
+ if (~c->flags & CLIENT_EXIT)
+ return;
+
+ if (c->flags & CLIENT_CONTROL) {
+ control_discard(c);
+ if (!control_all_done(c))
+ return;
+ }
+ RB_FOREACH(cf, client_files, &c->files) {
+ if (EVBUFFER_LENGTH(cf->buffer) != 0)
+ return;
+ }
+ c->flags |= CLIENT_EXITED;
+
+ switch (c->exit_type) {
+ case CLIENT_EXIT_RETURN:
+ if (c->exit_message != NULL)
+ msize = strlen(c->exit_message) + 1;
+ else
+ msize = 0;
+ size = (sizeof c->retval) + msize;
+ data = xmalloc(size);
+ memcpy(data, &c->retval, sizeof c->retval);
+ if (c->exit_message != NULL)
+ memcpy(data + sizeof c->retval, c->exit_message, msize);
+ proc_send(c->peer, MSG_EXIT, -1, data, size);
+ free(data);
+ break;
+ case CLIENT_EXIT_SHUTDOWN:
+ proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0);
+ break;
+ case CLIENT_EXIT_DETACH:
+ proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1);
+ break;
+ }
+ free(c->exit_session);
+ free(c->exit_message);
+}
+
+/* Redraw timer callback. */
+static void
+server_client_redraw_timer(__unused int fd, __unused short events,
+ __unused void *data)
+{
+ log_debug("redraw timer fired");
+}
+
+/*
+ * Check if modes need to be updated. Only modes in the current window are
+ * updated and it is done when the status line is redrawn.
+ */
+static void
+server_client_check_modes(struct client *c)
+{
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+
+ if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
+ return;
+ if (~c->flags & CLIENT_REDRAWSTATUS)
+ return;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme != NULL && wme->mode->update != NULL)
+ wme->mode->update(wme);
+ }
+}
+
+/* Check for client redraws. */
+static void
+server_client_check_redraw(struct client *c)
+{
+ struct session *s = c->session;
+ struct tty *tty = &c->tty;
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp;
+ int needed, flags, mode = tty->mode, new_flags = 0;
+ int redraw;
+ u_int bit = 0;
+ struct timeval tv = { .tv_usec = 1000 };
+ static struct event ev;
+ size_t left;
+
+ if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
+ return;
+ if (c->flags & CLIENT_ALLREDRAWFLAGS) {
+ log_debug("%s: redraw%s%s%s%s%s", c->name,
+ (c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
+ (c->flags & CLIENT_REDRAWSTATUS) ? " status" : "",
+ (c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "",
+ (c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "",
+ (c->flags & CLIENT_REDRAWPANES) ? " panes" : "");
+ }
+
+ /*
+ * If there is outstanding data, defer the redraw until it has been
+ * consumed. We can just add a timer to get out of the event loop and
+ * end up back here.
+ */
+ needed = 0;
+ if (c->flags & CLIENT_ALLREDRAWFLAGS)
+ needed = 1;
+ else {
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp->flags & PANE_REDRAW) {
+ needed = 1;
+ break;
+ }
+ }
+ if (needed)
+ new_flags |= CLIENT_REDRAWPANES;
+ }
+ if (needed && (left = EVBUFFER_LENGTH(tty->out)) != 0) {
+ log_debug("%s: redraw deferred (%zu left)", c->name, left);
+ if (!evtimer_initialized(&ev))
+ evtimer_set(&ev, server_client_redraw_timer, NULL);
+ if (!evtimer_pending(&ev, NULL)) {
+ log_debug("redraw timer started");
+ evtimer_add(&ev, &tv);
+ }
+
+ if (~c->flags & CLIENT_REDRAWWINDOW) {
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp->flags & PANE_REDRAW) {
+ log_debug("%s: pane %%%u needs redraw",
+ c->name, wp->id);
+ c->redraw_panes |= (1 << bit);
+ }
+ if (++bit == 64) {
+ /*
+ * If more that 64 panes, give up and
+ * just redraw the window.
+ */
+ new_flags &= CLIENT_REDRAWPANES;
+ new_flags |= CLIENT_REDRAWWINDOW;
+ break;
+ }
+ }
+ if (c->redraw_panes != 0)
+ c->flags |= CLIENT_REDRAWPANES;
+ }
+ c->flags |= new_flags;
+ return;
+ } else if (needed)
+ log_debug("%s: redraw needed", c->name);
+
+ flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR);
+ tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE))|TTY_NOCURSOR;
+
+ if (~c->flags & CLIENT_REDRAWWINDOW) {
+ /*
+ * If not redrawing the entire window, check whether each pane
+ * needs to be redrawn.
+ */
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ redraw = 0;
+ if (wp->flags & PANE_REDRAW)
+ redraw = 1;
+ else if (c->flags & CLIENT_REDRAWPANES)
+ redraw = !!(c->redraw_panes & (1 << bit));
+ bit++;
+ if (!redraw)
+ continue;
+ log_debug("%s: redrawing pane %%%u", __func__, wp->id);
+ screen_redraw_pane(c, wp);
+ }
+ c->redraw_panes = 0;
+ c->flags &= ~CLIENT_REDRAWPANES;
+ }
+
+ if (c->flags & CLIENT_ALLREDRAWFLAGS) {
+ if (options_get_number(s->options, "set-titles")) {
+ server_client_set_title(c);
+ server_client_set_path(c);
+ }
+ screen_redraw_screen(c);
+ }
+
+ tty->flags = (tty->flags & ~TTY_NOCURSOR)|(flags & TTY_NOCURSOR);
+ tty_update_mode(tty, mode, NULL);
+ tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR))|flags;
+
+ c->flags &= ~(CLIENT_ALLREDRAWFLAGS|CLIENT_STATUSFORCE);
+
+ if (needed) {
+ /*
+ * We would have deferred the redraw unless the output buffer
+ * was empty, so we can record how many bytes the redraw
+ * generated.
+ */
+ c->redraw = EVBUFFER_LENGTH(tty->out);
+ log_debug("%s: redraw added %zu bytes", c->name, c->redraw);
+ }
+}
+
+/* Set client title. */
+static void
+server_client_set_title(struct client *c)
+{
+ struct session *s = c->session;
+ const char *template;
+ char *title;
+ struct format_tree *ft;
+
+ template = options_get_string(s->options, "set-titles-string");
+
+ ft = format_create(c, NULL, FORMAT_NONE, 0);
+ format_defaults(ft, c, NULL, NULL, NULL);
+
+ title = format_expand_time(ft, template);
+ if (c->title == NULL || strcmp(title, c->title) != 0) {
+ free(c->title);
+ c->title = xstrdup(title);
+ tty_set_title(&c->tty, c->title);
+ }
+ free(title);
+
+ format_free(ft);
+}
+
+/* Set client path. */
+static void
+server_client_set_path(struct client *c)
+{
+ struct session *s = c->session;
+ const char *path;
+
+ if (s->curw == NULL)
+ return;
+ if (s->curw->window->active->base.path == NULL)
+ path = "";
+ else
+ path = s->curw->window->active->base.path;
+ if (c->path == NULL || strcmp(path, c->path) != 0) {
+ free(c->path);
+ c->path = xstrdup(path);
+ tty_set_path(&c->tty, c->path);
+ }
+}
+
+/* Dispatch message from client. */
+static void
+server_client_dispatch(struct imsg *imsg, void *arg)
+{
+ struct client *c = arg;
+ ssize_t datalen;
+ struct session *s;
+
+ if (c->flags & CLIENT_DEAD)
+ return;
+
+ if (imsg == NULL) {
+ server_client_lost(c);
+ return;
+ }
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+
+ switch (imsg->hdr.type) {
+ case MSG_IDENTIFY_CLIENTPID:
+ case MSG_IDENTIFY_CWD:
+ case MSG_IDENTIFY_ENVIRON:
+ case MSG_IDENTIFY_FEATURES:
+ case MSG_IDENTIFY_FLAGS:
+ case MSG_IDENTIFY_LONGFLAGS:
+ case MSG_IDENTIFY_STDIN:
+ case MSG_IDENTIFY_STDOUT:
+ case MSG_IDENTIFY_TERM:
+ case MSG_IDENTIFY_TERMINFO:
+ case MSG_IDENTIFY_TTYNAME:
+ case MSG_IDENTIFY_DONE:
+ server_client_dispatch_identify(c, imsg);
+ break;
+ case MSG_COMMAND:
+ server_client_dispatch_command(c, imsg);
+ break;
+ case MSG_RESIZE:
+ if (datalen != 0)
+ fatalx("bad MSG_RESIZE size");
+
+ if (c->flags & CLIENT_CONTROL)
+ break;
+ server_client_update_latest(c);
+ tty_resize(&c->tty);
+ recalculate_sizes();
+ if (c->overlay_resize == NULL)
+ server_client_clear_overlay(c);
+ else
+ c->overlay_resize(c, c->overlay_data);
+ server_redraw_client(c);
+ if (c->session != NULL)
+ notify_client("client-resized", c);
+ break;
+ case MSG_EXITING:
+ if (datalen != 0)
+ fatalx("bad MSG_EXITING size");
+ server_client_set_session(c, NULL);
+ recalculate_sizes();
+ tty_close(&c->tty);
+ proc_send(c->peer, MSG_EXITED, -1, NULL, 0);
+ break;
+ case MSG_WAKEUP:
+ case MSG_UNLOCK:
+ if (datalen != 0)
+ fatalx("bad MSG_WAKEUP size");
+
+ if (!(c->flags & CLIENT_SUSPENDED))
+ break;
+ c->flags &= ~CLIENT_SUSPENDED;
+
+ if (c->fd == -1 || c->session == NULL) /* exited already */
+ break;
+ s = c->session;
+
+ if (gettimeofday(&c->activity_time, NULL) != 0)
+ fatal("gettimeofday failed");
+
+ tty_start_tty(&c->tty);
+ server_redraw_client(c);
+ recalculate_sizes();
+
+ if (s != NULL)
+ session_update_activity(s, &c->activity_time);
+ break;
+ case MSG_SHELL:
+ if (datalen != 0)
+ fatalx("bad MSG_SHELL size");
+
+ server_client_dispatch_shell(c);
+ break;
+ case MSG_WRITE_READY:
+ file_write_ready(&c->files, imsg);
+ break;
+ case MSG_READ:
+ file_read_data(&c->files, imsg);
+ break;
+ case MSG_READ_DONE:
+ file_read_done(&c->files, imsg);
+ break;
+ }
+}
+
+/* Callback when command is not allowed. */
+static enum cmd_retval
+server_client_read_only(struct cmdq_item *item, __unused void *data)
+{
+ cmdq_error(item, "client is read-only");
+ return (CMD_RETURN_ERROR);
+}
+
+/* Callback when command is done. */
+static enum cmd_retval
+server_client_command_done(struct cmdq_item *item, __unused void *data)
+{
+ struct client *c = cmdq_get_client(item);
+
+ if (~c->flags & CLIENT_ATTACHED)
+ c->flags |= CLIENT_EXIT;
+ else if (~c->flags & CLIENT_EXIT)
+ tty_send_requests(&c->tty);
+ return (CMD_RETURN_NORMAL);
+}
+
+/* Handle command message. */
+static void
+server_client_dispatch_command(struct client *c, struct imsg *imsg)
+{
+ struct msg_command data;
+ char *buf;
+ size_t len;
+ int argc;
+ char **argv, *cause;
+ struct cmd_parse_result *pr;
+ struct args_value *values;
+ struct cmdq_item *new_item;
+
+ if (c->flags & CLIENT_EXIT)
+ return;
+
+ if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data)
+ fatalx("bad MSG_COMMAND size");
+ memcpy(&data, imsg->data, sizeof data);
+
+ buf = (char *)imsg->data + sizeof data;
+ len = imsg->hdr.len - IMSG_HEADER_SIZE - sizeof data;
+ if (len > 0 && buf[len - 1] != '\0')
+ fatalx("bad MSG_COMMAND string");
+
+ argc = data.argc;
+ if (cmd_unpack_argv(buf, len, argc, &argv) != 0) {
+ cause = xstrdup("command too long");
+ goto error;
+ }
+
+ if (argc == 0) {
+ argc = 1;
+ argv = xcalloc(1, sizeof *argv);
+ *argv = xstrdup("new-session");
+ }
+
+ values = args_from_vector(argc, argv);
+ pr = cmd_parse_from_arguments(values, argc, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ cause = pr->error;
+ goto error;
+ case CMD_PARSE_SUCCESS:
+ break;
+ }
+ args_free_values(values, argc);
+ free(values);
+ cmd_free_argv(argc, argv);
+
+ if ((c->flags & CLIENT_READONLY) &&
+ !cmd_list_all_have(pr->cmdlist, CMD_READONLY))
+ new_item = cmdq_get_callback(server_client_read_only, NULL);
+ else
+ new_item = cmdq_get_command(pr->cmdlist, NULL);
+ cmdq_append(c, new_item);
+ cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL));
+
+ cmd_list_free(pr->cmdlist);
+ return;
+
+error:
+ cmd_free_argv(argc, argv);
+
+ cmdq_append(c, cmdq_get_error(cause));
+ free(cause);
+
+ c->flags |= CLIENT_EXIT;
+}
+
+/* Handle identify message. */
+static void
+server_client_dispatch_identify(struct client *c, struct imsg *imsg)
+{
+ const char *data, *home;
+ size_t datalen;
+ int flags, feat;
+ uint64_t longflags;
+ char *name;
+
+ if (c->flags & CLIENT_IDENTIFIED)
+ fatalx("out-of-order identify message");
+
+ data = imsg->data;
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+
+ switch (imsg->hdr.type) {
+ case MSG_IDENTIFY_FEATURES:
+ if (datalen != sizeof feat)
+ fatalx("bad MSG_IDENTIFY_FEATURES size");
+ memcpy(&feat, data, sizeof feat);
+ c->term_features |= feat;
+ log_debug("client %p IDENTIFY_FEATURES %s", c,
+ tty_get_features(feat));
+ break;
+ case MSG_IDENTIFY_FLAGS:
+ if (datalen != sizeof flags)
+ fatalx("bad MSG_IDENTIFY_FLAGS size");
+ memcpy(&flags, data, sizeof flags);
+ c->flags |= flags;
+ log_debug("client %p IDENTIFY_FLAGS %#x", c, flags);
+ break;
+ case MSG_IDENTIFY_LONGFLAGS:
+ if (datalen != sizeof longflags)
+ fatalx("bad MSG_IDENTIFY_LONGFLAGS size");
+ memcpy(&longflags, data, sizeof longflags);
+ c->flags |= longflags;
+ log_debug("client %p IDENTIFY_LONGFLAGS %#llx", c,
+ (unsigned long long)longflags);
+ break;
+ case MSG_IDENTIFY_TERM:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_IDENTIFY_TERM string");
+ if (*data == '\0')
+ c->term_name = xstrdup("unknown");
+ else
+ c->term_name = xstrdup(data);
+ log_debug("client %p IDENTIFY_TERM %s", c, data);
+ break;
+ case MSG_IDENTIFY_TERMINFO:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_IDENTIFY_TERMINFO string");
+ c->term_caps = xreallocarray(c->term_caps, c->term_ncaps + 1,
+ sizeof *c->term_caps);
+ c->term_caps[c->term_ncaps++] = xstrdup(data);
+ log_debug("client %p IDENTIFY_TERMINFO %s", c, data);
+ break;
+ case MSG_IDENTIFY_TTYNAME:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_IDENTIFY_TTYNAME string");
+ c->ttyname = xstrdup(data);
+ log_debug("client %p IDENTIFY_TTYNAME %s", c, data);
+ break;
+ case MSG_IDENTIFY_CWD:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_IDENTIFY_CWD string");
+ if (access(data, X_OK) == 0)
+ c->cwd = xstrdup(data);
+ else if ((home = find_home()) != NULL)
+ c->cwd = xstrdup(home);
+ else
+ c->cwd = xstrdup("/");
+ log_debug("client %p IDENTIFY_CWD %s", c, data);
+ break;
+ 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);
+ 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);
+ break;
+ case MSG_IDENTIFY_ENVIRON:
+ if (datalen == 0 || data[datalen - 1] != '\0')
+ fatalx("bad MSG_IDENTIFY_ENVIRON string");
+ if (strchr(data, '=') != NULL)
+ environ_put(c->environ, data, 0);
+ log_debug("client %p IDENTIFY_ENVIRON %s", c, data);
+ break;
+ case MSG_IDENTIFY_CLIENTPID:
+ if (datalen != sizeof c->pid)
+ fatalx("bad MSG_IDENTIFY_CLIENTPID size");
+ memcpy(&c->pid, data, sizeof c->pid);
+ log_debug("client %p IDENTIFY_CLIENTPID %ld", c, (long)c->pid);
+ break;
+ default:
+ break;
+ }
+
+ if (imsg->hdr.type != MSG_IDENTIFY_DONE)
+ return;
+ c->flags |= CLIENT_IDENTIFIED;
+
+ if (*c->ttyname != '\0')
+ name = xstrdup(c->ttyname);
+ else
+ xasprintf(&name, "client-%ld", (long)c->pid);
+ c->name = name;
+ log_debug("client %p name is %s", c, c->name);
+
+#ifdef __CYGWIN__
+ c->fd = open(c->ttyname, O_RDWR|O_NOCTTY);
+#endif
+
+ if (c->flags & CLIENT_CONTROL)
+ control_start(c);
+ else if (c->fd != -1) {
+ if (tty_init(&c->tty, c) != 0) {
+ close(c->fd);
+ c->fd = -1;
+ } else {
+ tty_resize(&c->tty);
+ c->flags |= CLIENT_TERMINAL;
+ }
+ close(c->out_fd);
+ c->out_fd = -1;
+ }
+
+ /*
+ * If this is the first client, load configuration files. Any later
+ * clients are allowed to continue with their command even if the
+ * config has not been loaded - they might have been run from inside it
+ */
+ if ((~c->flags & CLIENT_EXIT) &&
+ !cfg_finished &&
+ c == TAILQ_FIRST(&clients))
+ start_cfg();
+}
+
+/* Handle shell message. */
+static void
+server_client_dispatch_shell(struct client *c)
+{
+ const char *shell;
+
+ shell = options_get_string(global_s_options, "default-shell");
+ if (!checkshell(shell))
+ shell = _PATH_BSHELL;
+ proc_send(c->peer, MSG_SHELL, -1, shell, strlen(shell) + 1);
+
+ proc_kill_peer(c->peer);
+}
+
+/* Get client working directory. */
+const char *
+server_client_get_cwd(struct client *c, struct session *s)
+{
+ const char *home;
+
+ if (!cfg_finished && cfg_client != NULL)
+ return (cfg_client->cwd);
+ if (c != NULL && c->session == NULL && c->cwd != NULL)
+ return (c->cwd);
+ if (s != NULL && s->cwd != NULL)
+ return (s->cwd);
+ if (c != NULL && (s = c->session) != NULL && s->cwd != NULL)
+ return (s->cwd);
+ if ((home = find_home()) != NULL)
+ return (home);
+ return ("/");
+}
+
+/* Get control client flags. */
+static uint64_t
+server_client_control_flags(struct client *c, const char *next)
+{
+ if (strcmp(next, "pause-after") == 0) {
+ c->pause_age = 0;
+ return (CLIENT_CONTROL_PAUSEAFTER);
+ }
+ if (sscanf(next, "pause-after=%u", &c->pause_age) == 1) {
+ c->pause_age *= 1000;
+ return (CLIENT_CONTROL_PAUSEAFTER);
+ }
+ if (strcmp(next, "no-output") == 0)
+ return (CLIENT_CONTROL_NOOUTPUT);
+ if (strcmp(next, "wait-exit") == 0)
+ return (CLIENT_CONTROL_WAITEXIT);
+ return (0);
+}
+
+/* Set client flags. */
+void
+server_client_set_flags(struct client *c, const char *flags)
+{
+ char *s, *copy, *next;
+ uint64_t flag;
+ int not;
+
+ s = copy = xstrdup(flags);
+ while ((next = strsep(&s, ",")) != NULL) {
+ not = (*next == '!');
+ if (not)
+ next++;
+
+ if (c->flags & CLIENT_CONTROL)
+ flag = server_client_control_flags(c, next);
+ else
+ flag = 0;
+ if (strcmp(next, "read-only") == 0)
+ flag = CLIENT_READONLY;
+ else if (strcmp(next, "ignore-size") == 0)
+ flag = CLIENT_IGNORESIZE;
+ else if (strcmp(next, "active-pane") == 0)
+ flag = CLIENT_ACTIVEPANE;
+ if (flag == 0)
+ continue;
+
+ log_debug("client %s set flag %s", c->name, next);
+ if (not) {
+ if (c->flags & CLIENT_READONLY)
+ flag &= ~CLIENT_READONLY;
+ c->flags &= ~flag;
+ } else
+ c->flags |= flag;
+ if (flag == CLIENT_CONTROL_NOOUTPUT)
+ control_reset_offsets(c);
+ }
+ free(copy);
+ proc_send(c->peer, MSG_FLAGS, -1, &c->flags, sizeof c->flags);
+}
+
+/* Get client flags. This is only flags useful to show to users. */
+const char *
+server_client_get_flags(struct client *c)
+{
+ static char s[256];
+ char tmp[32];
+
+ *s = '\0';
+ if (c->flags & CLIENT_ATTACHED)
+ strlcat(s, "attached,", sizeof s);
+ if (c->flags & CLIENT_FOCUSED)
+ strlcat(s, "focused,", sizeof s);
+ if (c->flags & CLIENT_CONTROL)
+ strlcat(s, "control-mode,", sizeof s);
+ if (c->flags & CLIENT_IGNORESIZE)
+ strlcat(s, "ignore-size,", sizeof s);
+ if (c->flags & CLIENT_CONTROL_NOOUTPUT)
+ strlcat(s, "no-output,", sizeof s);
+ if (c->flags & CLIENT_CONTROL_WAITEXIT)
+ strlcat(s, "wait-exit,", sizeof s);
+ if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
+ xsnprintf(tmp, sizeof tmp, "pause-after=%u,",
+ c->pause_age / 1000);
+ strlcat(s, tmp, sizeof s);
+ }
+ if (c->flags & CLIENT_READONLY)
+ strlcat(s, "read-only,", sizeof s);
+ if (c->flags & CLIENT_ACTIVEPANE)
+ strlcat(s, "active-pane,", sizeof s);
+ if (c->flags & CLIENT_SUSPENDED)
+ strlcat(s, "suspended,", sizeof s);
+ if (c->flags & CLIENT_UTF8)
+ strlcat(s, "UTF-8,", sizeof s);
+ if (*s != '\0')
+ s[strlen(s) - 1] = '\0';
+ return (s);
+}
+
+/* Get client window. */
+struct client_window *
+server_client_get_client_window(struct client *c, u_int id)
+{
+ struct client_window cw = { .window = id };
+
+ return (RB_FIND(client_windows, &c->windows, &cw));
+}
+
+/* Add client window. */
+struct client_window *
+server_client_add_client_window(struct client *c, u_int id)
+{
+ struct client_window *cw;
+
+ cw = server_client_get_client_window(c, id);
+ if (cw == NULL) {
+ cw = xcalloc(1, sizeof *cw);
+ cw->window = id;
+ RB_INSERT(client_windows, &c->windows, cw);
+ }
+ return (cw);
+}
+
+/* Get client active pane. */
+struct window_pane *
+server_client_get_pane(struct client *c)
+{
+ struct session *s = c->session;
+ struct client_window *cw;
+
+ if (s == NULL)
+ return (NULL);
+
+ if (~c->flags & CLIENT_ACTIVEPANE)
+ return (s->curw->window->active);
+ cw = server_client_get_client_window(c, s->curw->window->id);
+ if (cw == NULL)
+ return (s->curw->window->active);
+ return (cw->pane);
+}
+
+/* Set client active pane. */
+void
+server_client_set_pane(struct client *c, struct window_pane *wp)
+{
+ struct session *s = c->session;
+ struct client_window *cw;
+
+ if (s == NULL)
+ return;
+
+ cw = server_client_add_client_window(c, s->curw->window->id);
+ cw->pane = wp;
+ log_debug("%s pane now %%%u", c->name, wp->id);
+}
+
+/* Remove pane from client lists. */
+void
+server_client_remove_pane(struct window_pane *wp)
+{
+ struct client *c;
+ struct window *w = wp->window;
+ struct client_window *cw;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ cw = server_client_get_client_window(c, w->id);
+ if (cw != NULL && cw->pane == wp) {
+ RB_REMOVE(client_windows, &c->windows, cw);
+ free(cw);
+ }
+ }
+}
diff --git a/server-fn.c b/server-fn.c
new file mode 100644
index 0000000..2a79f3e
--- /dev/null
+++ b/server-fn.c
@@ -0,0 +1,474 @@
+/* $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 <sys/wait.h>
+#include <sys/uio.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static struct session *server_next_session(struct session *);
+static void server_destroy_session_group(struct session *);
+
+void
+server_redraw_client(struct client *c)
+{
+ c->flags |= CLIENT_ALLREDRAWFLAGS;
+}
+
+void
+server_status_client(struct client *c)
+{
+ c->flags |= CLIENT_REDRAWSTATUS;
+}
+
+void
+server_redraw_session(struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == s)
+ server_redraw_client(c);
+ }
+}
+
+void
+server_redraw_session_group(struct session *s)
+{
+ struct session_group *sg;
+
+ if ((sg = session_group_contains(s)) == NULL)
+ server_redraw_session(s);
+ else {
+ TAILQ_FOREACH(s, &sg->sessions, gentry)
+ server_redraw_session(s);
+ }
+}
+
+void
+server_status_session(struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == s)
+ server_status_client(c);
+ }
+}
+
+void
+server_status_session_group(struct session *s)
+{
+ struct session_group *sg;
+
+ if ((sg = session_group_contains(s)) == NULL)
+ server_status_session(s);
+ else {
+ TAILQ_FOREACH(s, &sg->sessions, gentry)
+ server_status_session(s);
+ }
+}
+
+void
+server_redraw_window(struct window *w)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL && c->session->curw->window == w)
+ server_redraw_client(c);
+ }
+}
+
+void
+server_redraw_window_borders(struct window *w)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL && c->session->curw->window == w)
+ c->flags |= CLIENT_REDRAWBORDERS;
+ }
+}
+
+void
+server_status_window(struct window *w)
+{
+ struct session *s;
+
+ /*
+ * This is slightly different. We want to redraw the status line of any
+ * clients containing this window rather than anywhere it is the
+ * current window.
+ */
+
+ RB_FOREACH(s, sessions, &sessions) {
+ if (session_has(s, w))
+ server_status_session(s);
+ }
+}
+
+void
+server_lock(void)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL)
+ server_lock_client(c);
+ }
+}
+
+void
+server_lock_session(struct session *s)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == s)
+ server_lock_client(c);
+ }
+}
+
+void
+server_lock_client(struct client *c)
+{
+ const char *cmd;
+
+ if (c->flags & CLIENT_CONTROL)
+ return;
+
+ if (c->flags & CLIENT_SUSPENDED)
+ return;
+
+ cmd = options_get_string(c->session->options, "lock-command");
+ if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
+ return;
+
+ tty_stop_tty(&c->tty);
+ tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP));
+ tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR));
+ tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3));
+
+ c->flags |= CLIENT_SUSPENDED;
+ proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1);
+}
+
+void
+server_kill_pane(struct window_pane *wp)
+{
+ struct window *w = wp->window;
+
+ if (window_count_panes(w) == 1) {
+ server_kill_window(w, 1);
+ recalculate_sizes();
+ } else {
+ server_unzoom_window(w);
+ server_client_remove_pane(wp);
+ layout_close_pane(wp);
+ window_remove_pane(w, wp);
+ server_redraw_window(w);
+ }
+}
+
+void
+server_kill_window(struct window *w, int renumber)
+{
+ struct session *s, *s1;
+ struct winlink *wl;
+
+ RB_FOREACH_SAFE(s, sessions, &sessions, s1) {
+ if (!session_has(s, w))
+ continue;
+
+ server_unzoom_window(w);
+ while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
+ if (session_detach(s, wl)) {
+ server_destroy_session_group(s);
+ break;
+ } else
+ server_redraw_session_group(s);
+ }
+
+ if (renumber)
+ server_renumber_session(s);
+ }
+ recalculate_sizes();
+}
+
+void
+server_renumber_session(struct session *s)
+{
+ struct session_group *sg;
+
+ if (options_get_number(s->options, "renumber-windows")) {
+ if ((sg = session_group_contains(s)) != NULL) {
+ TAILQ_FOREACH(s, &sg->sessions, gentry)
+ session_renumber_windows(s);
+ } else
+ session_renumber_windows(s);
+ }
+}
+
+void
+server_renumber_all(void)
+{
+ struct session *s;
+
+ RB_FOREACH(s, sessions, &sessions)
+ server_renumber_session(s);
+}
+
+int
+server_link_window(struct session *src, struct winlink *srcwl,
+ struct session *dst, int dstidx, int killflag, int selectflag,
+ char **cause)
+{
+ struct winlink *dstwl;
+ struct session_group *srcsg, *dstsg;
+
+ srcsg = session_group_contains(src);
+ dstsg = session_group_contains(dst);
+ if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
+ xasprintf(cause, "sessions are grouped");
+ return (-1);
+ }
+
+ dstwl = NULL;
+ if (dstidx != -1)
+ dstwl = winlink_find_by_index(&dst->windows, dstidx);
+ if (dstwl != NULL) {
+ if (dstwl->window == srcwl->window) {
+ xasprintf(cause, "same index: %d", dstidx);
+ return (-1);
+ }
+ if (killflag) {
+ /*
+ * Can't use session_detach as it will destroy session
+ * if this makes it empty.
+ */
+ notify_session_window("window-unlinked", dst,
+ dstwl->window);
+ dstwl->flags &= ~WINLINK_ALERTFLAGS;
+ winlink_stack_remove(&dst->lastw, dstwl);
+ winlink_remove(&dst->windows, dstwl);
+
+ /* Force select/redraw if current. */
+ if (dstwl == dst->curw) {
+ selectflag = 1;
+ dst->curw = NULL;
+ }
+ }
+ }
+
+ if (dstidx == -1)
+ dstidx = -1 - options_get_number(dst->options, "base-index");
+ dstwl = session_attach(dst, srcwl->window, dstidx, cause);
+ if (dstwl == NULL)
+ return (-1);
+
+ if (selectflag)
+ session_select(dst, dstwl->idx);
+ server_redraw_session_group(dst);
+
+ return (0);
+}
+
+void
+server_unlink_window(struct session *s, struct winlink *wl)
+{
+ if (session_detach(s, wl))
+ server_destroy_session_group(s);
+ else
+ server_redraw_session_group(s);
+}
+
+void
+server_destroy_pane(struct window_pane *wp, int notify)
+{
+ struct window *w = wp->window;
+ struct screen_write_ctx ctx;
+ struct grid_cell gc;
+ int remain_on_exit;
+ const char *s;
+ char *expanded;
+ u_int sx = screen_size_x(&wp->base);
+ u_int sy = screen_size_y(&wp->base);
+
+ if (wp->fd != -1) {
+#ifdef HAVE_UTEMPTER
+ utempter_remove_record(wp->fd);
+#endif
+ bufferevent_free(wp->event);
+ wp->event = NULL;
+ close(wp->fd);
+ wp->fd = -1;
+ }
+
+ remain_on_exit = options_get_number(wp->options, "remain-on-exit");
+ if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
+ return;
+ switch (remain_on_exit) {
+ case 0:
+ break;
+ case 2:
+ if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
+ break;
+ /* FALLTHROUGH */
+ case 1:
+ if (wp->flags & PANE_STATUSDRAWN)
+ return;
+ wp->flags |= PANE_STATUSDRAWN;
+
+ gettimeofday(&wp->dead_time, NULL);
+ if (notify)
+ notify_pane("pane-died", wp);
+
+ s = options_get_string(wp->options, "remain-on-exit-format");
+ if (*s != '\0') {
+ screen_write_start_pane(&ctx, wp, &wp->base);
+ screen_write_scrollregion(&ctx, 0, sy - 1);
+ screen_write_cursormove(&ctx, 0, sy - 1, 0);
+ screen_write_linefeed(&ctx, 1, 8);
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+
+ expanded = format_single(NULL, s, NULL, NULL, NULL, wp);
+ format_draw(&ctx, &gc, sx, expanded, NULL, 0);
+ free(expanded);
+
+ screen_write_stop(&ctx);
+ }
+ wp->base.mode &= ~MODE_CURSOR;
+
+ wp->flags |= PANE_REDRAW;
+ return;
+ }
+
+ if (notify)
+ notify_pane("pane-exited", wp);
+
+ server_unzoom_window(w);
+ server_client_remove_pane(wp);
+ layout_close_pane(wp);
+ window_remove_pane(w, wp);
+
+ if (TAILQ_EMPTY(&w->panes))
+ server_kill_window(w, 1);
+ else
+ server_redraw_window(w);
+}
+
+static void
+server_destroy_session_group(struct session *s)
+{
+ struct session_group *sg;
+ struct session *s1;
+
+ if ((sg = session_group_contains(s)) == NULL)
+ server_destroy_session(s);
+ else {
+ TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
+ server_destroy_session(s);
+ session_destroy(s, 1, __func__);
+ }
+ }
+}
+
+static struct session *
+server_next_session(struct session *s)
+{
+ 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, <))
+ s_out = s_loop;
+ }
+ return (s_out);
+}
+
+static struct session *
+server_next_detached_session(struct session *s)
+{
+ struct session *s_loop, *s_out = NULL;
+
+ 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);
+}
+
+void
+server_destroy_session(struct session *s)
+{
+ struct client *c;
+ struct session *s_new;
+ 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);
+ else if (detach_on_destroy == 2)
+ s_new = server_next_detached_session(s);
+ else
+ s_new = NULL;
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != s)
+ continue;
+ server_client_set_session(c, s_new);
+ if (s_new == NULL)
+ c->flags |= CLIENT_EXIT;
+ }
+ recalculate_sizes();
+}
+
+void
+server_check_unattached(void)
+{
+ struct session *s;
+
+ /*
+ * If any sessions are no longer attached and have destroy-unattached
+ * set, collect them.
+ */
+ RB_FOREACH(s, sessions, &sessions) {
+ if (s->attached != 0)
+ continue;
+ if (options_get_number (s->options, "destroy-unattached"))
+ session_destroy(s, 1, __func__);
+ }
+}
+
+void
+server_unzoom_window(struct window *w)
+{
+ if (window_unzoom(w) == 0)
+ server_redraw_window(w);
+}
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..05bc50f
--- /dev/null
+++ b/server.c
@@ -0,0 +1,554 @@
+/* $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 <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Main server functions.
+ */
+
+struct clients clients;
+
+struct tmuxproc *server_proc;
+static int server_fd = -1;
+static uint64_t server_client_flags;
+static int server_exit;
+static struct event server_ev_accept;
+static struct event server_ev_tidy;
+
+struct cmd_find_state marked_pane;
+
+static u_int message_next;
+struct message_list message_log;
+
+static int server_loop(void);
+static void server_send_exit(void);
+static void server_accept(int, short, void *);
+static void server_signal(int);
+static void server_child_signal(void);
+static void server_child_exited(pid_t, int);
+static void server_child_stopped(pid_t, int);
+
+/* Set marked pane. */
+void
+server_set_marked(struct session *s, struct winlink *wl, struct window_pane *wp)
+{
+ cmd_find_clear_state(&marked_pane, 0);
+ marked_pane.s = s;
+ marked_pane.wl = wl;
+ marked_pane.w = wl->window;
+ marked_pane.wp = wp;
+}
+
+/* Clear marked pane. */
+void
+server_clear_marked(void)
+{
+ cmd_find_clear_state(&marked_pane, 0);
+}
+
+/* Is this the marked pane? */
+int
+server_is_marked(struct session *s, struct winlink *wl, struct window_pane *wp)
+{
+ if (s == NULL || wl == NULL || wp == NULL)
+ return (0);
+ if (marked_pane.s != s || marked_pane.wl != wl)
+ return (0);
+ if (marked_pane.wp != wp)
+ return (0);
+ return (server_check_marked());
+}
+
+/* Check if the marked pane is still valid. */
+int
+server_check_marked(void)
+{
+ return (cmd_find_valid_state(&marked_pane));
+}
+
+/* Create server socket. */
+int
+server_create_socket(int flags, char **cause)
+{
+ struct sockaddr_un sa;
+ size_t size;
+ mode_t mask;
+ int fd, saved_errno;
+
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ size = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
+ if (size >= sizeof sa.sun_path) {
+ errno = ENAMETOOLONG;
+ goto fail;
+ }
+ unlink(sa.sun_path);
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ goto fail;
+
+ if (flags & CLIENT_DEFAULTSOCKET)
+ mask = umask(S_IXUSR|S_IXGRP|S_IRWXO);
+ else
+ mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
+ saved_errno = errno;
+ close(fd);
+ errno = saved_errno;
+ goto fail;
+ }
+ umask(mask);
+
+ if (listen(fd, 128) == -1) {
+ saved_errno = errno;
+ close(fd);
+ errno = saved_errno;
+ goto fail;
+ }
+ setblocking(fd, 0);
+
+ return (fd);
+
+fail:
+ if (cause != NULL) {
+ xasprintf(cause, "error creating %s (%s)", socket_path,
+ strerror(errno));
+ }
+ return (-1);
+}
+
+/* Tidy up every hour. */
+static void
+server_tidy_event(__unused int fd, __unused short events, __unused void *data)
+{
+ struct timeval tv = { .tv_sec = 3600 };
+ uint64_t t = get_timer();
+
+ format_tidy_jobs();
+
+#ifdef HAVE_MALLOC_TRIM
+ malloc_trim(0);
+#endif
+
+ log_debug("%s: took %llu milliseconds", __func__,
+ (unsigned long long)(get_timer() - t));
+ evtimer_add(&server_ev_tidy, &tv);
+}
+
+/* Fork new server. */
+int
+server_start(struct tmuxproc *client, int flags, struct event_base *base,
+ int lockfd, char *lockfile)
+{
+ int fd;
+ sigset_t set, oldset;
+ struct client *c = NULL;
+ char *cause = NULL;
+ struct timeval tv = { .tv_sec = 3600 };
+
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oldset);
+
+ if (~flags & CLIENT_NOFORK) {
+ if (proc_fork_and_daemon(&fd) != 0) {
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ return (fd);
+ }
+ }
+ proc_clear_signals(client, 0);
+ server_client_flags = flags;
+
+ if (event_reinit(base) != 0)
+ fatalx("event_reinit failed");
+ server_proc = proc_start("server");
+
+ proc_set_signals(server_proc, server_signal);
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+
+ if (log_get_level() > 1)
+ tty_create_log();
+ if (pledge("stdio rpath wpath cpath fattr unix getpw recvfd proc exec "
+ "tty ps", NULL) != 0)
+ fatal("pledge failed");
+
+ input_key_build();
+ RB_INIT(&windows);
+ RB_INIT(&all_window_panes);
+ TAILQ_INIT(&clients);
+ RB_INIT(&sessions);
+ key_bindings_init();
+ TAILQ_INIT(&message_log);
+
+ gettimeofday(&start_time, NULL);
+
+#ifdef HAVE_SYSTEMD
+ server_fd = systemd_create_socket(flags, &cause);
+#else
+ server_fd = server_create_socket(flags, &cause);
+#endif
+ if (server_fd != -1)
+ server_update_socket();
+ if (~flags & CLIENT_NOFORK)
+ c = server_client_create(fd);
+ else
+ options_set_number(global_options, "exit-empty", 0);
+
+ if (lockfd >= 0) {
+ unlink(lockfile);
+ free(lockfile);
+ close(lockfd);
+ }
+
+ if (cause != NULL) {
+ if (c != NULL) {
+ c->exit_message = cause;
+ c->flags |= CLIENT_EXIT;
+ } else {
+ fprintf(stderr, "%s\n", cause);
+ exit(1);
+ }
+ }
+
+ evtimer_set(&server_ev_tidy, server_tidy_event, NULL);
+ evtimer_add(&server_ev_tidy, &tv);
+
+ server_acl_init();
+
+ server_add_accept(0);
+ proc_loop(server_proc, server_loop);
+
+ job_kill_all();
+ status_prompt_save_history();
+
+ exit(0);
+}
+
+/* Server loop callback. */
+static int
+server_loop(void)
+{
+ struct client *c;
+ u_int items;
+
+ do {
+ items = cmdq_next(NULL);
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->flags & CLIENT_IDENTIFIED)
+ items += cmdq_next(c);
+ }
+ } while (items != 0);
+
+ server_client_loop();
+
+ if (!options_get_number(global_options, "exit-empty") && !server_exit)
+ return (0);
+
+ if (!options_get_number(global_options, "exit-unattached")) {
+ if (!RB_EMPTY(&sessions))
+ return (0);
+ }
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL)
+ return (0);
+ }
+
+ /*
+ * No attached clients therefore want to exit - flush any waiting
+ * clients but don't actually exit until they've gone.
+ */
+ cmd_wait_for_flush();
+ if (!TAILQ_EMPTY(&clients))
+ return (0);
+
+ if (job_still_running())
+ return (0);
+
+ return (1);
+}
+
+/* Exit the server by killing all clients and windows. */
+static void
+server_send_exit(void)
+{
+ struct client *c, *c1;
+ struct session *s, *s1;
+
+ cmd_wait_for_flush();
+
+ TAILQ_FOREACH_SAFE(c, &clients, entry, c1) {
+ if (c->flags & CLIENT_SUSPENDED)
+ server_client_lost(c);
+ else {
+ c->flags |= CLIENT_EXIT;
+ c->exit_type = CLIENT_EXIT_SHUTDOWN;
+ }
+ c->session = NULL;
+ }
+
+ RB_FOREACH_SAFE(s, sessions, &sessions, s1)
+ session_destroy(s, 1, __func__);
+}
+
+/* Update socket execute permissions based on whether sessions are attached. */
+void
+server_update_socket(void)
+{
+ struct session *s;
+ static int last = -1;
+ int n, mode;
+ struct stat sb;
+
+ n = 0;
+ RB_FOREACH(s, sessions, &sessions) {
+ if (s->attached != 0) {
+ n++;
+ break;
+ }
+ }
+
+ if (n != last) {
+ last = n;
+
+ if (stat(socket_path, &sb) != 0)
+ return;
+ mode = sb.st_mode & ACCESSPERMS;
+ if (n != 0) {
+ if (mode & S_IRUSR)
+ mode |= S_IXUSR;
+ if (mode & S_IRGRP)
+ mode |= S_IXGRP;
+ if (mode & S_IROTH)
+ mode |= S_IXOTH;
+ } else
+ mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH);
+ chmod(socket_path, mode);
+ }
+}
+
+/* Callback for server socket. */
+static void
+server_accept(int fd, short events, __unused void *data)
+{
+ struct sockaddr_storage sa;
+ socklen_t slen = sizeof sa;
+ int newfd;
+ struct client *c;
+
+ server_add_accept(0);
+ if (!(events & EV_READ))
+ return;
+
+ newfd = accept(fd, (struct sockaddr *) &sa, &slen);
+ if (newfd == -1) {
+ if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED)
+ return;
+ if (errno == ENFILE || errno == EMFILE) {
+ /* Delete and don't try again for 1 second. */
+ server_add_accept(1);
+ return;
+ }
+ fatal("accept failed");
+ }
+
+ if (server_exit) {
+ close(newfd);
+ return;
+ }
+ c = server_client_create(newfd);
+ if (!server_acl_join(c)) {
+ c->exit_message = xstrdup("access not allowed");
+ c->flags |= CLIENT_EXIT;
+ }
+}
+
+/*
+ * Add accept event. If timeout is nonzero, add as a timeout instead of a read
+ * event - used to backoff when running out of file descriptors.
+ */
+void
+server_add_accept(int timeout)
+{
+ struct timeval tv = { timeout, 0 };
+
+ if (server_fd == -1)
+ return;
+
+ if (event_initialized(&server_ev_accept))
+ event_del(&server_ev_accept);
+
+ if (timeout == 0) {
+ event_set(&server_ev_accept, server_fd, EV_READ, server_accept,
+ NULL);
+ event_add(&server_ev_accept, NULL);
+ } else {
+ event_set(&server_ev_accept, server_fd, EV_TIMEOUT,
+ server_accept, NULL);
+ event_add(&server_ev_accept, &tv);
+ }
+}
+
+/* Signal handler. */
+static void
+server_signal(int sig)
+{
+ int fd;
+
+ log_debug("%s: %s", __func__, strsignal(sig));
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ server_exit = 1;
+ server_send_exit();
+ break;
+ case SIGCHLD:
+ server_child_signal();
+ break;
+ case SIGUSR1:
+ event_del(&server_ev_accept);
+ fd = server_create_socket(server_client_flags, NULL);
+ if (fd != -1) {
+ close(server_fd);
+ server_fd = fd;
+ server_update_socket();
+ }
+ server_add_accept(0);
+ break;
+ case SIGUSR2:
+ proc_toggle_log(server_proc);
+ break;
+ }
+}
+
+/* Handle SIGCHLD. */
+static void
+server_child_signal(void)
+{
+ int status;
+ pid_t pid;
+
+ for (;;) {
+ switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
+ case -1:
+ if (errno == ECHILD)
+ return;
+ fatal("waitpid failed");
+ case 0:
+ return;
+ }
+ if (WIFSTOPPED(status))
+ server_child_stopped(pid, status);
+ else if (WIFEXITED(status) || WIFSIGNALED(status))
+ server_child_exited(pid, status);
+ }
+}
+
+/* Handle exited children. */
+static void
+server_child_exited(pid_t pid, int status)
+{
+ struct window *w, *w1;
+ struct window_pane *wp;
+
+ RB_FOREACH_SAFE(w, windows, &windows, w1) {
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp->pid == pid) {
+ wp->status = status;
+ wp->flags |= PANE_STATUSREADY;
+
+ log_debug("%%%u exited", wp->id);
+ wp->flags |= PANE_EXITED;
+
+ if (window_pane_destroy_ready(wp))
+ server_destroy_pane(wp, 1);
+ break;
+ }
+ }
+ }
+ job_check_died(pid, status);
+}
+
+/* Handle stopped children. */
+static void
+server_child_stopped(pid_t pid, int status)
+{
+ struct window *w;
+ struct window_pane *wp;
+
+ if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
+ return;
+
+ RB_FOREACH(w, windows, &windows) {
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp->pid == pid) {
+ if (killpg(pid, SIGCONT) != 0)
+ kill(pid, SIGCONT);
+ }
+ }
+ }
+ job_check_died(pid, status);
+}
+
+/* Add to message log. */
+void
+server_add_message(const char *fmt, ...)
+{
+ struct message_entry *msg, *msg1;
+ char *s;
+ va_list ap;
+ u_int limit;
+
+ va_start(ap, fmt);
+ xvasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ log_debug("message: %s", s);
+
+ msg = xcalloc(1, sizeof *msg);
+ gettimeofday(&msg->msg_time, NULL);
+ msg->msg_num = message_next++;
+ msg->msg = s;
+ TAILQ_INSERT_TAIL(&message_log, msg, entry);
+
+ limit = options_get_number(global_options, "message-limit");
+ TAILQ_FOREACH_SAFE(msg, &message_log, entry, msg1) {
+ if (msg->msg_num + limit >= message_next)
+ break;
+ free(msg->msg);
+ TAILQ_REMOVE(&message_log, msg, entry);
+ free(msg);
+ }
+}
diff --git a/session.c b/session.c
new file mode 100644
index 0000000..7502888
--- /dev/null
+++ b/session.c
@@ -0,0 +1,749 @@
+/* $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 <sys/time.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "tmux.h"
+
+struct sessions sessions;
+u_int next_session_id;
+struct session_groups session_groups = RB_INITIALIZER(&session_groups);
+
+static void session_free(int, short, void *);
+
+static void session_lock_timer(int, short, void *);
+
+static struct winlink *session_next_alert(struct winlink *);
+static struct winlink *session_previous_alert(struct winlink *);
+
+static void session_group_remove(struct session *);
+static void session_group_synchronize1(struct session *, struct session *);
+
+int
+session_cmp(struct session *s1, struct session *s2)
+{
+ return (strcmp(s1->name, s2->name));
+}
+RB_GENERATE(sessions, session, entry, session_cmp);
+
+static int
+session_group_cmp(struct session_group *s1, struct session_group *s2)
+{
+ return (strcmp(s1->name, s2->name));
+}
+RB_GENERATE_STATIC(session_groups, session_group, entry, session_group_cmp);
+
+/*
+ * Find if session is still alive. This is true if it is still on the global
+ * sessions list.
+ */
+int
+session_alive(struct session *s)
+{
+ struct session *s_loop;
+
+ RB_FOREACH(s_loop, sessions, &sessions) {
+ if (s_loop == s)
+ return (1);
+ }
+ return (0);
+}
+
+/* Find session by name. */
+struct session *
+session_find(const char *name)
+{
+ struct session s;
+
+ s.name = (char *) name;
+ return (RB_FIND(sessions, &sessions, &s));
+}
+
+/* Find session by id parsed from a string. */
+struct session *
+session_find_by_id_str(const char *s)
+{
+ const char *errstr;
+ u_int id;
+
+ if (*s != '$')
+ return (NULL);
+
+ id = strtonum(s + 1, 0, UINT_MAX, &errstr);
+ if (errstr != NULL)
+ return (NULL);
+ return (session_find_by_id(id));
+}
+
+/* Find session by id. */
+struct session *
+session_find_by_id(u_int id)
+{
+ struct session *s;
+
+ RB_FOREACH(s, sessions, &sessions) {
+ if (s->id == id)
+ return (s);
+ }
+ return (NULL);
+}
+
+/* Create a new session. */
+struct session *
+session_create(const char *prefix, const char *name, const char *cwd,
+ struct environ *env, struct options *oo, struct termios *tio)
+{
+ struct session *s;
+
+ s = xcalloc(1, sizeof *s);
+ s->references = 1;
+ s->flags = 0;
+
+ s->cwd = xstrdup(cwd);
+
+ TAILQ_INIT(&s->lastw);
+ RB_INIT(&s->windows);
+
+ s->environ = env;
+ s->options = oo;
+
+ status_update_cache(s);
+
+ s->tio = NULL;
+ if (tio != NULL) {
+ s->tio = xmalloc(sizeof *s->tio);
+ memcpy(s->tio, tio, sizeof *s->tio);
+ }
+
+ if (name != NULL) {
+ s->name = xstrdup(name);
+ s->id = next_session_id++;
+ } else {
+ do {
+ s->id = next_session_id++;
+ free(s->name);
+ if (prefix != NULL)
+ xasprintf(&s->name, "%s-%u", prefix, s->id);
+ else
+ xasprintf(&s->name, "%u", s->id);
+ } while (RB_FIND(sessions, &sessions, s) != NULL);
+ }
+ RB_INSERT(sessions, &sessions, s);
+
+ log_debug("new session %s $%u", s->name, s->id);
+
+ if (gettimeofday(&s->creation_time, NULL) != 0)
+ fatal("gettimeofday failed");
+ session_update_activity(s, &s->creation_time);
+
+ return (s);
+}
+
+/* Add a reference to a session. */
+void
+session_add_ref(struct session *s, const char *from)
+{
+ s->references++;
+ log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references);
+}
+
+/* Remove a reference from a session. */
+void
+session_remove_ref(struct session *s, const char *from)
+{
+ s->references--;
+ log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references);
+
+ if (s->references == 0)
+ event_once(-1, EV_TIMEOUT, session_free, s, NULL);
+}
+
+/* Free session. */
+static void
+session_free(__unused int fd, __unused short events, void *arg)
+{
+ struct session *s = arg;
+
+ log_debug("session %s freed (%d references)", s->name, s->references);
+
+ if (s->references == 0) {
+ environ_free(s->environ);
+ options_free(s->options);
+
+ free(s->name);
+ free(s);
+ }
+}
+
+/* Destroy a session. */
+void
+session_destroy(struct session *s, int notify, const char *from)
+{
+ struct winlink *wl;
+
+ log_debug("session %s destroyed (%s)", s->name, from);
+
+ if (s->curw == NULL)
+ return;
+ s->curw = NULL;
+
+ RB_REMOVE(sessions, &sessions, s);
+ if (notify)
+ notify_session("session-closed", s);
+
+ free(s->tio);
+
+ if (event_initialized(&s->lock_timer))
+ event_del(&s->lock_timer);
+
+ session_group_remove(s);
+
+ while (!TAILQ_EMPTY(&s->lastw))
+ winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw));
+ while (!RB_EMPTY(&s->windows)) {
+ wl = RB_ROOT(&s->windows);
+ notify_session_window("window-unlinked", s, wl->window);
+ winlink_remove(&s->windows, wl);
+ }
+
+ free((void *)s->cwd);
+
+ session_remove_ref(s, __func__);
+}
+
+/* Sanitize session name. */
+char *
+session_check_name(const char *name)
+{
+ char *copy, *cp, *new_name;
+
+ if (*name == '\0')
+ return (NULL);
+ copy = xstrdup(name);
+ for (cp = copy; *cp != '\0'; cp++) {
+ if (*cp == ':' || *cp == '.')
+ *cp = '_';
+ }
+ utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
+ free(copy);
+ return (new_name);
+}
+
+/* Lock session if it has timed out. */
+static void
+session_lock_timer(__unused int fd, __unused short events, void *arg)
+{
+ struct session *s = arg;
+
+ if (s->attached == 0)
+ return;
+
+ log_debug("session %s locked, activity time %lld", s->name,
+ (long long)s->activity_time.tv_sec);
+
+ server_lock_session(s);
+ recalculate_sizes();
+}
+
+/* Update activity time. */
+void
+session_update_activity(struct session *s, struct timeval *from)
+{
+ struct timeval *last = &s->last_activity_time;
+ struct timeval tv;
+
+ memcpy(last, &s->activity_time, sizeof *last);
+ if (from == NULL)
+ gettimeofday(&s->activity_time, NULL);
+ else
+ memcpy(&s->activity_time, from, sizeof s->activity_time);
+
+ log_debug("session $%u %s activity %lld.%06d (last %lld.%06d)", s->id,
+ s->name, (long long)s->activity_time.tv_sec,
+ (int)s->activity_time.tv_usec, (long long)last->tv_sec,
+ (int)last->tv_usec);
+
+ if (evtimer_initialized(&s->lock_timer))
+ evtimer_del(&s->lock_timer);
+ else
+ evtimer_set(&s->lock_timer, session_lock_timer, s);
+
+ if (s->attached != 0) {
+ timerclear(&tv);
+ tv.tv_sec = options_get_number(s->options, "lock-after-time");
+ if (tv.tv_sec != 0)
+ evtimer_add(&s->lock_timer, &tv);
+ }
+}
+
+/* Find the next usable session. */
+struct session *
+session_next_session(struct session *s)
+{
+ struct session *s2;
+
+ if (RB_EMPTY(&sessions) || !session_alive(s))
+ return (NULL);
+
+ s2 = RB_NEXT(sessions, &sessions, s);
+ if (s2 == NULL)
+ s2 = RB_MIN(sessions, &sessions);
+ if (s2 == s)
+ return (NULL);
+ return (s2);
+}
+
+/* Find the previous usable session. */
+struct session *
+session_previous_session(struct session *s)
+{
+ struct session *s2;
+
+ if (RB_EMPTY(&sessions) || !session_alive(s))
+ return (NULL);
+
+ s2 = RB_PREV(sessions, &sessions, s);
+ if (s2 == NULL)
+ s2 = RB_MAX(sessions, &sessions);
+ if (s2 == s)
+ return (NULL);
+ return (s2);
+}
+
+/* Attach a window to a session. */
+struct winlink *
+session_attach(struct session *s, struct window *w, int idx, char **cause)
+{
+ struct winlink *wl;
+
+ if ((wl = winlink_add(&s->windows, idx)) == NULL) {
+ xasprintf(cause, "index in use: %d", idx);
+ return (NULL);
+ }
+ wl->session = s;
+ winlink_set_window(wl, w);
+ notify_session_window("window-linked", s, w);
+
+ session_group_synchronize_from(s);
+ return (wl);
+}
+
+/* Detach a window from a session. */
+int
+session_detach(struct session *s, struct winlink *wl)
+{
+ if (s->curw == wl &&
+ session_last(s) != 0 &&
+ session_previous(s, 0) != 0)
+ session_next(s, 0);
+
+ wl->flags &= ~WINLINK_ALERTFLAGS;
+ notify_session_window("window-unlinked", s, wl->window);
+ winlink_stack_remove(&s->lastw, wl);
+ winlink_remove(&s->windows, wl);
+
+ session_group_synchronize_from(s);
+
+ if (RB_EMPTY(&s->windows)) {
+ session_destroy(s, 1, __func__);
+ return (1);
+ }
+ return (0);
+}
+
+/* Return if session has window. */
+int
+session_has(struct session *s, struct window *w)
+{
+ struct winlink *wl;
+
+ TAILQ_FOREACH(wl, &w->winlinks, wentry) {
+ if (wl->session == s)
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Return 1 if a window is linked outside this session (not including session
+ * groups). The window must be in this session!
+ */
+int
+session_is_linked(struct session *s, struct window *w)
+{
+ struct session_group *sg;
+
+ if ((sg = session_group_contains(s)) != NULL)
+ return (w->references != session_group_count(sg));
+ return (w->references != 1);
+}
+
+static struct winlink *
+session_next_alert(struct winlink *wl)
+{
+ while (wl != NULL) {
+ if (wl->flags & WINLINK_ALERTFLAGS)
+ break;
+ wl = winlink_next(wl);
+ }
+ return (wl);
+}
+
+/* Move session to next window. */
+int
+session_next(struct session *s, int alert)
+{
+ struct winlink *wl;
+
+ if (s->curw == NULL)
+ return (-1);
+
+ wl = winlink_next(s->curw);
+ if (alert)
+ wl = session_next_alert(wl);
+ if (wl == NULL) {
+ wl = RB_MIN(winlinks, &s->windows);
+ if (alert && ((wl = session_next_alert(wl)) == NULL))
+ return (-1);
+ }
+ return (session_set_current(s, wl));
+}
+
+static struct winlink *
+session_previous_alert(struct winlink *wl)
+{
+ while (wl != NULL) {
+ if (wl->flags & WINLINK_ALERTFLAGS)
+ break;
+ wl = winlink_previous(wl);
+ }
+ return (wl);
+}
+
+/* Move session to previous window. */
+int
+session_previous(struct session *s, int alert)
+{
+ struct winlink *wl;
+
+ if (s->curw == NULL)
+ return (-1);
+
+ wl = winlink_previous(s->curw);
+ if (alert)
+ wl = session_previous_alert(wl);
+ if (wl == NULL) {
+ wl = RB_MAX(winlinks, &s->windows);
+ if (alert && (wl = session_previous_alert(wl)) == NULL)
+ return (-1);
+ }
+ return (session_set_current(s, wl));
+}
+
+/* Move session to specific window. */
+int
+session_select(struct session *s, int idx)
+{
+ struct winlink *wl;
+
+ wl = winlink_find_by_index(&s->windows, idx);
+ return (session_set_current(s, wl));
+}
+
+/* Move session to last used window. */
+int
+session_last(struct session *s)
+{
+ struct winlink *wl;
+
+ wl = TAILQ_FIRST(&s->lastw);
+ if (wl == NULL)
+ return (-1);
+ if (wl == s->curw)
+ return (1);
+
+ return (session_set_current(s, wl));
+}
+
+/* Set current winlink to wl .*/
+int
+session_set_current(struct session *s, struct winlink *wl)
+{
+ struct winlink *old = s->curw;
+
+ if (wl == NULL)
+ return (-1);
+ if (wl == s->curw)
+ return (1);
+
+ winlink_stack_remove(&s->lastw, wl);
+ winlink_stack_push(&s->lastw, s->curw);
+ s->curw = wl;
+ if (options_get_number(global_options, "focus-events")) {
+ if (old != NULL)
+ window_update_focus(old->window);
+ window_update_focus(wl->window);
+ }
+ winlink_clear_flags(wl);
+ window_update_activity(wl->window);
+ tty_update_window_offset(wl->window);
+ notify_session("session-window-changed", s);
+ return (0);
+}
+
+/* Find the session group containing a session. */
+struct session_group *
+session_group_contains(struct session *target)
+{
+ struct session_group *sg;
+ struct session *s;
+
+ RB_FOREACH(sg, session_groups, &session_groups) {
+ TAILQ_FOREACH(s, &sg->sessions, gentry) {
+ if (s == target)
+ return (sg);
+ }
+ }
+ return (NULL);
+}
+
+/* Find session group by name. */
+struct session_group *
+session_group_find(const char *name)
+{
+ struct session_group sg;
+
+ sg.name = name;
+ return (RB_FIND(session_groups, &session_groups, &sg));
+}
+
+/* Create a new session group. */
+struct session_group *
+session_group_new(const char *name)
+{
+ struct session_group *sg;
+
+ if ((sg = session_group_find(name)) != NULL)
+ return (sg);
+
+ sg = xcalloc(1, sizeof *sg);
+ sg->name = xstrdup(name);
+ TAILQ_INIT(&sg->sessions);
+
+ RB_INSERT(session_groups, &session_groups, sg);
+ return (sg);
+}
+
+/* Add a session to a session group. */
+void
+session_group_add(struct session_group *sg, struct session *s)
+{
+ if (session_group_contains(s) == NULL)
+ TAILQ_INSERT_TAIL(&sg->sessions, s, gentry);
+}
+
+/* Remove a session from its group and destroy the group if empty. */
+static void
+session_group_remove(struct session *s)
+{
+ struct session_group *sg;
+
+ if ((sg = session_group_contains(s)) == NULL)
+ return;
+ TAILQ_REMOVE(&sg->sessions, s, gentry);
+ if (TAILQ_EMPTY(&sg->sessions)) {
+ RB_REMOVE(session_groups, &session_groups, sg);
+ free((void *)sg->name);
+ free(sg);
+ }
+}
+
+/* Count number of sessions in session group. */
+u_int
+session_group_count(struct session_group *sg)
+{
+ struct session *s;
+ u_int n;
+
+ n = 0;
+ TAILQ_FOREACH(s, &sg->sessions, gentry)
+ n++;
+ return (n);
+}
+
+/* Count number of clients attached to sessions in session group. */
+u_int
+session_group_attached_count(struct session_group *sg)
+{
+ struct session *s;
+ u_int n;
+
+ n = 0;
+ TAILQ_FOREACH(s, &sg->sessions, gentry)
+ n += s->attached;
+ return (n);
+}
+
+/* Synchronize a session to its session group. */
+void
+session_group_synchronize_to(struct session *s)
+{
+ struct session_group *sg;
+ struct session *target;
+
+ if ((sg = session_group_contains(s)) == NULL)
+ return;
+
+ target = NULL;
+ TAILQ_FOREACH(target, &sg->sessions, gentry) {
+ if (target != s)
+ break;
+ }
+ if (target != NULL)
+ session_group_synchronize1(target, s);
+}
+
+/* Synchronize a session group to a session. */
+void
+session_group_synchronize_from(struct session *target)
+{
+ struct session_group *sg;
+ struct session *s;
+
+ if ((sg = session_group_contains(target)) == NULL)
+ return;
+
+ TAILQ_FOREACH(s, &sg->sessions, gentry) {
+ if (s != target)
+ session_group_synchronize1(target, s);
+ }
+}
+
+/*
+ * Synchronize a session with a target session. This means destroying all
+ * winlinks then recreating them, then updating the current window, last window
+ * stack and alerts.
+ */
+static void
+session_group_synchronize1(struct session *target, struct session *s)
+{
+ struct winlinks old_windows, *ww;
+ struct winlink_stack old_lastw;
+ struct winlink *wl, *wl2;
+
+ /* Don't do anything if the session is empty (it'll be destroyed). */
+ ww = &target->windows;
+ if (RB_EMPTY(ww))
+ return;
+
+ /* If the current window has vanished, move to the next now. */
+ if (s->curw != NULL &&
+ winlink_find_by_index(ww, s->curw->idx) == NULL &&
+ session_last(s) != 0 && session_previous(s, 0) != 0)
+ session_next(s, 0);
+
+ /* Save the old pointer and reset it. */
+ memcpy(&old_windows, &s->windows, sizeof old_windows);
+ RB_INIT(&s->windows);
+
+ /* Link all the windows from the target. */
+ RB_FOREACH(wl, winlinks, ww) {
+ wl2 = winlink_add(&s->windows, wl->idx);
+ wl2->session = s;
+ winlink_set_window(wl2, wl->window);
+ notify_session_window("window-linked", s, wl2->window);
+ wl2->flags |= wl->flags & WINLINK_ALERTFLAGS;
+ }
+
+ /* Fix up the current window. */
+ if (s->curw != NULL)
+ s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
+ else
+ s->curw = winlink_find_by_index(&s->windows, target->curw->idx);
+
+ /* Fix up the last window stack. */
+ memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
+ TAILQ_INIT(&s->lastw);
+ TAILQ_FOREACH(wl, &old_lastw, sentry) {
+ wl2 = winlink_find_by_index(&s->windows, wl->idx);
+ if (wl2 != NULL)
+ TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry);
+ }
+
+ /* Then free the old winlinks list. */
+ while (!RB_EMPTY(&old_windows)) {
+ wl = RB_ROOT(&old_windows);
+ wl2 = winlink_find_by_window_id(&s->windows, wl->window->id);
+ if (wl2 == NULL)
+ notify_session_window("window-unlinked", s, wl->window);
+ winlink_remove(&old_windows, wl);
+ }
+}
+
+/* Renumber the windows across winlinks attached to a specific session. */
+void
+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;
+
+ /* Save and replace old window list. */
+ memcpy(&old_wins, &s->windows, sizeof old_wins);
+ RB_INIT(&s->windows);
+
+ /* Start renumbering from the base-index if it's set. */
+ new_idx = options_get_number(s->options, "base-index");
+ new_curw_idx = 0;
+
+ /* Go through the winlinks and assign new indexes. */
+ RB_FOREACH(wl, winlinks, &old_wins) {
+ wl_new = winlink_add(&s->windows, new_idx);
+ wl_new->session = s;
+ winlink_set_window(wl_new, wl->window);
+ wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS;
+
+ if (wl == s->curw)
+ new_curw_idx = wl_new->idx;
+
+ new_idx++;
+ }
+
+ /* Fix the stack of last windows now. */
+ memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
+ TAILQ_INIT(&s->lastw);
+ TAILQ_FOREACH(wl, &old_lastw, sentry) {
+ wl_new = winlink_find_by_window(&s->windows, wl->window);
+ if (wl_new != NULL)
+ TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry);
+ }
+
+ /* Set the current window. */
+ s->curw = winlink_find_by_index(&s->windows, new_curw_idx);
+
+ /* Free the old winlinks (reducing window references too). */
+ RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1)
+ winlink_remove(&old_wins, wl);
+}
diff --git a/spawn.c b/spawn.c
new file mode 100644
index 0000000..2cb2b65
--- /dev/null
+++ b/spawn.c
@@ -0,0 +1,484 @@
+/* $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 <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Set up the environment and create a new window and pane or a new pane.
+ *
+ * We need to set up the following items:
+ *
+ * - history limit, comes from the session;
+ *
+ * - base index, comes from the session;
+ *
+ * - current working directory, may be specified - if it isn't it comes from
+ * either the client or the session;
+ *
+ * - PATH variable, comes from the client if any, otherwise from the session
+ * environment;
+ *
+ * - shell, comes from default-shell;
+ *
+ * - termios, comes from the session;
+ *
+ * - remaining environment, comes from the session.
+ */
+
+static void
+spawn_log(const char *from, struct spawn_context *sc)
+{
+ struct session *s = sc->s;
+ struct winlink *wl = sc->wl;
+ struct window_pane *wp0 = sc->wp0;
+ const char *name = cmdq_get_name(sc->item);
+ char tmp[128];
+
+ log_debug("%s: %s, flags=%#x", from, name, sc->flags);
+
+ if (wl != NULL && wp0 != NULL)
+ xsnprintf(tmp, sizeof tmp, "wl=%d wp0=%%%u", wl->idx, wp0->id);
+ else if (wl != NULL)
+ xsnprintf(tmp, sizeof tmp, "wl=%d wp0=none", wl->idx);
+ else if (wp0 != NULL)
+ xsnprintf(tmp, sizeof tmp, "wl=none wp0=%%%u", wp0->id);
+ else
+ xsnprintf(tmp, sizeof tmp, "wl=none wp0=none");
+ log_debug("%s: s=$%u %s idx=%d", from, s->id, tmp, sc->idx);
+ log_debug("%s: name=%s", from, sc->name == NULL ? "none" : sc->name);
+}
+
+struct winlink *
+spawn_window(struct spawn_context *sc, char **cause)
+{
+ struct cmdq_item *item = sc->item;
+ struct client *c = cmdq_get_client(item);
+ struct session *s = sc->s;
+ struct window *w;
+ struct window_pane *wp;
+ struct winlink *wl;
+ int idx = sc->idx;
+ u_int sx, sy, xpixel, ypixel;
+
+ spawn_log(__func__, sc);
+
+ /*
+ * If the window already exists, we are respawning, so destroy all the
+ * panes except one.
+ */
+ if (sc->flags & SPAWN_RESPAWN) {
+ w = sc->wl->window;
+ if (~sc->flags & SPAWN_KILL) {
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp->fd != -1)
+ break;
+ }
+ if (wp != NULL) {
+ xasprintf(cause, "window %s:%d still active",
+ s->name, sc->wl->idx);
+ return (NULL);
+ }
+ }
+
+ sc->wp0 = TAILQ_FIRST(&w->panes);
+ TAILQ_REMOVE(&w->panes, sc->wp0, entry);
+
+ layout_free(w);
+ window_destroy_panes(w);
+
+ TAILQ_INSERT_HEAD(&w->panes, sc->wp0, entry);
+ window_pane_resize(sc->wp0, w->sx, w->sy);
+
+ layout_init(w, sc->wp0);
+ window_set_active_pane(w, sc->wp0, 0);
+ }
+
+ /*
+ * Otherwise we have no window so we will need to create one. First
+ * check if the given index already exists and destroy it if so.
+ */
+ if ((~sc->flags & SPAWN_RESPAWN) && idx != -1) {
+ wl = winlink_find_by_index(&s->windows, idx);
+ if (wl != NULL && (~sc->flags & SPAWN_KILL)) {
+ xasprintf(cause, "index %d in use", idx);
+ return (NULL);
+ }
+ if (wl != NULL) {
+ /*
+ * Can't use session_detach as it will destroy session
+ * if this makes it empty.
+ */
+ wl->flags &= ~WINLINK_ALERTFLAGS;
+ notify_session_window("window-unlinked", s, wl->window);
+ winlink_stack_remove(&s->lastw, wl);
+ winlink_remove(&s->windows, wl);
+
+ if (s->curw == wl) {
+ s->curw = NULL;
+ sc->flags &= ~SPAWN_DETACHED;
+ }
+ }
+ }
+
+ /* Then create a window if needed. */
+ if (~sc->flags & SPAWN_RESPAWN) {
+ if (idx == -1)
+ idx = -1 - options_get_number(s->options, "base-index");
+ if ((sc->wl = winlink_add(&s->windows, idx)) == NULL) {
+ xasprintf(cause, "couldn't add window %d", idx);
+ return (NULL);
+ }
+ default_window_size(sc->tc, s, NULL, &sx, &sy, &xpixel, &ypixel,
+ -1);
+ if ((w = window_create(sx, sy, xpixel, ypixel)) == NULL) {
+ winlink_remove(&s->windows, sc->wl);
+ xasprintf(cause, "couldn't create window %d", idx);
+ return (NULL);
+ }
+ if (s->curw == NULL)
+ s->curw = sc->wl;
+ sc->wl->session = s;
+ w->latest = sc->tc;
+ winlink_set_window(sc->wl, w);
+ } else
+ w = NULL;
+ sc->flags |= SPAWN_NONOTIFY;
+
+ /* Spawn the pane. */
+ wp = spawn_pane(sc, cause);
+ if (wp == NULL) {
+ if (~sc->flags & SPAWN_RESPAWN)
+ winlink_remove(&s->windows, sc->wl);
+ return (NULL);
+ }
+
+ /* Set the name of the new window. */
+ if (~sc->flags & SPAWN_RESPAWN) {
+ free(w->name);
+ if (sc->name != NULL) {
+ w->name = format_single(item, sc->name, c, s, NULL,
+ NULL);
+ options_set_number(w->options, "automatic-rename", 0);
+ } else
+ w->name = default_window_name(w);
+ }
+
+ /* Switch to the new window if required. */
+ if (~sc->flags & SPAWN_DETACHED)
+ session_select(s, sc->wl->idx);
+
+ /* Fire notification if new window. */
+ if (~sc->flags & SPAWN_RESPAWN)
+ notify_session_window("window-linked", s, w);
+
+ session_group_synchronize_from(s);
+ return (sc->wl);
+}
+
+struct window_pane *
+spawn_pane(struct spawn_context *sc, char **cause)
+{
+ struct cmdq_item *item = sc->item;
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *c = cmdq_get_client(item);
+ struct session *s = sc->s;
+ struct window *w = sc->wl->window;
+ struct window_pane *new_wp;
+ struct environ *child;
+ struct environ_entry *ee;
+ char **argv, *cp, **argvp, *argv0, *cwd, *new_cwd;
+ const char *cmd, *tmp;
+ int argc;
+ u_int idx;
+ struct termios now;
+ u_int hlimit;
+ struct winsize ws;
+ sigset_t set, oldset;
+ key_code key;
+
+ spawn_log(__func__, sc);
+
+ /*
+ * Work out the current working directory. If respawning, use
+ * the pane's stored one unless specified.
+ */
+ if (sc->cwd != NULL) {
+ cwd = format_single(item, sc->cwd, c, target->s, NULL, NULL);
+ if (*cwd != '/') {
+ xasprintf(&new_cwd, "%s/%s", server_client_get_cwd(c,
+ target->s), cwd);
+ free(cwd);
+ cwd = new_cwd;
+ }
+ } else if (~sc->flags & SPAWN_RESPAWN)
+ cwd = xstrdup(server_client_get_cwd(c, target->s));
+ else
+ cwd = NULL;
+
+ /*
+ * If we are respawning then get rid of the old process. Otherwise
+ * either create a new cell or assign to the one we are given.
+ */
+ hlimit = options_get_number(s->options, "history-limit");
+ if (sc->flags & SPAWN_RESPAWN) {
+ if (sc->wp0->fd != -1 && (~sc->flags & SPAWN_KILL)) {
+ window_pane_index(sc->wp0, &idx);
+ xasprintf(cause, "pane %s:%d.%u still active",
+ s->name, sc->wl->idx, idx);
+ free(cwd);
+ return (NULL);
+ }
+ if (sc->wp0->fd != -1) {
+ bufferevent_free(sc->wp0->event);
+ close(sc->wp0->fd);
+ }
+ window_pane_reset_mode_all(sc->wp0);
+ screen_reinit(&sc->wp0->base);
+ input_free(sc->wp0->ictx);
+ sc->wp0->ictx = NULL;
+ new_wp = sc->wp0;
+ new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
+ } else if (sc->lc == NULL) {
+ new_wp = window_add_pane(w, NULL, hlimit, sc->flags);
+ layout_init(w, new_wp);
+ } else {
+ new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags);
+ if (sc->flags & SPAWN_ZOOM)
+ layout_assign_pane(sc->lc, new_wp, 1);
+ else
+ layout_assign_pane(sc->lc, new_wp, 0);
+ }
+
+ /*
+ * Now we have a pane with nothing running in it ready for the new
+ * process. Work out the command and arguments and store the working
+ * directory.
+ */
+ if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) {
+ cmd = options_get_string(s->options, "default-command");
+ if (cmd != NULL && *cmd != '\0') {
+ argc = 1;
+ argv = (char **)&cmd;
+ } else {
+ argc = 0;
+ argv = NULL;
+ }
+ } else {
+ argc = sc->argc;
+ argv = sc->argv;
+ }
+ if (cwd != NULL) {
+ free(new_wp->cwd);
+ new_wp->cwd = cwd;
+ }
+
+ /*
+ * Replace the stored arguments if there are new ones. If not, the
+ * existing ones will be used (they will only exist for respawn).
+ */
+ if (argc > 0) {
+ cmd_free_argv(new_wp->argc, new_wp->argv);
+ new_wp->argc = argc;
+ new_wp->argv = cmd_copy_argv(argc, argv);
+ }
+
+ /* Create an environment for this pane. */
+ child = environ_for_session(s, 0);
+ if (sc->environ != NULL)
+ environ_copy(sc->environ, child);
+ environ_set(child, "TMUX_PANE", 0, "%%%u", new_wp->id);
+
+ /*
+ * Then the PATH environment variable. The session one is replaced from
+ * the client if there is one because otherwise running "tmux new
+ * myprogram" wouldn't work if myprogram isn't in the session's path.
+ */
+ if (c != NULL && c->session == NULL) { /* only unattached clients */
+ ee = environ_find(c->environ, "PATH");
+ if (ee != NULL)
+ environ_set(child, "PATH", 0, "%s", ee->value);
+ }
+ if (environ_find(child, "PATH") == NULL)
+ environ_set(child, "PATH", 0, "%s", _PATH_DEFPATH);
+
+ /* Then the shell. If respawning, use the old one. */
+ if (~sc->flags & SPAWN_RESPAWN) {
+ tmp = options_get_string(s->options, "default-shell");
+ if (!checkshell(tmp))
+ tmp = _PATH_BSHELL;
+ free(new_wp->shell);
+ new_wp->shell = xstrdup(tmp);
+ }
+ environ_set(child, "SHELL", 0, "%s", new_wp->shell);
+
+ /* Log the arguments we are going to use. */
+ log_debug("%s: shell=%s", __func__, new_wp->shell);
+ if (new_wp->argc != 0) {
+ cp = cmd_stringify_argv(new_wp->argc, new_wp->argv);
+ log_debug("%s: cmd=%s", __func__, cp);
+ free(cp);
+ }
+ log_debug("%s: cwd=%s", __func__, new_wp->cwd);
+ cmd_log_argv(new_wp->argc, new_wp->argv, "%s", __func__);
+ environ_log(child, "%s: environment ", __func__);
+
+ /* Initialize the window size. */
+ memset(&ws, 0, sizeof ws);
+ ws.ws_col = screen_size_x(&new_wp->base);
+ ws.ws_row = screen_size_y(&new_wp->base);
+ ws.ws_xpixel = w->xpixel * ws.ws_col;
+ ws.ws_ypixel = w->ypixel * ws.ws_row;
+
+ /* Block signals until fork has completed. */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oldset);
+
+ /* If the command is empty, don't fork a child process. */
+ if (sc->flags & SPAWN_EMPTY) {
+ new_wp->flags |= PANE_EMPTY;
+ new_wp->base.mode &= ~MODE_CURSOR;
+ new_wp->base.mode |= MODE_CRLF;
+ goto complete;
+ }
+
+ /* Fork the new process. */
+ new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws);
+ if (new_wp->pid == -1) {
+ xasprintf(cause, "fork failed: %s", strerror(errno));
+ new_wp->fd = -1;
+ if (~sc->flags & SPAWN_RESPAWN) {
+ server_client_remove_pane(new_wp);
+ layout_close_pane(new_wp);
+ window_remove_pane(w, new_wp);
+ }
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ environ_free(child);
+ return (NULL);
+ }
+
+ /* In the parent process, everything is done now. */
+ if (new_wp->pid != 0)
+ goto complete;
+
+ /*
+ * Child process. Change to the working directory or home if that
+ * fails.
+ */
+ if (chdir(new_wp->cwd) == 0)
+ environ_set(child, "PWD", 0, "%s", new_wp->cwd);
+ else if ((tmp = find_home()) != NULL || chdir(tmp) == 0)
+ environ_set(child, "PWD", 0, "%s", tmp);
+ else if (chdir("/") == 0)
+ environ_set(child, "PWD", 0, "/");
+ else
+ fatal("chdir failed");
+
+ /*
+ * Update terminal escape characters from the session if available and
+ * force VERASE to tmux's backspace.
+ */
+ if (tcgetattr(STDIN_FILENO, &now) != 0)
+ _exit(1);
+ if (s->tio != NULL)
+ memcpy(now.c_cc, s->tio->c_cc, sizeof now.c_cc);
+ key = options_get_number(global_options, "backspace");
+ if (key >= 0x7f)
+ now.c_cc[VERASE] = '\177';
+ else
+ now.c_cc[VERASE] = key;
+#ifdef IUTF8
+ now.c_iflag |= IUTF8;
+#endif
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &now) != 0)
+ _exit(1);
+
+ /* Clean up file descriptors and signals and update the environment. */
+ closefrom(STDERR_FILENO + 1);
+ proc_clear_signals(server_proc, 1);
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ log_close();
+ environ_push(child);
+
+ /*
+ * If given multiple arguments, use execvp(). Copy the arguments to
+ * ensure they end in a NULL.
+ */
+ if (new_wp->argc != 0 && new_wp->argc != 1) {
+ argvp = cmd_copy_argv(new_wp->argc, new_wp->argv);
+ execvp(argvp[0], argvp);
+ _exit(1);
+ }
+
+ /*
+ * If one argument, pass it to $SHELL -c. Otherwise create a login
+ * shell.
+ */
+ cp = strrchr(new_wp->shell, '/');
+ if (new_wp->argc == 1) {
+ tmp = new_wp->argv[0];
+ if (cp != NULL && cp[1] != '\0')
+ xasprintf(&argv0, "%s", cp + 1);
+ else
+ xasprintf(&argv0, "%s", new_wp->shell);
+ execl(new_wp->shell, argv0, "-c", tmp, (char *)NULL);
+ _exit(1);
+ }
+ if (cp != NULL && cp[1] != '\0')
+ xasprintf(&argv0, "-%s", cp + 1);
+ else
+ xasprintf(&argv0, "-%s", new_wp->shell);
+ execl(new_wp->shell, argv0, (char *)NULL);
+ _exit(1);
+
+complete:
+#ifdef HAVE_UTEMPTER
+ if (~new_wp->flags & PANE_EMPTY) {
+ xasprintf(&cp, "tmux(%lu).%%%u", (long)getpid(), new_wp->id);
+ utempter_add_record(new_wp->fd, cp);
+ kill(getpid(), SIGCHLD);
+ free(cp);
+ }
+#endif
+
+ new_wp->flags &= ~PANE_EXITED;
+
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ window_pane_set_event(new_wp);
+
+ environ_free(child);
+
+ if (sc->flags & SPAWN_RESPAWN)
+ return (new_wp);
+ if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) {
+ if (sc->flags & SPAWN_NONOTIFY)
+ window_set_active_pane(w, new_wp, 0);
+ else
+ window_set_active_pane(w, new_wp, 1);
+ }
+ if (~sc->flags & SPAWN_NONOTIFY)
+ notify_window("window-layout-changed", w);
+ return (new_wp);
+}
diff --git a/status.c b/status.c
new file mode 100644
index 0000000..929276d
--- /dev/null
+++ b/status.c
@@ -0,0 +1,2004 @@
+/* $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 <sys/time.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static void status_message_callback(int, short, void *);
+static void status_timer_callback(int, short, void *);
+
+static char *status_prompt_find_history_file(void);
+static const char *status_prompt_up_history(u_int *, u_int);
+static const char *status_prompt_down_history(u_int *, u_int);
+static void status_prompt_add_history(const char *, u_int);
+
+static char *status_prompt_complete(struct client *, const char *, u_int);
+static char *status_prompt_complete_window_menu(struct client *,
+ struct session *, const char *, u_int, char);
+
+struct status_prompt_menu {
+ struct client *c;
+ u_int start;
+ u_int size;
+ char **list;
+ char flag;
+};
+
+static const char *prompt_type_strings[] = {
+ "command",
+ "search",
+ "target",
+ "window-target"
+};
+
+/* Status prompt history. */
+char **status_prompt_hlist[PROMPT_NTYPES];
+u_int status_prompt_hsize[PROMPT_NTYPES];
+
+/* Find the history file to load/save from/to. */
+static char *
+status_prompt_find_history_file(void)
+{
+ const char *home, *history_file;
+ char *path;
+
+ history_file = options_get_string(global_options, "history-file");
+ if (*history_file == '\0')
+ return (NULL);
+ if (*history_file == '/')
+ return (xstrdup(history_file));
+
+ if (history_file[0] != '~' || history_file[1] != '/')
+ return (NULL);
+ if ((home = find_home()) == NULL)
+ return (NULL);
+ xasprintf(&path, "%s%s", home, history_file + 1);
+ return (path);
+}
+
+/* Add loaded history item to the appropriate list. */
+static void
+status_prompt_add_typed_history(char *line)
+{
+ char *typestr;
+ enum prompt_type type = PROMPT_TYPE_INVALID;
+
+ typestr = strsep(&line, ":");
+ if (line != NULL)
+ type = status_prompt_type(typestr);
+ if (type == PROMPT_TYPE_INVALID) {
+ /*
+ * Invalid types are not expected, but this provides backward
+ * compatibility with old history files.
+ */
+ if (line != NULL)
+ *(--line) = ':';
+ status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
+ } else
+ status_prompt_add_history(line, type);
+}
+
+/* Load status prompt history from file. */
+void
+status_prompt_load_history(void)
+{
+ FILE *f;
+ char *history_file, *line, *tmp;
+ size_t length;
+
+ if ((history_file = status_prompt_find_history_file()) == NULL)
+ return;
+ log_debug("loading history from %s", history_file);
+
+ f = fopen(history_file, "r");
+ if (f == NULL) {
+ log_debug("%s: %s", history_file, strerror(errno));
+ free(history_file);
+ return;
+ }
+ free(history_file);
+
+ for (;;) {
+ if ((line = fgetln(f, &length)) == NULL)
+ break;
+
+ if (length > 0) {
+ if (line[length - 1] == '\n') {
+ line[length - 1] = '\0';
+ status_prompt_add_typed_history(line);
+ } else {
+ tmp = xmalloc(length + 1);
+ memcpy(tmp, line, length);
+ tmp[length] = '\0';
+ status_prompt_add_typed_history(tmp);
+ free(tmp);
+ }
+ }
+ }
+ fclose(f);
+}
+
+/* Save status prompt history to file. */
+void
+status_prompt_save_history(void)
+{
+ FILE *f;
+ u_int i, type;
+ char *history_file;
+
+ if ((history_file = status_prompt_find_history_file()) == NULL)
+ return;
+ log_debug("saving history to %s", history_file);
+
+ f = fopen(history_file, "w");
+ if (f == NULL) {
+ log_debug("%s: %s", history_file, strerror(errno));
+ free(history_file);
+ return;
+ }
+ free(history_file);
+
+ for (type = 0; type < PROMPT_NTYPES; type++) {
+ for (i = 0; i < status_prompt_hsize[type]; i++) {
+ fputs(prompt_type_strings[type], f);
+ fputc(':', f);
+ fputs(status_prompt_hlist[type][i], f);
+ fputc('\n', f);
+ }
+ }
+ fclose(f);
+
+}
+
+/* Status timer callback. */
+static void
+status_timer_callback(__unused int fd, __unused short events, void *arg)
+{
+ struct client *c = arg;
+ struct session *s = c->session;
+ struct timeval tv;
+
+ evtimer_del(&c->status.timer);
+
+ if (s == NULL)
+ return;
+
+ if (c->message_string == NULL && c->prompt_string == NULL)
+ c->flags |= CLIENT_REDRAWSTATUS;
+
+ timerclear(&tv);
+ tv.tv_sec = options_get_number(s->options, "status-interval");
+
+ if (tv.tv_sec != 0)
+ evtimer_add(&c->status.timer, &tv);
+ log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
+}
+
+/* Start status timer for client. */
+void
+status_timer_start(struct client *c)
+{
+ struct session *s = c->session;
+
+ if (event_initialized(&c->status.timer))
+ evtimer_del(&c->status.timer);
+ else
+ evtimer_set(&c->status.timer, status_timer_callback, c);
+
+ if (s != NULL && options_get_number(s->options, "status"))
+ status_timer_callback(-1, 0, c);
+}
+
+/* Start status timer for all clients. */
+void
+status_timer_start_all(void)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry)
+ status_timer_start(c);
+}
+
+/* Update status cache. */
+void
+status_update_cache(struct session *s)
+{
+ s->statuslines = options_get_number(s->options, "status");
+ if (s->statuslines == 0)
+ s->statusat = -1;
+ else if (options_get_number(s->options, "status-position") == 0)
+ s->statusat = 0;
+ else
+ s->statusat = 1;
+}
+
+/* Get screen line of status line. -1 means off. */
+int
+status_at_line(struct client *c)
+{
+ struct session *s = c->session;
+
+ if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
+ return (-1);
+ if (s->statusat != 1)
+ return (s->statusat);
+ return (c->tty.sy - status_line_size(c));
+}
+
+/* Get size of status line for client's session. 0 means off. */
+u_int
+status_line_size(struct client *c)
+{
+ struct session *s = c->session;
+
+ if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
+ return (0);
+ if (s == NULL)
+ return (options_get_number(global_s_options, "status"));
+ return (s->statuslines);
+}
+
+/* Get window at window list position. */
+struct style_range *
+status_get_range(struct client *c, u_int x, u_int y)
+{
+ struct status_line *sl = &c->status;
+ struct style_range *sr;
+
+ if (y >= nitems(sl->entries))
+ return (NULL);
+ TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) {
+ if (x >= sr->start && x < sr->end)
+ return (sr);
+ }
+ return (NULL);
+}
+
+/* Free all ranges. */
+static void
+status_free_ranges(struct style_ranges *srs)
+{
+ struct style_range *sr, *sr1;
+
+ TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) {
+ TAILQ_REMOVE(srs, sr, entry);
+ free(sr);
+ }
+}
+
+/* Save old status line. */
+static void
+status_push_screen(struct client *c)
+{
+ struct status_line *sl = &c->status;
+
+ if (sl->active == &sl->screen) {
+ sl->active = xmalloc(sizeof *sl->active);
+ screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
+ }
+ sl->references++;
+}
+
+/* Restore old status line. */
+static void
+status_pop_screen(struct client *c)
+{
+ struct status_line *sl = &c->status;
+
+ if (--sl->references == 0) {
+ screen_free(sl->active);
+ free(sl->active);
+ sl->active = &sl->screen;
+ }
+}
+
+/* Initialize status line. */
+void
+status_init(struct client *c)
+{
+ struct status_line *sl = &c->status;
+ u_int i;
+
+ for (i = 0; i < nitems(sl->entries); i++)
+ TAILQ_INIT(&sl->entries[i].ranges);
+
+ screen_init(&sl->screen, c->tty.sx, 1, 0);
+ sl->active = &sl->screen;
+}
+
+/* Free status line. */
+void
+status_free(struct client *c)
+{
+ struct status_line *sl = &c->status;
+ u_int i;
+
+ for (i = 0; i < nitems(sl->entries); i++) {
+ status_free_ranges(&sl->entries[i].ranges);
+ free((void *)sl->entries[i].expanded);
+ }
+
+ if (event_initialized(&sl->timer))
+ evtimer_del(&sl->timer);
+
+ if (sl->active != &sl->screen) {
+ screen_free(sl->active);
+ free(sl->active);
+ }
+ screen_free(&sl->screen);
+}
+
+/* Draw status line for client. */
+int
+status_redraw(struct client *c)
+{
+ struct status_line *sl = &c->status;
+ struct status_line_entry *sle;
+ struct session *s = c->session;
+ struct screen_write_ctx ctx;
+ struct grid_cell gc;
+ u_int lines, i, n, width = c->tty.sx;
+ int flags, force = 0, changed = 0, fg, bg;
+ struct options_entry *o;
+ union options_value *ov;
+ struct format_tree *ft;
+ char *expanded;
+
+ log_debug("%s enter", __func__);
+
+ /* Shouldn't get here if not the active screen. */
+ if (sl->active != &sl->screen)
+ fatalx("not the active screen");
+
+ /* No status line? */
+ lines = status_line_size(c);
+ if (c->tty.sy == 0 || lines == 0)
+ return (1);
+
+ /* Create format tree. */
+ flags = FORMAT_STATUS;
+ if (c->flags & CLIENT_STATUSFORCE)
+ flags |= FORMAT_FORCE;
+ ft = format_create(c, NULL, FORMAT_NONE, flags);
+ format_defaults(ft, c, NULL, NULL, NULL);
+
+ /* Set up default colour. */
+ style_apply(&gc, s->options, "status-style", ft);
+ fg = options_get_number(s->options, "status-fg");
+ if (!COLOUR_DEFAULT(fg))
+ gc.fg = fg;
+ bg = options_get_number(s->options, "status-bg");
+ if (!COLOUR_DEFAULT(bg))
+ gc.bg = bg;
+ if (!grid_cells_equal(&gc, &sl->style)) {
+ force = 1;
+ memcpy(&sl->style, &gc, sizeof sl->style);
+ }
+
+ /* Resize the target screen. */
+ if (screen_size_x(&sl->screen) != width ||
+ screen_size_y(&sl->screen) != lines) {
+ screen_resize(&sl->screen, width, lines, 0);
+ changed = force = 1;
+ }
+ screen_write_start(&ctx, &sl->screen);
+
+ /* Write the status lines. */
+ o = options_get(s->options, "status-format");
+ if (o == NULL) {
+ for (n = 0; n < width * lines; n++)
+ screen_write_putc(&ctx, &gc, ' ');
+ } else {
+ for (i = 0; i < lines; i++) {
+ screen_write_cursormove(&ctx, 0, i, 0);
+
+ ov = options_array_get(o, i);
+ if (ov == NULL) {
+ for (n = 0; n < width; n++)
+ screen_write_putc(&ctx, &gc, ' ');
+ continue;
+ }
+ sle = &sl->entries[i];
+
+ expanded = format_expand_time(ft, ov->string);
+ if (!force &&
+ sle->expanded != NULL &&
+ strcmp(expanded, sle->expanded) == 0) {
+ free(expanded);
+ continue;
+ }
+ changed = 1;
+
+ for (n = 0; n < width; n++)
+ screen_write_putc(&ctx, &gc, ' ');
+ screen_write_cursormove(&ctx, 0, i, 0);
+
+ status_free_ranges(&sle->ranges);
+ format_draw(&ctx, &gc, width, expanded, &sle->ranges,
+ 0);
+
+ free(sle->expanded);
+ sle->expanded = expanded;
+ }
+ }
+ screen_write_stop(&ctx);
+
+ /* Free the format tree. */
+ format_free(ft);
+
+ /* Return if the status line has changed. */
+ log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
+ return (force || changed);
+}
+
+/* Set a status line message. */
+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);
+
+ va_start(ap, fmt);
+ xvasprintf(&c->message_string, fmt, ap);
+ va_end(ap);
+
+ server_add_message("%s message: %s", c->name, c->message_string);
+
+ /*
+ * With delay -1, the display-time option is used; zero means wait for
+ * key press; more than zero is the actual delay time in milliseconds.
+ */
+ if (delay == -1)
+ delay = options_get_number(c->session->options, "display-time");
+ if (delay > 0) {
+ tv.tv_sec = delay / 1000;
+ tv.tv_usec = (delay % 1000) * 1000L;
+
+ if (event_initialized(&c->message_timer))
+ evtimer_del(&c->message_timer);
+ evtimer_set(&c->message_timer, status_message_callback, c);
+
+ evtimer_add(&c->message_timer, &tv);
+ }
+
+ if (delay != 0)
+ c->message_ignore_keys = ignore_keys;
+ c->message_ignore_styles = ignore_styles;
+
+ c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
+ c->flags |= CLIENT_REDRAWSTATUS;
+}
+
+/* Clear status line message. */
+void
+status_message_clear(struct client *c)
+{
+ if (c->message_string == NULL)
+ return;
+
+ free(c->message_string);
+ c->message_string = NULL;
+
+ if (c->prompt_string == NULL)
+ c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
+ c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
+
+ status_pop_screen(c);
+}
+
+/* Clear status line message after timer expires. */
+static void
+status_message_callback(__unused int fd, __unused short event, void *data)
+{
+ struct client *c = data;
+
+ status_message_clear(c);
+}
+
+/* Draw client message on status line of present else on last line. */
+int
+status_message_redraw(struct client *c)
+{
+ struct status_line *sl = &c->status;
+ struct screen_write_ctx ctx;
+ struct session *s = c->session;
+ struct screen old_screen;
+ size_t len;
+ u_int lines, offset;
+ struct grid_cell gc;
+ struct format_tree *ft;
+
+ if (c->tty.sx == 0 || c->tty.sy == 0)
+ return (0);
+ memcpy(&old_screen, sl->active, sizeof old_screen);
+
+ lines = status_line_size(c);
+ if (lines <= 1)
+ lines = 1;
+ screen_init(sl->active, c->tty.sx, lines, 0);
+
+ len = screen_write_strlen("%s", c->message_string);
+ if (len > c->tty.sx)
+ len = c->tty.sx;
+
+ ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
+ style_apply(&gc, s->options, "message-style", ft);
+ 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);
+ for (offset = 0; offset < c->tty.sx; offset++)
+ screen_write_putc(&ctx, &gc, ' ');
+ screen_write_cursormove(&ctx, 0, lines - 1, 0);
+ if (c->message_ignore_styles)
+ screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
+ else
+ format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0);
+ screen_write_stop(&ctx);
+
+ if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
+ screen_free(&old_screen);
+ return (0);
+ }
+ screen_free(&old_screen);
+ return (1);
+}
+
+/* Enable status line prompt. */
+void
+status_prompt_set(struct client *c, struct cmd_find_state *fs,
+ const char *msg, const char *input, prompt_input_cb inputcb,
+ prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type)
+{
+ struct format_tree *ft;
+ char *tmp;
+
+ if (fs != NULL)
+ ft = format_create_from_state(NULL, c, fs);
+ else
+ ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
+
+ if (input == NULL)
+ input = "";
+ if (flags & PROMPT_NOFORMAT)
+ tmp = xstrdup(input);
+ else
+ tmp = format_expand_time(ft, input);
+
+ status_message_clear(c);
+ status_prompt_clear(c);
+ status_push_screen(c);
+
+ c->prompt_string = format_expand_time(ft, msg);
+
+ if (flags & PROMPT_INCREMENTAL) {
+ c->prompt_last = xstrdup(tmp);
+ c->prompt_buffer = utf8_fromcstr("");
+ } else {
+ c->prompt_last = NULL;
+ c->prompt_buffer = utf8_fromcstr(tmp);
+ }
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+
+ c->prompt_inputcb = inputcb;
+ c->prompt_freecb = freecb;
+ c->prompt_data = data;
+
+ memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
+
+ c->prompt_flags = flags;
+ c->prompt_type = prompt_type;
+ c->prompt_mode = PROMPT_ENTRY;
+
+ if (~flags & PROMPT_INCREMENTAL)
+ c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
+ c->flags |= CLIENT_REDRAWSTATUS;
+
+ if (flags & PROMPT_INCREMENTAL)
+ c->prompt_inputcb(c, c->prompt_data, "=", 0);
+
+ free(tmp);
+ format_free(ft);
+}
+
+/* Remove status line prompt. */
+void
+status_prompt_clear(struct client *c)
+{
+ if (c->prompt_string == NULL)
+ return;
+
+ if (c->prompt_freecb != NULL && c->prompt_data != NULL)
+ c->prompt_freecb(c->prompt_data);
+
+ free(c->prompt_last);
+ c->prompt_last = NULL;
+
+ free(c->prompt_string);
+ c->prompt_string = NULL;
+
+ free(c->prompt_buffer);
+ c->prompt_buffer = NULL;
+
+ free(c->prompt_saved);
+ c->prompt_saved = NULL;
+
+ c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
+ c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
+
+ status_pop_screen(c);
+}
+
+/* Update status line prompt with a new prompt string. */
+void
+status_prompt_update(struct client *c, const char *msg, const char *input)
+{
+ struct format_tree *ft;
+ char *tmp;
+
+ ft = format_create(c, NULL, FORMAT_NONE, 0);
+ format_defaults(ft, c, NULL, NULL, NULL);
+
+ tmp = format_expand_time(ft, input);
+
+ free(c->prompt_string);
+ c->prompt_string = format_expand_time(ft, msg);
+
+ free(c->prompt_buffer);
+ c->prompt_buffer = utf8_fromcstr(tmp);
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+
+ memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
+
+ c->flags |= CLIENT_REDRAWSTATUS;
+
+ free(tmp);
+ format_free(ft);
+}
+
+/* Draw client prompt on status line of present else on last line. */
+int
+status_prompt_redraw(struct client *c)
+{
+ struct status_line *sl = &c->status;
+ struct screen_write_ctx ctx;
+ struct session *s = c->session;
+ struct screen old_screen;
+ u_int i, lines, offset, left, start, width;
+ u_int pcursor, pwidth;
+ struct grid_cell gc, cursorgc;
+ struct format_tree *ft;
+
+ if (c->tty.sx == 0 || c->tty.sy == 0)
+ return (0);
+ memcpy(&old_screen, sl->active, sizeof old_screen);
+
+ lines = status_line_size(c);
+ if (lines <= 1)
+ lines = 1;
+ screen_init(sl->active, c->tty.sx, lines, 0);
+
+ ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
+ if (c->prompt_mode == PROMPT_COMMAND)
+ style_apply(&gc, s->options, "message-command-style", ft);
+ else
+ style_apply(&gc, s->options, "message-style", ft);
+ format_free(ft);
+
+ memcpy(&cursorgc, &gc, sizeof cursorgc);
+ cursorgc.attr ^= GRID_ATTR_REVERSE;
+
+ start = format_width(c->prompt_string);
+ if (start > c->tty.sx)
+ 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);
+ for (offset = 0; offset < c->tty.sx; offset++)
+ screen_write_putc(&ctx, &gc, ' ');
+ screen_write_cursormove(&ctx, 0, lines - 1, 0);
+ format_draw(&ctx, &gc, start, c->prompt_string, NULL, 0);
+ screen_write_cursormove(&ctx, start, lines - 1, 0);
+
+ left = c->tty.sx - start;
+ if (left == 0)
+ goto finished;
+
+ pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
+ pwidth = utf8_strwidth(c->prompt_buffer, -1);
+ if (pcursor >= left) {
+ /*
+ * The cursor would be outside the screen so start drawing
+ * with it on the right.
+ */
+ offset = (pcursor - left) + 1;
+ pwidth = left;
+ } else
+ offset = 0;
+ if (pwidth > left)
+ pwidth = left;
+ c->prompt_cursor = start + c->prompt_index - offset;
+
+ width = 0;
+ for (i = 0; c->prompt_buffer[i].size != 0; i++) {
+ if (width < offset) {
+ width += c->prompt_buffer[i].width;
+ continue;
+ }
+ if (width >= offset + pwidth)
+ break;
+ width += c->prompt_buffer[i].width;
+ if (width > offset + pwidth)
+ break;
+
+ if (i != c->prompt_index) {
+ utf8_copy(&gc.data, &c->prompt_buffer[i]);
+ screen_write_cell(&ctx, &gc);
+ } else {
+ utf8_copy(&cursorgc.data, &c->prompt_buffer[i]);
+ screen_write_cell(&ctx, &cursorgc);
+ }
+ }
+ if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i)
+ screen_write_putc(&ctx, &cursorgc, ' ');
+
+finished:
+ screen_write_stop(&ctx);
+
+ if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
+ screen_free(&old_screen);
+ return (0);
+ }
+ screen_free(&old_screen);
+ return (1);
+}
+
+/* Is this a separator? */
+static int
+status_prompt_in_list(const char *ws, const struct utf8_data *ud)
+{
+ if (ud->size != 1 || ud->width != 1)
+ return (0);
+ return (strchr(ws, *ud->data) != NULL);
+}
+
+/* Is this a space? */
+static int
+status_prompt_space(const struct utf8_data *ud)
+{
+ if (ud->size != 1 || ud->width != 1)
+ return (0);
+ return (*ud->data == ' ');
+}
+
+/*
+ * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
+ * as an emacs key; return 2 to append to the buffer.
+ */
+static int
+status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
+{
+ if (c->prompt_mode == PROMPT_ENTRY) {
+ switch (key) {
+ case '\001': /* C-a */
+ case '\003': /* C-c */
+ case '\005': /* C-e */
+ case '\007': /* C-g */
+ case '\010': /* C-h */
+ case '\011': /* Tab */
+ case '\013': /* C-k */
+ case '\016': /* C-n */
+ case '\020': /* C-p */
+ case '\024': /* C-t */
+ case '\025': /* C-u */
+ case '\027': /* C-w */
+ case '\031': /* C-y */
+ case '\n':
+ case '\r':
+ case KEYC_LEFT|KEYC_CTRL:
+ case KEYC_RIGHT|KEYC_CTRL:
+ case KEYC_BSPACE:
+ case KEYC_DC:
+ case KEYC_DOWN:
+ case KEYC_END:
+ case KEYC_HOME:
+ case KEYC_LEFT:
+ case KEYC_RIGHT:
+ case KEYC_UP:
+ *new_key = key;
+ return (1);
+ case '\033': /* Escape */
+ c->prompt_mode = PROMPT_COMMAND;
+ c->flags |= CLIENT_REDRAWSTATUS;
+ return (0);
+ }
+ *new_key = key;
+ return (2);
+ }
+
+ switch (key) {
+ case KEYC_BSPACE:
+ *new_key = KEYC_LEFT;
+ return (1);
+ case 'A':
+ case 'I':
+ case 'C':
+ case 's':
+ case 'a':
+ c->prompt_mode = PROMPT_ENTRY;
+ c->flags |= CLIENT_REDRAWSTATUS;
+ break; /* switch mode and... */
+ case 'S':
+ c->prompt_mode = PROMPT_ENTRY;
+ c->flags |= CLIENT_REDRAWSTATUS;
+ *new_key = '\025'; /* C-u */
+ return (1);
+ case 'i':
+ case '\033': /* Escape */
+ c->prompt_mode = PROMPT_ENTRY;
+ c->flags |= CLIENT_REDRAWSTATUS;
+ return (0);
+ }
+
+ switch (key) {
+ case 'A':
+ case '$':
+ *new_key = KEYC_END;
+ return (1);
+ case 'I':
+ case '0':
+ case '^':
+ *new_key = KEYC_HOME;
+ return (1);
+ case 'C':
+ case 'D':
+ *new_key = '\013'; /* C-k */
+ return (1);
+ case KEYC_BSPACE:
+ case 'X':
+ *new_key = KEYC_BSPACE;
+ return (1);
+ case 'b':
+ *new_key = 'b'|KEYC_META;
+ return (1);
+ case 'B':
+ *new_key = 'B'|KEYC_VI;
+ return (1);
+ case 'd':
+ *new_key = '\025'; /* C-u */
+ return (1);
+ case 'e':
+ *new_key = 'e'|KEYC_VI;
+ return (1);
+ case 'E':
+ *new_key = 'E'|KEYC_VI;
+ return (1);
+ case 'w':
+ *new_key = 'w'|KEYC_VI;
+ return (1);
+ case 'W':
+ *new_key = 'W'|KEYC_VI;
+ return (1);
+ case 'p':
+ *new_key = '\031'; /* C-y */
+ return (1);
+ case 'q':
+ *new_key = '\003'; /* C-c */
+ return (1);
+ case 's':
+ case KEYC_DC:
+ case 'x':
+ *new_key = KEYC_DC;
+ return (1);
+ case KEYC_DOWN:
+ case 'j':
+ *new_key = KEYC_DOWN;
+ return (1);
+ case KEYC_LEFT:
+ case 'h':
+ *new_key = KEYC_LEFT;
+ return (1);
+ case 'a':
+ case KEYC_RIGHT:
+ case 'l':
+ *new_key = KEYC_RIGHT;
+ return (1);
+ case KEYC_UP:
+ case 'k':
+ *new_key = KEYC_UP;
+ return (1);
+ case '\010' /* C-h */:
+ case '\003' /* C-c */:
+ case '\n':
+ case '\r':
+ return (1);
+ }
+ return (0);
+}
+
+/* Paste into prompt. */
+static int
+status_prompt_paste(struct client *c)
+{
+ struct paste_buffer *pb;
+ const char *bufdata;
+ size_t size, n, bufsize;
+ u_int i;
+ struct utf8_data *ud, *udp;
+ enum utf8_state more;
+
+ size = utf8_strlen(c->prompt_buffer);
+ if (c->prompt_saved != NULL) {
+ ud = c->prompt_saved;
+ n = utf8_strlen(c->prompt_saved);
+ } else {
+ if ((pb = paste_get_top(NULL)) == NULL)
+ return (0);
+ bufdata = paste_buffer_data(pb, &bufsize);
+ ud = xreallocarray(NULL, bufsize + 1, sizeof *ud);
+ udp = ud;
+ for (i = 0; i != bufsize; /* nothing */) {
+ more = utf8_open(udp, bufdata[i]);
+ if (more == UTF8_MORE) {
+ while (++i != bufsize && more == UTF8_MORE)
+ more = utf8_append(udp, bufdata[i]);
+ if (more == UTF8_DONE) {
+ udp++;
+ continue;
+ }
+ i -= udp->have;
+ }
+ if (bufdata[i] <= 31 || bufdata[i] >= 127)
+ break;
+ utf8_set(udp, bufdata[i]);
+ udp++;
+ i++;
+ }
+ udp->size = 0;
+ n = udp - ud;
+ }
+ if (n == 0)
+ return (0);
+
+ c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
+ sizeof *c->prompt_buffer);
+ if (c->prompt_index == size) {
+ memcpy(c->prompt_buffer + c->prompt_index, ud,
+ n * sizeof *c->prompt_buffer);
+ c->prompt_index += n;
+ c->prompt_buffer[c->prompt_index].size = 0;
+ } else {
+ memmove(c->prompt_buffer + c->prompt_index + n,
+ c->prompt_buffer + c->prompt_index,
+ (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer);
+ memcpy(c->prompt_buffer + c->prompt_index, ud,
+ n * sizeof *c->prompt_buffer);
+ c->prompt_index += n;
+ }
+
+ if (ud != c->prompt_saved)
+ free(ud);
+ return (1);
+}
+
+/* Finish completion. */
+static int
+status_prompt_replace_complete(struct client *c, const char *s)
+{
+ char word[64], *allocated = NULL;
+ size_t size, n, off, idx, used;
+ struct utf8_data *first, *last, *ud;
+
+ /* Work out where the cursor currently is. */
+ idx = c->prompt_index;
+ if (idx != 0)
+ idx--;
+ size = utf8_strlen(c->prompt_buffer);
+
+ /* Find the word we are in. */
+ first = &c->prompt_buffer[idx];
+ while (first > c->prompt_buffer && !status_prompt_space(first))
+ first--;
+ while (first->size != 0 && status_prompt_space(first))
+ first++;
+ last = &c->prompt_buffer[idx];
+ while (last->size != 0 && !status_prompt_space(last))
+ last++;
+ while (last > c->prompt_buffer && status_prompt_space(last))
+ last--;
+ if (last->size != 0)
+ last++;
+ if (last < first)
+ return (0);
+ if (s == NULL) {
+ used = 0;
+ for (ud = first; ud < last; ud++) {
+ if (used + ud->size >= sizeof word)
+ break;
+ memcpy(word + used, ud->data, ud->size);
+ used += ud->size;
+ }
+ if (ud != last)
+ return (0);
+ word[used] = '\0';
+ }
+
+ /* Try to complete it. */
+ if (s == NULL) {
+ allocated = status_prompt_complete(c, word,
+ first - c->prompt_buffer);
+ if (allocated == NULL)
+ return (0);
+ s = allocated;
+ }
+
+ /* Trim out word. */
+ n = size - (last - c->prompt_buffer) + 1; /* with \0 */
+ memmove(first, last, n * sizeof *c->prompt_buffer);
+ size -= last - first;
+
+ /* Insert the new word. */
+ size += strlen(s);
+ off = first - c->prompt_buffer;
+ c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
+ sizeof *c->prompt_buffer);
+ first = c->prompt_buffer + off;
+ memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
+ for (idx = 0; idx < strlen(s); idx++)
+ utf8_set(&first[idx], s[idx]);
+ c->prompt_index = (first - c->prompt_buffer) + strlen(s);
+
+ free(allocated);
+ return (1);
+}
+
+/* Prompt forward to the next beginning of a word. */
+static void
+status_prompt_forward_word(struct client *c, size_t size, int vi,
+ const char *separators)
+{
+ size_t idx = c->prompt_index;
+ int word_is_separators;
+
+ /* In emacs mode, skip until the first non-whitespace character. */
+ if (!vi)
+ while (idx != size &&
+ status_prompt_space(&c->prompt_buffer[idx]))
+ idx++;
+
+ /* Can't move forward if we're already at the end. */
+ if (idx == size) {
+ c->prompt_index = idx;
+ return;
+ }
+
+ /* Determine the current character class (separators or not). */
+ word_is_separators = status_prompt_in_list(separators,
+ &c->prompt_buffer[idx]) &&
+ !status_prompt_space(&c->prompt_buffer[idx]);
+
+ /* Skip ahead until the first space or opposite character class. */
+ do {
+ idx++;
+ if (status_prompt_space(&c->prompt_buffer[idx])) {
+ /* In vi mode, go to the start of the next word. */
+ if (vi)
+ while (idx != size &&
+ status_prompt_space(&c->prompt_buffer[idx]))
+ idx++;
+ break;
+ }
+ } while (idx != size && word_is_separators == status_prompt_in_list(
+ separators, &c->prompt_buffer[idx]));
+
+ c->prompt_index = idx;
+}
+
+/* Prompt forward to the next end of a word. */
+static void
+status_prompt_end_word(struct client *c, size_t size, const char *separators)
+{
+ size_t idx = c->prompt_index;
+ int word_is_separators;
+
+ /* Can't move forward if we're already at the end. */
+ if (idx == size)
+ return;
+
+ /* Find the next word. */
+ do {
+ idx++;
+ if (idx == size) {
+ c->prompt_index = idx;
+ return;
+ }
+ } while (status_prompt_space(&c->prompt_buffer[idx]));
+
+ /* Determine the character class (separators or not). */
+ word_is_separators = status_prompt_in_list(separators,
+ &c->prompt_buffer[idx]);
+
+ /* Skip ahead until the next space or opposite character class. */
+ do {
+ idx++;
+ if (idx == size)
+ break;
+ } while (!status_prompt_space(&c->prompt_buffer[idx]) &&
+ word_is_separators == status_prompt_in_list(separators,
+ &c->prompt_buffer[idx]));
+
+ /* Back up to the previous character to stop at the end of the word. */
+ c->prompt_index = idx - 1;
+}
+
+/* Prompt backward to the previous beginning of a word. */
+static void
+status_prompt_backward_word(struct client *c, const char *separators)
+{
+ size_t idx = c->prompt_index;
+ int word_is_separators;
+
+ /* Find non-whitespace. */
+ while (idx != 0) {
+ --idx;
+ if (!status_prompt_space(&c->prompt_buffer[idx]))
+ break;
+ }
+ word_is_separators = status_prompt_in_list(separators,
+ &c->prompt_buffer[idx]);
+
+ /* Find the character before the beginning of the word. */
+ while (idx != 0) {
+ --idx;
+ if (status_prompt_space(&c->prompt_buffer[idx]) ||
+ word_is_separators != status_prompt_in_list(separators,
+ &c->prompt_buffer[idx])) {
+ /* Go back to the word. */
+ idx++;
+ break;
+ }
+ }
+ c->prompt_index = idx;
+}
+
+/* Handle keys in prompt. */
+int
+status_prompt_key(struct client *c, key_code key)
+{
+ struct options *oo = c->session->options;
+ char *s, *cp, prefix = '=';
+ const char *histstr, *separators = NULL, *keystring;
+ size_t size, idx;
+ struct utf8_data tmp;
+ int keys, word_is_separators;
+
+ if (c->prompt_flags & PROMPT_KEY) {
+ keystring = key_string_lookup_key(key, 0);
+ c->prompt_inputcb(c, c->prompt_data, keystring, 1);
+ status_prompt_clear(c);
+ return (0);
+ }
+ size = utf8_strlen(c->prompt_buffer);
+
+ if (c->prompt_flags & PROMPT_NUMERIC) {
+ if (key >= '0' && key <= '9')
+ goto append_key;
+ s = utf8_tocstr(c->prompt_buffer);
+ c->prompt_inputcb(c, c->prompt_data, s, 1);
+ status_prompt_clear(c);
+ free(s);
+ return (1);
+ }
+ key &= ~KEYC_MASK_FLAGS;
+
+ keys = options_get_number(c->session->options, "status-keys");
+ if (keys == MODEKEY_VI) {
+ switch (status_prompt_translate_key(c, key, &key)) {
+ case 1:
+ goto process_key;
+ case 2:
+ goto append_key;
+ default:
+ return (0);
+ }
+ }
+
+process_key:
+ switch (key) {
+ case KEYC_LEFT:
+ case '\002': /* C-b */
+ if (c->prompt_index > 0) {
+ c->prompt_index--;
+ break;
+ }
+ break;
+ case KEYC_RIGHT:
+ case '\006': /* C-f */
+ if (c->prompt_index < size) {
+ c->prompt_index++;
+ break;
+ }
+ break;
+ case KEYC_HOME:
+ case '\001': /* C-a */
+ if (c->prompt_index != 0) {
+ c->prompt_index = 0;
+ break;
+ }
+ break;
+ case KEYC_END:
+ case '\005': /* C-e */
+ if (c->prompt_index != size) {
+ c->prompt_index = size;
+ break;
+ }
+ break;
+ case '\011': /* Tab */
+ if (status_prompt_replace_complete(c, NULL))
+ goto changed;
+ break;
+ case KEYC_BSPACE:
+ case '\010': /* C-h */
+ if (c->prompt_index != 0) {
+ if (c->prompt_index == size)
+ c->prompt_buffer[--c->prompt_index].size = 0;
+ else {
+ memmove(c->prompt_buffer + c->prompt_index - 1,
+ c->prompt_buffer + c->prompt_index,
+ (size + 1 - c->prompt_index) *
+ sizeof *c->prompt_buffer);
+ c->prompt_index--;
+ }
+ goto changed;
+ }
+ break;
+ case KEYC_DC:
+ case '\004': /* C-d */
+ if (c->prompt_index != size) {
+ memmove(c->prompt_buffer + c->prompt_index,
+ c->prompt_buffer + c->prompt_index + 1,
+ (size + 1 - c->prompt_index) *
+ sizeof *c->prompt_buffer);
+ goto changed;
+ }
+ break;
+ case '\025': /* C-u */
+ c->prompt_buffer[0].size = 0;
+ c->prompt_index = 0;
+ goto changed;
+ case '\013': /* C-k */
+ if (c->prompt_index < size) {
+ c->prompt_buffer[c->prompt_index].size = 0;
+ goto changed;
+ }
+ break;
+ case '\027': /* C-w */
+ separators = options_get_string(oo, "word-separators");
+ idx = c->prompt_index;
+
+ /* Find non-whitespace. */
+ while (idx != 0) {
+ idx--;
+ if (!status_prompt_space(&c->prompt_buffer[idx]))
+ break;
+ }
+ word_is_separators = status_prompt_in_list(separators,
+ &c->prompt_buffer[idx]);
+
+ /* Find the character before the beginning of the word. */
+ while (idx != 0) {
+ idx--;
+ if (status_prompt_space(&c->prompt_buffer[idx]) ||
+ word_is_separators != status_prompt_in_list(
+ separators, &c->prompt_buffer[idx])) {
+ /* Go back to the word. */
+ idx++;
+ break;
+ }
+ }
+
+ free(c->prompt_saved);
+ c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
+ (c->prompt_index - idx) + 1);
+ memcpy(c->prompt_saved, c->prompt_buffer + idx,
+ (c->prompt_index - idx) * sizeof *c->prompt_buffer);
+
+ memmove(c->prompt_buffer + idx,
+ c->prompt_buffer + c->prompt_index,
+ (size + 1 - c->prompt_index) *
+ sizeof *c->prompt_buffer);
+ memset(c->prompt_buffer + size - (c->prompt_index - idx),
+ '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
+ c->prompt_index = idx;
+
+ goto changed;
+ case KEYC_RIGHT|KEYC_CTRL:
+ case 'f'|KEYC_META:
+ separators = options_get_string(oo, "word-separators");
+ status_prompt_forward_word(c, size, 0, separators);
+ goto changed;
+ case 'E'|KEYC_VI:
+ status_prompt_end_word(c, size, "");
+ goto changed;
+ case 'e'|KEYC_VI:
+ separators = options_get_string(oo, "word-separators");
+ status_prompt_end_word(c, size, separators);
+ goto changed;
+ case 'W'|KEYC_VI:
+ status_prompt_forward_word(c, size, 1, "");
+ goto changed;
+ case 'w'|KEYC_VI:
+ separators = options_get_string(oo, "word-separators");
+ status_prompt_forward_word(c, size, 1, separators);
+ goto changed;
+ case 'B'|KEYC_VI:
+ status_prompt_backward_word(c, "");
+ goto changed;
+ case KEYC_LEFT|KEYC_CTRL:
+ case 'b'|KEYC_META:
+ separators = options_get_string(oo, "word-separators");
+ status_prompt_backward_word(c, separators);
+ goto changed;
+ case KEYC_UP:
+ case '\020': /* C-p */
+ histstr = status_prompt_up_history(c->prompt_hindex,
+ c->prompt_type);
+ if (histstr == NULL)
+ break;
+ free(c->prompt_buffer);
+ c->prompt_buffer = utf8_fromcstr(histstr);
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+ goto changed;
+ case KEYC_DOWN:
+ case '\016': /* C-n */
+ histstr = status_prompt_down_history(c->prompt_hindex,
+ c->prompt_type);
+ if (histstr == NULL)
+ break;
+ free(c->prompt_buffer);
+ c->prompt_buffer = utf8_fromcstr(histstr);
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+ goto changed;
+ case '\031': /* C-y */
+ if (status_prompt_paste(c))
+ goto changed;
+ break;
+ case '\024': /* C-t */
+ idx = c->prompt_index;
+ if (idx < size)
+ idx++;
+ if (idx >= 2) {
+ utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
+ utf8_copy(&c->prompt_buffer[idx - 2],
+ &c->prompt_buffer[idx - 1]);
+ utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
+ c->prompt_index = idx;
+ goto changed;
+ }
+ break;
+ case '\r':
+ case '\n':
+ s = utf8_tocstr(c->prompt_buffer);
+ if (*s != '\0')
+ status_prompt_add_history(s, c->prompt_type);
+ if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
+ status_prompt_clear(c);
+ free(s);
+ break;
+ case '\033': /* Escape */
+ case '\003': /* C-c */
+ case '\007': /* C-g */
+ if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
+ status_prompt_clear(c);
+ break;
+ case '\022': /* C-r */
+ if (~c->prompt_flags & PROMPT_INCREMENTAL)
+ break;
+ if (c->prompt_buffer[0].size == 0) {
+ prefix = '=';
+ free(c->prompt_buffer);
+ c->prompt_buffer = utf8_fromcstr(c->prompt_last);
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+ } else
+ prefix = '-';
+ goto changed;
+ case '\023': /* C-s */
+ if (~c->prompt_flags & PROMPT_INCREMENTAL)
+ break;
+ if (c->prompt_buffer[0].size == 0) {
+ prefix = '=';
+ free(c->prompt_buffer);
+ c->prompt_buffer = utf8_fromcstr(c->prompt_last);
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+ } else
+ prefix = '+';
+ goto changed;
+ default:
+ goto append_key;
+ }
+
+ c->flags |= CLIENT_REDRAWSTATUS;
+ 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))
+ utf8_to_data(key, &tmp);
+ else
+ return (0);
+
+ c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
+ sizeof *c->prompt_buffer);
+
+ if (c->prompt_index == size) {
+ utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
+ c->prompt_index++;
+ c->prompt_buffer[c->prompt_index].size = 0;
+ } else {
+ memmove(c->prompt_buffer + c->prompt_index + 1,
+ c->prompt_buffer + c->prompt_index,
+ (size + 1 - c->prompt_index) *
+ sizeof *c->prompt_buffer);
+ utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
+ c->prompt_index++;
+ }
+
+ if (c->prompt_flags & PROMPT_SINGLE) {
+ if (utf8_strlen(c->prompt_buffer) != 1)
+ status_prompt_clear(c);
+ else {
+ s = utf8_tocstr(c->prompt_buffer);
+ if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
+ status_prompt_clear(c);
+ free(s);
+ }
+ }
+
+changed:
+ c->flags |= CLIENT_REDRAWSTATUS;
+ if (c->prompt_flags & PROMPT_INCREMENTAL) {
+ s = utf8_tocstr(c->prompt_buffer);
+ xasprintf(&cp, "%c%s", prefix, s);
+ c->prompt_inputcb(c, c->prompt_data, cp, 0);
+ free(cp);
+ free(s);
+ }
+ return (0);
+}
+
+/* Get previous line from the history. */
+static const char *
+status_prompt_up_history(u_int *idx, u_int type)
+{
+ /*
+ * History runs from 0 to size - 1. Index is from 0 to size. Zero is
+ * empty.
+ */
+
+ if (status_prompt_hsize[type] == 0 ||
+ idx[type] == status_prompt_hsize[type])
+ return (NULL);
+ idx[type]++;
+ return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
+}
+
+/* Get next line from the history. */
+static const char *
+status_prompt_down_history(u_int *idx, u_int type)
+{
+ if (status_prompt_hsize[type] == 0 || idx[type] == 0)
+ return ("");
+ idx[type]--;
+ if (idx[type] == 0)
+ return ("");
+ return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
+}
+
+/* Add line to the history. */
+static void
+status_prompt_add_history(const char *line, u_int type)
+{
+ u_int i, oldsize, newsize, freecount, hlimit, new = 1;
+ size_t movesize;
+
+ oldsize = status_prompt_hsize[type];
+ if (oldsize > 0 &&
+ strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0)
+ new = 0;
+
+ hlimit = options_get_number(global_options, "prompt-history-limit");
+ if (hlimit > oldsize) {
+ if (new == 0)
+ return;
+ newsize = oldsize + new;
+ } else {
+ newsize = hlimit;
+ freecount = oldsize + new - newsize;
+ if (freecount > oldsize)
+ freecount = oldsize;
+ if (freecount == 0)
+ return;
+ for (i = 0; i < freecount; i++)
+ free(status_prompt_hlist[type][i]);
+ movesize = (oldsize - freecount) *
+ sizeof *status_prompt_hlist[type];
+ if (movesize > 0) {
+ memmove(&status_prompt_hlist[type][0],
+ &status_prompt_hlist[type][freecount], movesize);
+ }
+ }
+
+ if (newsize == 0) {
+ free(status_prompt_hlist[type]);
+ status_prompt_hlist[type] = NULL;
+ } else if (newsize != oldsize) {
+ status_prompt_hlist[type] =
+ xreallocarray(status_prompt_hlist[type], newsize,
+ sizeof *status_prompt_hlist[type]);
+ }
+
+ if (new == 1 && newsize > 0)
+ status_prompt_hlist[type][newsize - 1] = xstrdup(line);
+ status_prompt_hsize[type] = newsize;
+}
+
+/* Add to completion list. */
+static void
+status_prompt_add_list(char ***list, u_int *size, const char *s)
+{
+ u_int i;
+
+ for (i = 0; i < *size; i++) {
+ if (strcmp((*list)[i], s) == 0)
+ return;
+ }
+ *list = xreallocarray(*list, (*size) + 1, sizeof **list);
+ (*list)[(*size)++] = xstrdup(s);
+}
+
+/* Build completion list. */
+static char **
+status_prompt_complete_list(u_int *size, const char *s, int at_start)
+{
+ char **list = NULL, *tmp;
+ const char **layout, *value, *cp;
+ const struct cmd_entry **cmdent;
+ const struct options_table_entry *oe;
+ size_t slen = strlen(s), valuelen;
+ struct options_entry *o;
+ struct options_array_item *a;
+ const char *layouts[] = {
+ "even-horizontal", "even-vertical", "main-horizontal",
+ "main-vertical", "tiled", NULL
+ };
+
+ *size = 0;
+ for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
+ if (strncmp((*cmdent)->name, s, slen) == 0)
+ status_prompt_add_list(&list, size, (*cmdent)->name);
+ if ((*cmdent)->alias != NULL &&
+ strncmp((*cmdent)->alias, s, slen) == 0)
+ status_prompt_add_list(&list, size, (*cmdent)->alias);
+ }
+ o = options_get_only(global_options, "command-alias");
+ if (o != NULL) {
+ a = options_array_first(o);
+ while (a != NULL) {
+ value = options_array_item_value(a)->string;
+ if ((cp = strchr(value, '=')) == NULL)
+ goto next;
+ valuelen = cp - value;
+ if (slen > valuelen || strncmp(value, s, slen) != 0)
+ goto next;
+
+ xasprintf(&tmp, "%.*s", (int)valuelen, value);
+ status_prompt_add_list(&list, size, tmp);
+ free(tmp);
+
+ next:
+ a = options_array_next(a);
+ }
+ }
+ if (at_start)
+ return (list);
+ for (oe = options_table; oe->name != NULL; oe++) {
+ if (strncmp(oe->name, s, slen) == 0)
+ status_prompt_add_list(&list, size, oe->name);
+ }
+ for (layout = layouts; *layout != NULL; layout++) {
+ if (strncmp(*layout, s, slen) == 0)
+ status_prompt_add_list(&list, size, *layout);
+ }
+ return (list);
+}
+
+/* Find longest prefix. */
+static char *
+status_prompt_complete_prefix(char **list, u_int size)
+{
+ char *out;
+ u_int i;
+ size_t j;
+
+ if (list == NULL || size == 0)
+ return (NULL);
+ out = xstrdup(list[0]);
+ for (i = 1; i < size; i++) {
+ j = strlen(list[i]);
+ if (j > strlen(out))
+ j = strlen(out);
+ for (; j > 0; j--) {
+ if (out[j - 1] != list[i][j - 1])
+ out[j - 1] = '\0';
+ }
+ }
+ return (out);
+}
+
+/* Complete word menu callback. */
+static void
+status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key,
+ void *data)
+{
+ struct status_prompt_menu *spm = data;
+ struct client *c = spm->c;
+ u_int i;
+ char *s;
+
+ if (key != KEYC_NONE) {
+ idx += spm->start;
+ if (spm->flag == '\0')
+ s = xstrdup(spm->list[idx]);
+ else
+ xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]);
+ if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
+ free(c->prompt_buffer);
+ c->prompt_buffer = utf8_fromcstr(s);
+ c->prompt_index = utf8_strlen(c->prompt_buffer);
+ c->flags |= CLIENT_REDRAWSTATUS;
+ } else if (status_prompt_replace_complete(c, s))
+ c->flags |= CLIENT_REDRAWSTATUS;
+ free(s);
+ }
+
+ for (i = 0; i < spm->size; i++)
+ free(spm->list[i]);
+ free(spm->list);
+}
+
+/* Show complete word menu. */
+static int
+status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
+ u_int offset, char flag)
+{
+ struct menu *menu;
+ struct menu_item item;
+ struct status_prompt_menu *spm;
+ u_int lines = status_line_size(c), height, i;
+ u_int py;
+
+ if (size <= 1)
+ return (0);
+ if (c->tty.sy - lines < 3)
+ return (0);
+
+ spm = xmalloc(sizeof *spm);
+ spm->c = c;
+ spm->size = size;
+ spm->list = list;
+ spm->flag = flag;
+
+ height = c->tty.sy - lines - 2;
+ if (height > 10)
+ height = 10;
+ if (height > size)
+ height = size;
+ spm->start = size - height;
+
+ menu = menu_create("");
+ for (i = spm->start; i < size; i++) {
+ item.name = list[i];
+ item.key = '0' + (i - spm->start);
+ item.command = NULL;
+ menu_add_item(menu, &item, NULL, c, NULL);
+ }
+
+ if (options_get_number(c->session->options, "status-position") == 0)
+ py = lines;
+ else
+ py = c->tty.sy - 3 - height;
+ offset += utf8_cstrwidth(c->prompt_string);
+ if (offset > 2)
+ offset -= 2;
+ else
+ offset = 0;
+
+ if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset,
+ py, c, NULL, status_prompt_menu_callback, spm) != 0) {
+ menu_free(menu);
+ free(spm);
+ return (0);
+ }
+ return (1);
+}
+
+/* Show complete word menu. */
+static char *
+status_prompt_complete_window_menu(struct client *c, struct session *s,
+ const char *word, u_int offset, char flag)
+{
+ struct menu *menu;
+ struct menu_item item;
+ struct status_prompt_menu *spm;
+ struct winlink *wl;
+ char **list = NULL, *tmp;
+ u_int lines = status_line_size(c), height;
+ u_int py, size = 0;
+
+ if (c->tty.sy - lines < 3)
+ return (NULL);
+
+ spm = xmalloc(sizeof *spm);
+ spm->c = c;
+ spm->flag = flag;
+
+ height = c->tty.sy - lines - 2;
+ if (height > 10)
+ height = 10;
+ spm->start = 0;
+
+ menu = menu_create("");
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ if (word != NULL && *word != '\0') {
+ xasprintf(&tmp, "%d", wl->idx);
+ if (strncmp(tmp, word, strlen(word)) != 0) {
+ free(tmp);
+ continue;
+ }
+ free(tmp);
+ }
+
+ list = xreallocarray(list, size + 1, sizeof *list);
+ if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
+ xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name);
+ xasprintf(&list[size++], "%d", wl->idx);
+ } else {
+ xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx,
+ wl->window->name);
+ xasprintf(&list[size++], "%s:%d", s->name, wl->idx);
+ }
+ item.name = tmp;
+ item.key = '0' + size - 1;
+ item.command = NULL;
+ menu_add_item(menu, &item, NULL, c, NULL);
+ free(tmp);
+
+ if (size == height)
+ break;
+ }
+ if (size == 0) {
+ menu_free(menu);
+ return (NULL);
+ }
+ if (size == 1) {
+ menu_free(menu);
+ if (flag != '\0') {
+ xasprintf(&tmp, "-%c%s", flag, list[0]);
+ free(list[0]);
+ } else
+ tmp = list[0];
+ free(list);
+ return (tmp);
+ }
+ if (height > size)
+ height = size;
+
+ spm->size = size;
+ spm->list = list;
+
+ if (options_get_number(c->session->options, "status-position") == 0)
+ py = lines;
+ else
+ py = c->tty.sy - 3 - height;
+ offset += utf8_cstrwidth(c->prompt_string);
+ if (offset > 2)
+ offset -= 2;
+ else
+ offset = 0;
+
+ if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset,
+ py, c, NULL, status_prompt_menu_callback, spm) != 0) {
+ menu_free(menu);
+ free(spm);
+ return (NULL);
+ }
+ return (NULL);
+}
+
+/* Sort complete list. */
+static int
+status_prompt_complete_sort(const void *a, const void *b)
+{
+ const char **aa = (const char **)a, **bb = (const char **)b;
+
+ return (strcmp(*aa, *bb));
+}
+
+/* Complete a session. */
+static char *
+status_prompt_complete_session(char ***list, u_int *size, const char *s,
+ char flag)
+{
+ struct session *loop;
+ char *out, *tmp, n[11];
+
+ RB_FOREACH(loop, sessions, &sessions) {
+ if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) {
+ *list = xreallocarray(*list, (*size) + 2,
+ sizeof **list);
+ xasprintf(&(*list)[(*size)++], "%s:", loop->name);
+ } else if (*s == '$') {
+ xsnprintf(n, sizeof n, "%u", loop->id);
+ if (s[1] == '\0' ||
+ strncmp(n, s + 1, strlen(s) - 1) == 0) {
+ *list = xreallocarray(*list, (*size) + 2,
+ sizeof **list);
+ xasprintf(&(*list)[(*size)++], "$%s:", n);
+ }
+ }
+ }
+ out = status_prompt_complete_prefix(*list, *size);
+ if (out != NULL && flag != '\0') {
+ xasprintf(&tmp, "-%c%s", flag, out);
+ free(out);
+ out = tmp;
+ }
+ return (out);
+}
+
+/* Complete word. */
+static char *
+status_prompt_complete(struct client *c, const char *word, u_int offset)
+{
+ struct session *session;
+ const char *s, *colon;
+ char **list = NULL, *copy = NULL, *out = NULL;
+ char flag = '\0';
+ u_int size = 0, i;
+
+ if (*word == '\0' &&
+ c->prompt_type != PROMPT_TYPE_TARGET &&
+ c->prompt_type != PROMPT_TYPE_WINDOW_TARGET)
+ return (NULL);
+
+ if (c->prompt_type != PROMPT_TYPE_TARGET &&
+ c->prompt_type != PROMPT_TYPE_WINDOW_TARGET &&
+ strncmp(word, "-t", 2) != 0 &&
+ strncmp(word, "-s", 2) != 0) {
+ list = status_prompt_complete_list(&size, word, offset == 0);
+ if (size == 0)
+ out = NULL;
+ else if (size == 1)
+ xasprintf(&out, "%s ", list[0]);
+ else
+ out = status_prompt_complete_prefix(list, size);
+ goto found;
+ }
+
+ if (c->prompt_type == PROMPT_TYPE_TARGET ||
+ c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
+ s = word;
+ flag = '\0';
+ } else {
+ s = word + 2;
+ flag = word[1];
+ offset += 2;
+ }
+
+ /* If this is a window completion, open the window menu. */
+ if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
+ out = status_prompt_complete_window_menu(c, c->session, s,
+ offset, '\0');
+ goto found;
+ }
+ colon = strchr(s, ':');
+
+ /* If there is no colon, complete as a session. */
+ if (colon == NULL) {
+ out = status_prompt_complete_session(&list, &size, s, flag);
+ goto found;
+ }
+
+ /* If there is a colon but no period, find session and show a menu. */
+ if (strchr(colon + 1, '.') == NULL) {
+ if (*s == ':')
+ session = c->session;
+ else {
+ copy = xstrdup(s);
+ *strchr(copy, ':') = '\0';
+ session = session_find(copy);
+ free(copy);
+ if (session == NULL)
+ goto found;
+ }
+ out = status_prompt_complete_window_menu(c, session, colon + 1,
+ offset, flag);
+ if (out == NULL)
+ return (NULL);
+ }
+
+found:
+ if (size != 0) {
+ qsort(list, size, sizeof *list, status_prompt_complete_sort);
+ for (i = 0; i < size; i++)
+ log_debug("complete %u: %s", i, list[i]);
+ }
+
+ if (out != NULL && strcmp(word, out) == 0) {
+ free(out);
+ out = NULL;
+ }
+ if (out != NULL ||
+ !status_prompt_complete_list_menu(c, list, size, offset, flag)) {
+ for (i = 0; i < size; i++)
+ free(list[i]);
+ free(list);
+ }
+ return (out);
+}
+
+/* Return the type of the prompt as an enum. */
+enum prompt_type
+status_prompt_type(const char *type)
+{
+ u_int i;
+
+ for (i = 0; i < PROMPT_NTYPES; i++) {
+ if (strcmp(type, status_prompt_type_string(i)) == 0)
+ return (i);
+ }
+ return (PROMPT_TYPE_INVALID);
+}
+
+/* Accessor for prompt_type_strings. */
+const char *
+status_prompt_type_string(u_int type)
+{
+ if (type >= PROMPT_NTYPES)
+ return ("invalid");
+ return (prompt_type_strings[type]);
+}
diff --git a/style.c b/style.c
new file mode 100644
index 0000000..89a4e63
--- /dev/null
+++ b/style.c
@@ -0,0 +1,318 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
+ * Copyright (c) 2014 Tiago Cunha <tcunha@users.sourceforge.net>
+ *
+ * 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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/* Mask for bits not included in style. */
+#define STYLE_ATTR_MASK (~0)
+
+/* Default style. */
+static struct style style_default = {
+ { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 },
+ 0,
+
+ 8,
+ STYLE_ALIGN_DEFAULT,
+ STYLE_LIST_OFF,
+
+ STYLE_RANGE_NONE, 0,
+
+ STYLE_DEFAULT_BASE
+};
+
+/*
+ * 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
+ * already.
+ */
+int
+style_parse(struct style *sy, const struct grid_cell *base, const char *in)
+{
+ struct style saved;
+ const char delimiters[] = " ,\n", *cp;
+ char tmp[256], *found;
+ int value;
+ size_t end;
+
+ if (*in == '\0')
+ return (0);
+ style_copy(&saved, sy);
+
+ log_debug("%s: %s", __func__, in);
+ do {
+ while (*in != '\0' && strchr(delimiters, *in) != NULL)
+ in++;
+ if (*in == '\0')
+ break;
+
+ end = strcspn(in, delimiters);
+ if (end > (sizeof tmp) - 1)
+ goto error;
+ memcpy(tmp, in, end);
+ tmp[end] = '\0';
+
+ log_debug("%s: %s", __func__, tmp);
+ if (strcasecmp(tmp, "default") == 0) {
+ sy->gc.fg = base->fg;
+ sy->gc.bg = base->bg;
+ sy->gc.attr = base->attr;
+ sy->gc.flags = base->flags;
+ } else if (strcasecmp(tmp, "ignore") == 0)
+ sy->ignore = 1;
+ else if (strcasecmp(tmp, "noignore") == 0)
+ sy->ignore = 0;
+ else if (strcasecmp(tmp, "push-default") == 0)
+ sy->default_type = STYLE_DEFAULT_PUSH;
+ else if (strcasecmp(tmp, "pop-default") == 0)
+ sy->default_type = STYLE_DEFAULT_POP;
+ else if (strcasecmp(tmp, "nolist") == 0)
+ sy->list = STYLE_LIST_OFF;
+ else if (strncasecmp(tmp, "list=", 5) == 0) {
+ if (strcasecmp(tmp + 5, "on") == 0)
+ sy->list = STYLE_LIST_ON;
+ else if (strcasecmp(tmp + 5, "focus") == 0)
+ sy->list = STYLE_LIST_FOCUS;
+ else if (strcasecmp(tmp + 5, "left-marker") == 0)
+ sy->list = STYLE_LIST_LEFT_MARKER;
+ else if (strcasecmp(tmp + 5, "right-marker") == 0)
+ sy->list = STYLE_LIST_RIGHT_MARKER;
+ else
+ goto error;
+ } else if (strcasecmp(tmp, "norange") == 0) {
+ sy->range_type = style_default.range_type;
+ sy->range_argument = style_default.range_type;
+ } 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;
+ } else if (strcasecmp(tmp + 6, "right") == 0) {
+ if (found != NULL)
+ goto error;
+ sy->range_type = STYLE_RANGE_RIGHT;
+ sy->range_argument = 0;
+ } else if (strcasecmp(tmp + 6, "window") == 0) {
+ if (found == NULL)
+ goto error;
+ sy->range_type = STYLE_RANGE_WINDOW;
+ sy->range_argument = atoi(found);
+ }
+ } else if (strcasecmp(tmp, "noalign") == 0)
+ sy->align = style_default.align;
+ else if (end > 6 && strncasecmp(tmp, "align=", 6) == 0) {
+ if (strcasecmp(tmp + 6, "left") == 0)
+ sy->align = STYLE_ALIGN_LEFT;
+ else if (strcasecmp(tmp + 6, "centre") == 0)
+ sy->align = STYLE_ALIGN_CENTRE;
+ else if (strcasecmp(tmp + 6, "right") == 0)
+ sy->align = STYLE_ALIGN_RIGHT;
+ else if (strcasecmp(tmp + 6, "absolute-centre") == 0)
+ sy->align = STYLE_ALIGN_ABSOLUTE_CENTRE;
+ else
+ goto error;
+ } else if (end > 5 && strncasecmp(tmp, "fill=", 5) == 0) {
+ if ((value = colour_fromstring(tmp + 5)) == -1)
+ goto error;
+ sy->fill = value;
+ } else if (end > 3 && strncasecmp(tmp + 1, "g=", 2) == 0) {
+ if ((value = colour_fromstring(tmp + 3)) == -1)
+ goto error;
+ if (*in == 'f' || *in == 'F') {
+ if (value != 8)
+ sy->gc.fg = value;
+ else
+ sy->gc.fg = base->fg;
+ } else if (*in == 'b' || *in == 'B') {
+ if (value != 8)
+ sy->gc.bg = value;
+ else
+ sy->gc.bg = base->bg;
+ } else
+ goto error;
+ } else if (strcasecmp(tmp, "none") == 0)
+ sy->gc.attr = 0;
+ else if (end > 2 && strncasecmp(tmp, "no", 2) == 0) {
+ if ((value = attributes_fromstring(tmp + 2)) == -1)
+ goto error;
+ sy->gc.attr &= ~value;
+ } else {
+ if ((value = attributes_fromstring(tmp)) == -1)
+ goto error;
+ sy->gc.attr |= value;
+ }
+
+ in += end + strspn(in + end, delimiters);
+ } while (*in != '\0');
+
+ return (0);
+
+error:
+ style_copy(sy, &saved);
+ return (-1);
+}
+
+/* Convert style to a string. */
+const char *
+style_tostring(struct style *sy)
+{
+ struct grid_cell *gc = &sy->gc;
+ int off = 0;
+ const char *comma = "", *tmp = "";
+ static char s[256];
+ char b[16];
+
+ *s = '\0';
+
+ if (sy->list != STYLE_LIST_OFF) {
+ if (sy->list == STYLE_LIST_ON)
+ tmp = "on";
+ else if (sy->list == STYLE_LIST_FOCUS)
+ tmp = "focus";
+ else if (sy->list == STYLE_LIST_LEFT_MARKER)
+ tmp = "left-marker";
+ else if (sy->list == STYLE_LIST_RIGHT_MARKER)
+ tmp = "right-marker";
+ off += xsnprintf(s + off, sizeof s - off, "%slist=%s", comma,
+ tmp);
+ comma = ",";
+ }
+ if (sy->range_type != STYLE_RANGE_NONE) {
+ if (sy->range_type == STYLE_RANGE_LEFT)
+ tmp = "left";
+ else if (sy->range_type == STYLE_RANGE_RIGHT)
+ tmp = "right";
+ else if (sy->range_type == STYLE_RANGE_WINDOW) {
+ snprintf(b, sizeof b, "window|%u", sy->range_argument);
+ tmp = b;
+ }
+ off += xsnprintf(s + off, sizeof s - off, "%srange=%s", comma,
+ tmp);
+ comma = ",";
+ }
+ if (sy->align != STYLE_ALIGN_DEFAULT) {
+ if (sy->align == STYLE_ALIGN_LEFT)
+ tmp = "left";
+ else if (sy->align == STYLE_ALIGN_CENTRE)
+ tmp = "centre";
+ else if (sy->align == STYLE_ALIGN_RIGHT)
+ tmp = "right";
+ else if (sy->align == STYLE_ALIGN_ABSOLUTE_CENTRE)
+ tmp = "absolute-centre";
+ off += xsnprintf(s + off, sizeof s - off, "%salign=%s", comma,
+ tmp);
+ comma = ",";
+ }
+ if (sy->default_type != STYLE_DEFAULT_BASE) {
+ if (sy->default_type == STYLE_DEFAULT_PUSH)
+ tmp = "push-default";
+ else if (sy->default_type == STYLE_DEFAULT_POP)
+ tmp = "pop-default";
+ off += xsnprintf(s + off, sizeof s - off, "%s%s", comma, tmp);
+ comma = ",";
+ }
+ if (sy->fill != 8) {
+ off += xsnprintf(s + off, sizeof s - off, "%sfill=%s", comma,
+ colour_tostring(sy->fill));
+ comma = ",";
+ }
+ if (gc->fg != 8) {
+ off += xsnprintf(s + off, sizeof s - off, "%sfg=%s", comma,
+ colour_tostring(gc->fg));
+ comma = ",";
+ }
+ if (gc->bg != 8) {
+ off += xsnprintf(s + off, sizeof s - off, "%sbg=%s", comma,
+ colour_tostring(gc->bg));
+ comma = ",";
+ }
+ if (gc->attr != 0) {
+ xsnprintf(s + off, sizeof s - off, "%s%s", comma,
+ attributes_tostring(gc->attr));
+ comma = ",";
+ }
+
+ if (*s == '\0')
+ return ("default");
+ return (s);
+}
+
+/* Apply a style on top of the given style. */
+void
+style_add(struct grid_cell *gc, struct options *oo, const char *name,
+ struct format_tree *ft)
+{
+ struct style *sy;
+ struct format_tree *ft0 = NULL;
+
+ if (ft == NULL)
+ ft = ft0 = format_create(NULL, NULL, 0, FORMAT_NOJOBS);
+
+ sy = options_string_to_style(oo, name, ft);
+ if (sy == NULL)
+ sy = &style_default;
+ if (sy->gc.fg != 8)
+ gc->fg = sy->gc.fg;
+ if (sy->gc.bg != 8)
+ gc->bg = sy->gc.bg;
+ gc->attr |= sy->gc.attr;
+
+ if (ft0 != NULL)
+ format_free(ft0);
+}
+
+/* Apply a style on top of the default style. */
+void
+style_apply(struct grid_cell *gc, struct options *oo, const char *name,
+ struct format_tree *ft)
+{
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ style_add(gc, oo, name, ft);
+}
+
+/* Initialize style from cell. */
+void
+style_set(struct style *sy, const struct grid_cell *gc)
+{
+ memcpy(sy, &style_default, sizeof *sy);
+ memcpy(&sy->gc, gc, sizeof sy->gc);
+}
+
+/* Copy style. */
+void
+style_copy(struct style *dst, struct style *src)
+{
+ memcpy(dst, src, sizeof *dst);
+}
diff --git a/tmux-protocol.h b/tmux-protocol.h
new file mode 100644
index 0000000..0842229
--- /dev/null
+++ b/tmux-protocol.h
@@ -0,0 +1,114 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2021 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.
+ */
+
+#ifndef TMUX_PROTOCOL_H
+#define TMUX_PROTOCOL_H
+
+/* Protocol version. */
+#define PROTOCOL_VERSION 8
+
+/* Message types. */
+enum msgtype {
+ MSG_VERSION = 12,
+
+ MSG_IDENTIFY_FLAGS = 100,
+ MSG_IDENTIFY_TERM,
+ MSG_IDENTIFY_TTYNAME,
+ MSG_IDENTIFY_OLDCWD, /* unused */
+ MSG_IDENTIFY_STDIN,
+ MSG_IDENTIFY_ENVIRON,
+ MSG_IDENTIFY_DONE,
+ MSG_IDENTIFY_CLIENTPID,
+ MSG_IDENTIFY_CWD,
+ MSG_IDENTIFY_FEATURES,
+ MSG_IDENTIFY_STDOUT,
+ MSG_IDENTIFY_LONGFLAGS,
+ MSG_IDENTIFY_TERMINFO,
+
+ MSG_COMMAND = 200,
+ MSG_DETACH,
+ MSG_DETACHKILL,
+ MSG_EXIT,
+ MSG_EXITED,
+ MSG_EXITING,
+ MSG_LOCK,
+ MSG_READY,
+ MSG_RESIZE,
+ MSG_SHELL,
+ MSG_SHUTDOWN,
+ MSG_OLDSTDERR, /* unused */
+ MSG_OLDSTDIN, /* unused */
+ MSG_OLDSTDOUT, /* unused */
+ MSG_SUSPEND,
+ MSG_UNLOCK,
+ MSG_WAKEUP,
+ MSG_EXEC,
+ MSG_FLAGS,
+
+ MSG_READ_OPEN = 300,
+ MSG_READ,
+ MSG_READ_DONE,
+ MSG_WRITE_OPEN,
+ MSG_WRITE,
+ MSG_WRITE_READY,
+ MSG_WRITE_CLOSE
+};
+
+/*
+ * Message data.
+ *
+ * Don't forget to bump PROTOCOL_VERSION if any of these change!
+ */
+struct msg_command {
+ int argc;
+}; /* followed by packed argv */
+
+struct msg_read_open {
+ int stream;
+ int fd;
+}; /* followed by path */
+
+struct msg_read_data {
+ int stream;
+};
+
+struct msg_read_done {
+ int stream;
+ int error;
+};
+
+struct msg_write_open {
+ int stream;
+ int fd;
+ int flags;
+}; /* followed by path */
+
+struct msg_write_data {
+ int stream;
+}; /* followed by data */
+
+struct msg_write_ready {
+ int stream;
+ int error;
+};
+
+struct msg_write_close {
+ int stream;
+};
+
+#endif /* TMUX_PROTOCOL_H */
diff --git a/tmux.1 b/tmux.1
new file mode 100644
index 0000000..d89ee84
--- /dev/null
+++ b/tmux.1
@@ -0,0 +1,6830 @@
+.\" $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.
+.\"
+.Dd $Mdocdate$
+.Dt TMUX 1
+.Os
+.Sh NAME
+.Nm tmux
+.Nd terminal multiplexer
+.Sh SYNOPSIS
+.Nm tmux
+.Bk -words
+.Op Fl 2CDluvV
+.Op Fl c Ar shell-command
+.Op Fl f Ar file
+.Op Fl L Ar socket-name
+.Op Fl S Ar socket-path
+.Op Fl T Ar features
+.Op Ar command Op Ar flags
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a terminal multiplexer:
+it enables a number of terminals to be created, accessed, and
+controlled from a single screen.
+.Nm
+may be detached from a screen
+and continue running in the background,
+then later reattached.
+.Pp
+When
+.Nm
+is started, it creates a new
+.Em session
+with a single
+.Em window
+and displays it on screen.
+A status line at the bottom of the screen
+shows information on the current session
+and is used to enter interactive commands.
+.Pp
+A session is a single collection of
+.Em pseudo terminals
+under the management of
+.Nm .
+Each session has one or more
+windows linked to it.
+A window occupies the entire screen
+and may be split into rectangular panes,
+each of which is a separate pseudo terminal
+(the
+.Xr pty 4
+manual page documents the technical details of pseudo terminals).
+Any number of
+.Nm
+instances may connect to the same session,
+and any number of windows may be present in the same session.
+Once all sessions are killed,
+.Nm
+exits.
+.Pp
+Each session is persistent and will survive accidental disconnection
+(such as
+.Xr ssh 1
+connection timeout) or intentional detaching (with the
+.Ql C-b d
+key strokes).
+.Nm
+may be reattached using:
+.Pp
+.Dl $ tmux attach
+.Pp
+In
+.Nm ,
+a session is displayed on screen by a
+.Em client
+and all sessions are managed by a single
+.Em server .
+The server and each client are separate processes which communicate through a
+socket in
+.Pa /tmp .
+.Pp
+The options are as follows:
+.Bl -tag -width "XXXXXXXXXXXX"
+.It Fl 2
+Force
+.Nm
+to assume the terminal supports 256 colours.
+This is equivalent to
+.Fl T Ar 256 .
+.It Fl C
+Start in control mode (see the
+.Sx CONTROL MODE
+section).
+Given twice
+.Xo ( Fl CC ) Xc
+disables echo.
+.It Fl c Ar shell-command
+Execute
+.Ar shell-command
+using the default shell.
+If necessary, the
+.Nm
+server will be started to retrieve the
+.Ic default-shell
+option.
+This option is for compatibility with
+.Xr sh 1
+when
+.Nm
+is used as a login shell.
+.It Fl D
+Do not start the
+.Nm
+server as a daemon.
+This also turns the
+.Ic exit-empty
+option off.
+With
+.Fl D ,
+.Ar command
+may not be specified.
+.It Fl f Ar file
+Specify an alternative configuration file.
+By default,
+.Nm
+loads the system configuration file from
+.Pa @SYSCONFDIR@/tmux.conf ,
+if present, then looks for a user configuration file at
+.Pa ~/.tmux.conf,
+.Pa $XDG_CONFIG_HOME/tmux/tmux.conf
+or
+.Pa ~/.config/tmux/tmux.conf .
+.Pp
+The configuration file is a set of
+.Nm
+commands which are executed in sequence when the server is first started.
+.Nm
+loads configuration files once when the server process has started.
+The
+.Ic source-file
+command may be used to load a file later.
+.Pp
+.Nm
+shows any error messages from commands in configuration files in the first
+session created, and continues to process the rest of the configuration file.
+.It Fl L Ar socket-name
+.Nm
+stores the server socket in a directory under
+.Ev TMUX_TMPDIR
+or
+.Pa /tmp
+if it is unset.
+The default socket is named
+.Em default .
+This option allows a different socket name to be specified, allowing several
+independent
+.Nm
+servers to be run.
+Unlike
+.Fl S
+a full path is not necessary: the sockets are all created in a directory
+.Pa tmux-UID
+under the directory given by
+.Ev TMUX_TMPDIR
+or in
+.Pa /tmp .
+The
+.Pa tmux-UID
+directory is created by
+.Nm
+and must not be world readable, writable or executable.
+.Pp
+If the socket is accidentally removed, the
+.Dv SIGUSR1
+signal may be sent to the
+.Nm
+server process to recreate it (note that this will fail if any parent
+directories are missing).
+.It Fl l
+Behave as a login shell.
+This flag currently has no effect and is for compatibility with other shells
+when using tmux as a login shell.
+.It Fl N
+Do not start the server even if the command would normally do so (for example
+.Ic new-session
+or
+.Ic start-server ) .
+.It Fl S Ar socket-path
+Specify a full alternative path to the server socket.
+If
+.Fl S
+is specified, the default socket directory is not used and any
+.Fl L
+flag is ignored.
+.It Fl u
+Write UTF-8 output to the terminal even if the first environment
+variable of
+.Ev LC_ALL ,
+.Ev LC_CTYPE ,
+or
+.Ev LANG
+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
+Request verbose logging.
+Log messages will be saved into
+.Pa tmux-client-PID.log
+and
+.Pa tmux-server-PID.log
+files in the current directory, where
+.Em PID
+is the PID of the server or client process.
+If
+.Fl v
+is specified twice, an additional
+.Pa tmux-out-PID.log
+file is generated with a copy of everything
+.Nm
+writes to the terminal.
+.Pp
+The
+.Dv SIGUSR2
+signal may be sent to the
+.Nm
+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 ,
+as described in the following sections.
+If no commands are specified, the
+.Ic new-session
+command is assumed.
+.El
+.Sh DEFAULT KEY BINDINGS
+.Nm
+may be controlled from an attached client by using a key combination of a
+prefix key,
+.Ql C-b
+(Ctrl-b) by default, followed by a command key.
+.Pp
+The default command key bindings are:
+.Pp
+.Bl -tag -width "XXXXXXXXXX" -offset indent -compact
+.It C-b
+Send the prefix key (C-b) through to the application.
+.It C-o
+Rotate the panes in the current window forwards.
+.It C-z
+Suspend the
+.Nm
+client.
+.It !
+Break the current pane out of the window.
+.It \&"
+.\" "
+Split the current pane into two, top and bottom.
+.It #
+List all paste buffers.
+.It $
+Rename the current session.
+.It %
+Split the current pane into two, left and right.
+.It &
+Kill the current window.
+.It '
+Prompt for a window index to select.
+.It \&(
+Switch the attached client to the previous session.
+.It \&)
+Switch the attached client to the next session.
+.It ,
+Rename the current window.
+.It -
+Delete the most recently copied buffer of text.
+.It .
+Prompt for an index to move the current window.
+.It 0 to 9
+Select windows 0 to 9.
+.It :
+Enter the
+.Nm
+command prompt.
+.It ;
+Move to the previously active pane.
+.It =
+Choose which buffer to paste interactively from a list.
+.It \&?
+List all key bindings.
+.It D
+Choose a client to detach.
+.It L
+Switch the attached client back to the last session.
+.It \&[
+Enter copy mode to copy text or view the history.
+.It \&]
+Paste the most recently copied buffer of text.
+.It c
+Create a new window.
+.It d
+Detach the current client.
+.It f
+Prompt to search for text in open windows.
+.It i
+Display some information about the current window.
+.It l
+Move to the previously selected window.
+.It m
+Mark the current pane (see
+.Ic select-pane
+.Fl m ) .
+.It M
+Clear the marked pane.
+.It n
+Change to the next window.
+.It o
+Select the next pane in the current window.
+.It p
+Change to the previous window.
+.It q
+Briefly display pane indexes.
+.It r
+Force redraw of the attached client.
+.It s
+Select a new session for the attached client interactively.
+.It t
+Show the time.
+.It w
+Choose the current window interactively.
+.It x
+Kill the current pane.
+.It z
+Toggle zoom state of the current pane.
+.It {
+Swap the current pane with the previous pane.
+.It }
+Swap the current pane with the next pane.
+.It ~
+Show previous messages from
+.Nm ,
+if any.
+.It Page Up
+Enter copy mode and scroll one page up.
+.It Up, Down
+.It Left, Right
+Change to the pane above, below, to the left, or to the right of the current
+pane.
+.It M-1 to M-5
+Arrange panes in one of the five preset layouts: even-horizontal,
+even-vertical, main-horizontal, main-vertical, or tiled.
+.It Space
+Arrange the current window in the next preset layout.
+.It M-n
+Move to the next window with a bell or activity marker.
+.It M-o
+Rotate the panes in the current window backwards.
+.It M-p
+Move to the previous window with a bell or activity marker.
+.It C-Up, C-Down
+.It C-Left, C-Right
+Resize the current pane in steps of one cell.
+.It M-Up, M-Down
+.It M-Left, M-Right
+Resize the current pane in steps of five cells.
+.El
+.Pp
+Key bindings may be changed with the
+.Ic bind-key
+and
+.Ic unbind-key
+commands.
+.Sh COMMAND PARSING AND EXECUTION
+.Nm
+supports a large number of commands which can be used to control its
+behaviour.
+Each command is named and can accept zero or more flags and arguments.
+They may be bound to a key with the
+.Ic bind-key
+command or run from the shell prompt, a shell script, a configuration file or
+the command prompt.
+For example, the same
+.Ic set-option
+command run from the shell prompt, from
+.Pa ~/.tmux.conf
+and bound to a key may look like:
+.Bd -literal -offset indent
+$ tmux set-option -g status-style bg=cyan
+
+set-option -g status-style bg=cyan
+
+bind-key C set-option -g status-style bg=cyan
+.Ed
+.Pp
+Here, the command name is
+.Ql set-option ,
+.Ql Fl g
+is a flag and
+.Ql status-style
+and
+.Ql bg=cyan
+are arguments.
+.Pp
+.Nm
+distinguishes between command parsing and execution.
+In order to execute a command,
+.Nm
+needs it to be split up into its name and arguments.
+This is command parsing.
+If a command is run from the shell, the shell parses it; from inside
+.Nm
+or from a configuration file,
+.Nm
+does.
+Examples of when
+.Nm
+parses commands are:
+.Bl -dash -offset indent
+.It
+in a configuration file;
+.It
+typed at the command prompt (see
+.Ic command-prompt ) ;
+.It
+given to
+.Ic bind-key ;
+.It
+passed as arguments to
+.Ic if-shell
+or
+.Ic confirm-before .
+.El
+.Pp
+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 .
+Parsed commands added to the queue are executed in order.
+Some commands, like
+.Ic if-shell
+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
+.Ic if-shell )
+is parsed and again when it parses and executes its command.
+Commands like
+.Ic if-shell ,
+.Ic run-shell
+and
+.Ic display-panes
+stop execution of subsequent commands on the queue until something happens -
+.Ic if-shell
+and
+.Ic run-shell
+until a shell command finishes and
+.Ic display-panes
+until a key is pressed.
+For example, the following commands:
+.Bd -literal -offset indent
+new-session; new-window
+if-shell "true" "split-window"
+kill-session
+.Ed
+.Pp
+Will execute
+.Ic new-session ,
+.Ic new-window ,
+.Ic if-shell ,
+the shell command
+.Xr true 1 ,
+.Ic split-window
+and
+.Ic kill-session
+in that order.
+.Pp
+The
+.Sx COMMANDS
+section lists the
+.Nm
+commands and their arguments.
+.Sh PARSING SYNTAX
+This section describes the syntax of commands parsed by
+.Nm ,
+for example in a configuration file or at the command prompt.
+Note that when commands are entered into the shell, they are parsed by the shell
+- see for example
+.Xr ksh 1
+or
+.Xr csh 1 .
+.Pp
+Each command is terminated by a newline or a semicolon (;).
+Commands separated by semicolons together form a
+.Ql command sequence
+- if a command in the sequence encounters an error, no subsequent commands are
+executed.
+.Pp
+It is recommended that a semicolon used as a command separator should be
+written as an individual token, for example from
+.Xr sh 1 :
+.Bd -literal -offset indent
+$ tmux neww \\; splitw
+.Ed
+.Pp
+Or:
+.Bd -literal -offset indent
+$ tmux neww ';' splitw
+.Ed
+.Pp
+Or from the tmux command prompt:
+.Bd -literal -offset indent
+neww ; splitw
+.Ed
+.Pp
+However, a trailing semicolon is also interpreted as a command separator,
+for example in these
+.Xr sh 1
+commands:
+.Bd -literal -offset indent
+$ tmux neww\e\e; splitw
+.Ed
+.Pp
+Or:
+.Bd -literal -offset indent
+$ tmux 'neww;' splitw
+.Ed
+.Pp
+As in these examples, when running tmux from the shell extra care must be taken
+to properly quote semicolons:
+.Bl -enum -offset Ds
+.It
+Semicolons that should be interpreted as a command separator
+should be escaped according to the shell conventions.
+For
+.Xr sh 1
+this typically means quoted (such as
+.Ql neww ';' splitw )
+or escaped (such as
+.Ql neww \e\e\e\e; splitw ) .
+.It
+Individual semicolons or trailing semicolons that should be interpreted as
+arguments should be escaped twice: once according to the shell conventions and
+a second time for
+.Nm ;
+for example:
+.Bd -literal -offset indent
+$ tmux neww 'foo\e\e;' 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 foo-\e\e;-bar
+.Ed
+.El
+.Pp
+Comments are marked by the unquoted # character - any remaining text after a
+comment is ignored until the end of the line.
+.Pp
+If the last character of a line is \e, the line is joined with the following
+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 ({}).
+.\" "
+This is required when the argument contains any special character.
+Single and double quoted strings cannot span multiple lines except with line
+continuation.
+Braces can span multiple lines.
+.Pp
+Outside of quotes and inside double quotes, these replacements are performed:
+.Bl -dash -offset indent
+.It
+Environment variables preceded by $ are replaced with their value from the
+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
+specified user.
+.It
+\euXXXX or \euXXXXXXXX is replaced by the Unicode codepoint corresponding to
+the given four or eight digit hexadecimal number.
+.It
+When preceded (escaped) by a \e, the following characters are replaced: \ee by
+the escape character; \er by a carriage return; \en by a newline; and \et by a
+tab.
+.It
+\eooo is replaced by a character of the octal value ooo.
+Three octal digits are required, for example \e001.
+The largest valid character is \e377.
+.It
+Any other characters preceded by \e are replaced by themselves (that is, the \e
+is removed) and are not treated as having any special meaning - so for example
+\e; will not mark a command sequence and \e$ will not expand an environment
+variable.
+.El
+.Pp
+Braces are parsed as a configuration file (so conditions such as
+.Ql %if
+are processed) and then converted into a string.
+They are designed to avoid the need for additional escaping when passing a
+group of
+.Nm
+commands as an argument (for example to
+.Ic if-shell ) .
+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'
+}
+
+if-shell true "display -p 'brace-dollar-foo: }\e$foo'"
+.Ed
+.Pp
+Braces may be enclosed inside braces, for example:
+.Bd -literal -offset indent
+bind x if-shell "true" {
+ if-shell "true" {
+ display "true!"
+ }
+}
+.Ed
+.Pp
+Environment variables may be set by using the syntax
+.Ql name=value ,
+for example
+.Ql HOME=/home/user .
+Variables set during parsing are added to the global environment.
+A hidden variable may be set with
+.Ql %hidden ,
+for example:
+.Bd -literal -offset indent
+%hidden MYVAR=42
+.Ed
+.Pp
+Hidden variables are not passed to the environment of processes created
+by tmux.
+See the
+.Sx GLOBAL AND SESSION ENVIRONMENT
+section.
+.Pp
+Commands may be parsed conditionally by surrounding them with
+.Ql %if ,
+.Ql %elif ,
+.Ql %else
+and
+.Ql %endif .
+The argument to
+.Ql %if
+and
+.Ql %elif
+is expanded as a format (see
+.Sx FORMATS )
+and if it evaluates to false (zero or empty), subsequent text is ignored until
+the closing
+.Ql %elif ,
+.Ql %else
+or
+.Ql %endif .
+For example:
+.Bd -literal -offset indent
+%if "#{==:#{host},myhost}"
+set -g status-style bg=red
+%elif "#{==:#{host},myotherhost}"
+set -g status-style bg=green
+%else
+set -g status-style bg=blue
+%endif
+.Ed
+.Pp
+Will change the status line to red if running on
+.Ql myhost ,
+green if running on
+.Ql myotherhost ,
+or blue if running on another host.
+Conditionals may be given on one line, for example:
+.Bd -literal -offset indent
+%if #{==:#{host},myhost} set -g status-style bg=red %endif
+.Ed
+.Sh COMMANDS
+This section describes the commands supported by
+.Nm .
+Most commands accept the optional
+.Fl t
+(and sometimes
+.Fl s )
+argument with one of
+.Ar target-client ,
+.Ar target-session ,
+.Ar target-window ,
+or
+.Ar target-pane .
+These specify the client, session, window or pane which a command should affect.
+.Pp
+.Ar target-client
+should be the name of the client,
+typically the
+.Xr pty 4
+file to which the client is connected, for example either of
+.Pa /dev/ttyp1
+or
+.Pa ttyp1
+for the client attached to
+.Pa /dev/ttyp1 .
+If no client is specified,
+.Nm
+attempts to work out the client currently in use; if that fails, an error is
+reported.
+Clients may be listed with the
+.Ic list-clients
+command.
+.Pp
+.Ar target-session
+is tried as, in order:
+.Bl -enum -offset Ds
+.It
+A session ID prefixed with a $.
+.It
+An exact name of a session (as listed by the
+.Ic list-sessions
+command).
+.It
+The start of a session name, for example
+.Ql mysess
+would match a session named
+.Ql mysession .
+.It
+An
+.Xr fnmatch 3
+pattern which is matched against the session name.
+.El
+.Pp
+If the session name is prefixed with an
+.Ql = ,
+only an exact match is accepted (so
+.Ql =mysess
+will only match exactly
+.Ql mysess ,
+not
+.Ql mysession ) .
+.Pp
+If a single session is found, it is used as the target session; multiple matches
+produce an error.
+If a session is omitted, the current session is used if available; if no
+current session is available, the most recently used is chosen.
+.Pp
+.Ar target-window
+(or
+.Ar src-window
+or
+.Ar dst-window )
+specifies a window in the form
+.Em session Ns \&: Ns Em window .
+.Em session
+follows the same rules as for
+.Ar target-session ,
+and
+.Em window
+is looked for in order as:
+.Bl -enum -offset Ds
+.It
+A special token, listed below.
+.It
+A window index, for example
+.Ql mysession:1
+is window 1 in session
+.Ql mysession .
+.It
+A window ID, such as @1.
+.It
+An exact window name, such as
+.Ql mysession:mywindow .
+.It
+The start of a window name, such as
+.Ql mysession:mywin .
+.It
+As an
+.Xr fnmatch 3
+pattern matched against the window name.
+.El
+.Pp
+Like sessions, a
+.Ql =
+prefix will do an exact match only.
+An empty window name specifies the next unused index if appropriate (for
+example the
+.Ic new-window
+and
+.Ic link-window
+commands)
+otherwise the current window in
+.Em session
+is chosen.
+.Pp
+The following special tokens are available to indicate particular windows.
+Each has a single-character alternative form.
+.Bl -column "XXXXXXXXXX" "X"
+.It Sy "Token" Ta Sy "" Ta Sy "Meaning"
+.It Li "{start}" Ta "^" Ta "The lowest-numbered window"
+.It Li "{end}" Ta "$" Ta "The highest-numbered window"
+.It Li "{last}" Ta "!" Ta "The last (previously current) window"
+.It Li "{next}" Ta "+" Ta "The next window by number"
+.It Li "{previous}" Ta "-" Ta "The previous window by number"
+.El
+.Pp
+.Ar target-pane
+(or
+.Ar src-pane
+or
+.Ar dst-pane )
+may be a pane ID or takes a similar form to
+.Ar target-window
+but with the optional addition of a period followed by a pane index or pane ID,
+for example:
+.Ql mysession:mywindow.1 .
+If the pane index is omitted, the currently active pane in the specified
+window is used.
+The following special tokens are available for the pane index:
+.Bl -column "XXXXXXXXXXXXXX" "X"
+.It Sy "Token" Ta Sy "" Ta Sy "Meaning"
+.It Li "{last}" Ta "!" Ta "The last (previously active) pane"
+.It Li "{next}" Ta "+" Ta "The next pane by number"
+.It Li "{previous}" Ta "-" Ta "The previous pane by number"
+.It Li "{top}" Ta "" Ta "The top pane"
+.It Li "{bottom}" Ta "" Ta "The bottom pane"
+.It Li "{left}" Ta "" Ta "The leftmost pane"
+.It Li "{right}" Ta "" Ta "The rightmost pane"
+.It Li "{top-left}" Ta "" Ta "The top-left pane"
+.It Li "{top-right}" Ta "" Ta "The top-right pane"
+.It Li "{bottom-left}" Ta "" Ta "The bottom-left pane"
+.It Li "{bottom-right}" Ta "" Ta "The bottom-right pane"
+.It Li "{up-of}" Ta "" Ta "The pane above the active pane"
+.It Li "{down-of}" Ta "" Ta "The pane below the active pane"
+.It Li "{left-of}" Ta "" Ta "The pane to the left of the active pane"
+.It Li "{right-of}" Ta "" Ta "The pane to the right of the active pane"
+.El
+.Pp
+The tokens
+.Ql +
+and
+.Ql -
+may be followed by an offset, for example:
+.Bd -literal -offset indent
+select-window -t:+2
+.Ed
+.Pp
+In addition,
+.Em target-session ,
+.Em target-window
+or
+.Em target-pane
+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
+.Sx MOUSE SUPPORT
+section)
+or
+.Ql {marked}
+(alternative form
+.Ql ~ )
+to specify the marked pane (see
+.Ic select-pane
+.Fl m ) .
+.Pp
+Sessions, window and panes are each numbered with a unique ID; session IDs are
+prefixed with a
+.Ql $ ,
+windows with a
+.Ql @ ,
+and panes with a
+.Ql % .
+These are unique and are unchanged for the life of the session, window or pane
+in the
+.Nm
+server.
+The pane ID is passed to the child process of the pane in the
+.Ev TMUX_PANE
+environment variable.
+IDs may be displayed using the
+.Ql session_id ,
+.Ql window_id ,
+or
+.Ql pane_id
+formats (see the
+.Sx FORMATS
+section) and the
+.Ic display-message ,
+.Ic list-sessions ,
+.Ic list-windows
+or
+.Ic list-panes
+commands.
+.Pp
+.Ar shell-command
+arguments are
+.Xr sh 1
+commands.
+This may be a single argument passed to the shell, for example:
+.Bd -literal -offset indent
+new-window 'vi ~/.tmux.conf'
+.Ed
+.Pp
+Will run:
+.Bd -literal -offset indent
+/bin/sh -c 'vi ~/.tmux.conf'
+.Ed
+.Pp
+Additionally, the
+.Ic new-window ,
+.Ic new-session ,
+.Ic split-window ,
+.Ic respawn-window
+and
+.Ic respawn-pane
+commands allow
+.Ar shell-command
+to be given as multiple arguments and executed directly (without
+.Ql sh -c ) .
+This can avoid issues with shell quoting.
+For example:
+.Bd -literal -offset indent
+$ tmux new-window vi ~/.tmux.conf
+.Ed
+.Pp
+Will run
+.Xr vi 1
+directly without invoking the shell.
+.Pp
+.Ar command
+.Op Ar arguments
+refers to a
+.Nm
+command, either passed with the command and arguments separately, for example:
+.Bd -literal -offset indent
+bind-key F1 set-option status off
+.Ed
+.Pp
+Or passed as a single string argument in
+.Pa .tmux.conf ,
+for example:
+.Bd -literal -offset indent
+bind-key F1 { set-option status off }
+.Ed
+.Pp
+Example
+.Nm
+commands include:
+.Bd -literal -offset indent
+refresh-client -t/dev/ttyp2
+
+rename-session -tfirst newname
+
+set-option -wt:0 monitor-activity on
+
+new-window ; split-window -d
+
+bind-key R source-file ~/.tmux.conf \e; \e
+ display-message "source-file done"
+.Ed
+.Pp
+Or from
+.Xr sh 1 :
+.Bd -literal -offset indent
+$ 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
+.Ed
+.Sh CLIENTS AND SESSIONS
+The
+.Nm
+server manages clients, sessions, windows and panes.
+Clients are attached to sessions to interact with them, either
+when they are created with the
+.Ic new-session
+command, or later with the
+.Ic attach-session
+command.
+Each session has one or more windows
+.Em linked
+into it.
+Windows may be linked to multiple sessions and are made up of one or
+more panes,
+each of which contains a pseudo terminal.
+Commands for creating, linking and otherwise manipulating windows
+are covered
+in the
+.Sx WINDOWS AND PANES
+section.
+.Pp
+The following commands are available to manage clients and sessions:
+.Bl -tag -width Ds
+.Tg attach
+.It Xo Ic attach-session
+.Op Fl dErx
+.Op Fl c Ar working-directory
+.Op Fl f Ar flags
+.Op Fl t Ar target-session
+.Xc
+.D1 Pq alias: Ic attach
+If run from outside
+.Nm ,
+create a new client in the current terminal and attach it to
+.Ar target-session .
+If used from inside, switch the current client.
+If
+.Fl d
+is specified, any other clients attached to the session are detached.
+If
+.Fl x
+is given, send
+.Dv SIGHUP
+to the parent process of the client as well as
+detaching the client, typically causing it to exit.
+.Fl f
+sets a comma-separated list of client flags.
+The flags are:
+.Bl -tag -width Ds
+.It active-pane
+the client has an independent active pane
+.It ignore-size
+the client does not affect the size of other clients
+.It no-output
+the client does not receive pane output in control mode
+.It pause-after=seconds
+output is paused once the pane is
+.Ar seconds
+behind in control mode
+.It read-only
+the client is read-only
+.It wait-exit
+wait for an empty line input before exiting in control mode
+.El
+.Pp
+A leading
+.Ql \&!
+turns a flag off if the client is already attached.
+.Fl r
+is an alias for
+.Fl f
+.Ar read-only,ignore-size .
+When a client is read-only, only keys bound to the
+.Ic detach-client
+or
+.Ic switch-client
+commands have any effect.
+A client with the
+.Ar active-pane
+flag allows the active pane to be selected independently of the window's active
+pane used by clients without the flag.
+This only affects the cursor position and commands issued from the client;
+other features such as hooks and styles continue to use the window's active
+pane.
+.Pp
+If no server is started,
+.Ic attach-session
+will attempt to start it; this will fail unless sessions are created in the
+configuration file.
+.Pp
+The
+.Ar target-session
+rules for
+.Ic attach-session
+are slightly adjusted: if
+.Nm
+needs to select the most recently used session, it will prefer the most
+recently used
+.Em unattached
+session.
+.Pp
+.Fl c
+will set the session working directory (used for new windows) to
+.Ar working-directory .
+.Pp
+If
+.Fl E
+is used, the
+.Ic update-environment
+option will not be applied.
+.Tg detach
+.It Xo Ic detach-client
+.Op Fl aP
+.Op Fl E Ar shell-command
+.Op Fl s Ar target-session
+.Op Fl t Ar target-client
+.Xc
+.D1 Pq alias: Ic detach
+Detach the current client if bound to a key, the client specified with
+.Fl t ,
+or all clients currently attached to the session specified by
+.Fl s .
+The
+.Fl a
+option kills all but the client given with
+.Fl t .
+If
+.Fl P
+is given, send
+.Dv SIGHUP
+to the parent process of the client, typically causing it
+to exit.
+With
+.Fl E ,
+run
+.Ar shell-command
+to replace the client.
+.Tg has
+.It Ic has-session Op Fl t Ar target-session
+.D1 Pq alias: Ic has
+Report an error and exit with 1 if the specified session does not exist.
+If it does exist, exit with 0.
+.It Ic kill-server
+Kill the
+.Nm
+server and clients and destroy all sessions.
+.It Xo Ic kill-session
+.Op Fl aC
+.Op Fl t Ar target-session
+.Xc
+Destroy the given session, closing any windows linked to it and no other
+sessions, and detaching all clients attached to it.
+If
+.Fl a
+is given, all sessions but the specified one is killed.
+The
+.Fl C
+flag clears alerts (bell, activity, or silence) in all windows linked to the
+session.
+.Tg lsc
+.It Xo Ic list-clients
+.Op Fl F Ar format
+.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
+.Sx FORMATS
+section.
+If
+.Ar target-session
+is specified, list only clients connected to that session.
+.Tg lscm
+.It Xo Ic list-commands
+.Op Fl F Ar format
+.Op Ar command
+.Xc
+.D1 Pq alias: Ic lscm
+List the syntax of
+.Ar command
+or - if omitted - of all commands supported by
+.Nm .
+.Tg ls
+.It Xo Ic list-sessions
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Xc
+.D1 Pq alias: Ic ls
+List all sessions managed by the server.
+.Fl F
+specifies the format of each line and
+.Fl f
+a filter.
+Only sessions for which the filter is true are shown.
+See the
+.Sx FORMATS
+section.
+.Tg lockc
+.It Ic lock-client Op Fl t Ar target-client
+.D1 Pq alias: Ic lockc
+Lock
+.Ar target-client ,
+see the
+.Ic lock-server
+command.
+.Tg locks
+.It Ic lock-session Op Fl t Ar target-session
+.D1 Pq alias: Ic locks
+Lock all clients attached to
+.Ar target-session .
+.Tg new
+.It Xo Ic new-session
+.Op Fl AdDEPX
+.Op Fl c Ar start-directory
+.Op Fl e Ar environment
+.Op Fl f Ar flags
+.Op Fl F Ar format
+.Op Fl n Ar window-name
+.Op Fl s Ar session-name
+.Op Fl t Ar group-name
+.Op Fl x Ar width
+.Op Fl y Ar height
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic new
+Create a new session with name
+.Ar session-name .
+.Pp
+The new session is attached to the current terminal unless
+.Fl d
+is given.
+.Ar window-name
+and
+.Ar shell-command
+are the name of and shell command to execute in the initial window.
+With
+.Fl d ,
+the initial size comes from the global
+.Ic default-size
+option;
+.Fl x
+and
+.Fl y
+can be used to specify a different size.
+.Ql -
+uses the size of the current client if any.
+If
+.Fl x
+or
+.Fl y
+is given, the
+.Ic default-size
+option is set for the session.
+.Fl f
+sets a comma-separated list of client flags (see
+.Ic attach-session ) .
+.Pp
+If run from a terminal, any
+.Xr termios 4
+special characters are saved and used for new windows in the new session.
+.Pp
+The
+.Fl A
+flag makes
+.Ic new-session
+behave like
+.Ic attach-session
+if
+.Ar session-name
+already exists; in this case,
+.Fl D
+behaves like
+.Fl d
+to
+.Ic attach-session ,
+and
+.Fl X
+behaves like
+.Fl x
+to
+.Ic attach-session .
+.Pp
+If
+.Fl t
+is given, it specifies a
+.Ic session group .
+Sessions in the same group share the same set of windows - new windows are
+linked to all sessions in the group and any windows closed removed from all
+sessions.
+The current and previous window and any session options remain independent and
+any session in a group may be killed without affecting the others.
+The
+.Ar group-name
+argument may be:
+.Bl -enum -width Ds
+.It
+the name of an existing group, in which case the new session is added to that
+group;
+.It
+the name of an existing session - the new session is added to the same group
+as that session, creating a new group if necessary;
+.It
+the name for a new group containing only the new session.
+.El
+.Pp
+.Fl n
+and
+.Ar shell-command
+are invalid if
+.Fl t
+is used.
+.Pp
+The
+.Fl P
+option prints information about the new session after it has been created.
+By default, it uses the format
+.Ql #{session_name}:\&
+but a different format may be specified with
+.Fl F .
+.Pp
+If
+.Fl E
+is used, the
+.Ic update-environment
+option will not be applied.
+.Fl e
+takes the form
+.Ql VARIABLE=value
+and sets an environment variable for the newly created session; it may be
+specified multiple times.
+.Tg refresh
+.It Xo Ic refresh-client
+.Op Fl cDLRSU
+.Op Fl A Ar pane:state
+.Op Fl B Ar name:what:format
+.Op Fl C Ar size
+.Op Fl f Ar flags
+.Op Fl l Op Ar target-pane
+.Op Fl t Ar target-client
+.Op Ar adjustment
+.Xc
+.D1 Pq alias: Ic refresh
+Refresh the current client if bound to a key, or a single client if one is given
+with
+.Fl t .
+If
+.Fl S
+is specified, only update the client's status line.
+.Pp
+The
+.Fl U ,
+.Fl D ,
+.Fl L
+.Fl R ,
+and
+.Fl c
+flags allow the visible portion of a window which is larger than the client
+to be changed.
+.Fl U
+moves the visible part up by
+.Ar adjustment
+rows and
+.Fl D
+down,
+.Fl L
+left by
+.Ar adjustment
+columns and
+.Fl R
+right.
+.Fl c
+returns to tracking the cursor automatically.
+If
+.Ar adjustment
+is omitted, 1 is used.
+Note that the visible position is a property of the client not of the
+window, changing the current window in the attached session will reset
+it.
+.Pp
+.Fl C
+sets the width and height of a control mode client or of a window for a
+control mode client,
+.Ar size
+must be one of
+.Ql widthxheight
+or
+.Ql window ID:widthxheight ,
+for example
+.Ql 80x24
+or
+.Ql @0:80x24 .
+.Fl A
+allows a control mode client to trigger actions on a pane.
+The argument is a pane ID (with leading
+.Ql % ) ,
+a colon, then one of
+.Ql on ,
+.Ql off ,
+.Ql continue
+or
+.Ql pause .
+If
+.Ql off ,
+.Nm
+will not send output from the pane to the client and if all clients have turned
+the pane off, will stop reading from the pane.
+If
+.Ql continue ,
+.Nm
+will return to sending output to the pane if it was paused (manually or with the
+.Ar pause-after
+flag).
+If
+.Ql pause ,
+.Nm
+will pause the pane.
+.Fl A
+may be given multiple times for different panes.
+.Pp
+.Fl B
+sets a subscription to a format for a control mode client.
+The argument is split into three items by colons:
+.Ar name
+is a name for the subscription;
+.Ar what
+is a type of item to subscribe to;
+.Ar format
+is the format.
+After a subscription is added, changes to the format are reported with the
+.Ic %subscription-changed
+notification, at most once a second.
+If only the name is given, the subscription is removed.
+.Ar what
+may be empty to check the format only for the attached session, or one of:
+a pane ID such as
+.Ql %0 ;
+.Ql %*
+for all panes in the attached session;
+a window ID such as
+.Ql @0 ;
+or
+.Ql @*
+for all windows in the attached session.
+.Pp
+.Fl f
+sets a comma-separated list of client flags, see
+.Ic attach-session .
+.Pp
+.Fl l
+requests the clipboard from the client using the
+.Xr xterm 1
+escape sequence.
+If
+Ar target-pane
+is given, the clipboard is sent (in encoded form), otherwise it is stored in a
+new paste buffer.
+.Pp
+.Fl L ,
+.Fl R ,
+.Fl U
+and
+.Fl D
+move the visible portion of the window left, right, up or down
+by
+.Ar adjustment ,
+if the window is larger than the client.
+.Fl c
+resets so that the position follows the cursor.
+See the
+.Ic window-size
+option.
+.Tg rename
+.It Xo Ic rename-session
+.Op Fl t Ar target-session
+.Ar new-name
+.Xc
+.D1 Pq alias: Ic rename
+Rename the session to
+.Ar new-name .
+.It Xo Ic server-access
+.Op Fl adlrw
+.Op Ar user
+.Xc
+Change the access or read/write permission of
+.Ar user .
+The user running the
+.Nm
+server (its owner) and the root user cannot be changed and are always
+permitted access.
+.Pp
+.Fl a
+and
+.Fl d
+are used to give or revoke access for the specified user.
+If the user is already attached, the
+.Fl d
+flag causes their clients to be detached.
+.Pp
+.Fl r
+and
+.Fl w
+change the permissions for
+.Ar user :
+.Fl r
+makes their clients read-only and
+.Fl w
+writable.
+.Fl l
+lists current access permissions.
+.Pp
+By default, the access list is empty and
+.Nm
+creates sockets with file system permissions preventing access by any user
+other than the owner (and root).
+These permissions must be changed manually.
+Great care should be taken not to allow access to untrusted users even
+read-only.
+.Tg showmsgs
+.It Xo Ic show-messages
+.Op Fl JT
+.Op Fl t Ar target-client
+.Xc
+.D1 Pq alias: Ic showmsgs
+Show server messages or information.
+Messages are stored, up to a maximum of the limit set by the
+.Ar message-limit
+server option.
+.Fl J
+and
+.Fl T
+show debugging information about jobs and terminals.
+.Tg source
+.It Xo Ic source-file
+.Op Fl Fnqv
+.Ar path
+.Ar ...
+.Xc
+.D1 Pq alias: Ic source
+Execute commands from one or more files specified by
+.Ar path
+(which may be
+.Xr glob 7
+patterns).
+If
+.Fl F
+is present, then
+.Ar path
+is expanded as a format.
+If
+.Fl q
+is given, no error will be returned if
+.Ar path
+does not exist.
+With
+.Fl n ,
+the file is parsed but no commands are executed.
+.Fl v
+shows the parsed commands and line numbers if possible.
+.Tg start
+.It Ic start-server
+.D1 Pq alias: Ic start
+Start the
+.Nm
+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 ,
+.Ic exit-empty
+is turned off, or another command is run as part of the same command sequence.
+For example:
+.Bd -literal -offset indent
+$ tmux start \\; show -g
+.Ed
+.Tg suspendc
+.It Xo Ic suspend-client
+.Op Fl t Ar target-client
+.Xc
+.D1 Pq alias: Ic suspendc
+Suspend a client by sending
+.Dv SIGTSTP
+(tty stop).
+.Tg switchc
+.It Xo Ic switch-client
+.Op Fl ElnprZ
+.Op Fl c Ar target-client
+.Op Fl t Ar target-session
+.Op Fl T Ar key-table
+.Xc
+.D1 Pq alias: Ic switchc
+Switch the current session for client
+.Ar target-client
+to
+.Ar target-session .
+As a special case,
+.Fl t
+may refer to a pane (a target that contains
+.Ql \&: ,
+.Ql \&.
+or
+.Ql % ) ,
+to change session, window and pane.
+In that case,
+.Fl Z
+keeps the window zoomed if it was zoomed.
+If
+.Fl l ,
+.Fl n
+or
+.Fl p
+is used, the client is moved to the last, next or previous session
+respectively.
+.Fl r
+toggles the client
+.Ic read-only
+and
+.Ic ignore-size
+flags (see the
+.Ic attach-session
+command).
+.Pp
+If
+.Fl E
+is used,
+.Ic update-environment
+option will not be applied.
+.Pp
+.Fl T
+sets the client's key table; the next key from the client will be interpreted
+from
+.Ar key-table .
+This may be used to configure multiple prefix keys, or to bind commands to
+sequences of keys.
+For example, to make typing
+.Ql abc
+run the
+.Ic list-keys
+command:
+.Bd -literal -offset indent
+bind-key -Ttable2 c list-keys
+bind-key -Ttable1 b switch-client -Ttable2
+bind-key -Troot a switch-client -Ttable1
+.Ed
+.El
+.Sh WINDOWS AND PANES
+Each window displayed by
+.Nm
+may be split into one or more
+.Em panes ;
+each pane takes up a certain area of the display and is a separate terminal.
+A window may be split into panes using the
+.Ic split-window
+command.
+Windows may be split horizontally (with the
+.Fl h
+flag) or vertically.
+Panes may be resized with the
+.Ic resize-pane
+command (bound to
+.Ql C-Up ,
+.Ql C-Down
+.Ql C-Left
+and
+.Ql C-Right
+by default), the current pane may be changed with the
+.Ic select-pane
+command and the
+.Ic rotate-window
+and
+.Ic swap-pane
+commands may be used to swap panes without changing their position.
+Panes are numbered beginning from zero in the order they are created.
+.Pp
+By default, a
+.Nm
+pane permits direct access to the terminal contained in the pane.
+A pane may also be put into one of several modes:
+.Bl -dash -offset indent
+.It
+Copy mode, which permits a section of a window or its
+history to be copied to a
+.Em paste buffer
+for later insertion into another window.
+This mode is entered with the
+.Ic copy-mode
+command, bound to
+.Ql \&[
+by default.
+Copied text can be pasted with the
+.Ic paste-buffer
+command, bound to
+.Ql \&] .
+.It
+View mode, which is like copy mode but is entered when a command that produces
+output, such as
+.Ic list-keys ,
+is executed from a key binding.
+.It
+Choose mode, which allows an item to be chosen from a list.
+This may be a client, a session or window or pane, or a buffer.
+This mode is entered with the
+.Ic choose-buffer ,
+.Ic choose-client
+and
+.Ic choose-tree
+commands.
+.El
+.Pp
+In copy mode an indicator is displayed in the top-right corner of the pane with
+the current position and the number of lines in the history.
+.Pp
+Commands are sent to copy mode using the
+.Fl X
+flag to the
+.Ic send-keys
+command.
+When a key is pressed, copy mode automatically uses one of two key tables,
+depending on the
+.Ic mode-keys
+option:
+.Ic copy-mode
+for emacs, or
+.Ic copy-mode-vi
+for vi.
+Key tables may be viewed with the
+.Ic list-keys
+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"
+.El
+.Pp
+The search commands come in several varieties:
+.Ql search-forward
+and
+.Ql search-backward
+search for a regular expression;
+the
+.Ql -text
+variants search for a plain text string rather than a regular expression;
+.Ql -incremental
+perform an incremental search and expect to be used with the
+.Fl i
+flag to the
+.Ic command-prompt
+command.
+.Ql search-again
+repeats the last search and
+.Ql search-reverse
+does the same but reverses the direction (forward becomes backward and backward
+becomes forward).
+.Pp
+Copy commands may take an optional buffer prefix argument which is used
+to generate the buffer name (the default is
+.Ql buffer
+so buffers are named
+.Ql buffer0 ,
+.Ql buffer1
+and so on).
+Pipe commands take a command argument which is the command to which the
+selected text is piped.
+.Ql copy-pipe
+variants also copy the selection.
+The
+.Ql -and-cancel
+variants of some commands exit copy mode after they have completed (for copy
+commands) or when the cursor reaches the bottom (for scrolling commands).
+.Ql -no-clear
+variants do not clear the selection.
+.Pp
+The next and previous word keys skip over whitespace and treat consecutive
+runs of either word separators or other letters as words.
+Word separators can be customized with the
+.Em word-separators
+session option.
+Next word moves to the start of the next word, next word end to the end of the
+next word and previous word to the start of the previous word.
+The three next and previous space keys work similarly but use a space alone as
+the word separator.
+Setting
+.Em word-separators
+to the empty string makes next/previous word equivalent to next/previous space.
+.Pp
+The jump commands enable quick movement within a line.
+For instance, typing
+.Ql f
+followed by
+.Ql /
+will move the cursor to the next
+.Ql /
+character on the current line.
+A
+.Ql \&;
+will then jump to the next occurrence.
+.Pp
+Commands in copy mode may be prefaced by an optional repeat count.
+With vi key bindings, a prefix is entered using the number keys; with
+emacs, the Alt (meta) key and a number begins prefix entry.
+.Pp
+The synopsis for the
+.Ic copy-mode
+command is:
+.Bl -tag -width Ds
+.It Xo Ic copy-mode
+.Op Fl eHMqu
+.Op Fl s Ar src-pane
+.Op Fl t Ar target-pane
+.Xc
+Enter copy mode.
+The
+.Fl u
+option scrolls one page up.
+.Fl M
+begins a mouse drag (only valid if bound to a mouse key binding, see
+.Sx MOUSE SUPPORT ) .
+.Fl H
+hides the position indicator in the top right.
+.Fl q
+cancels copy mode and any other modes.
+.Fl s
+copies from
+.Ar src-pane
+instead of
+.Ar target-pane .
+.Pp
+.Fl e
+specifies that scrolling to the bottom of the history (to the visible screen)
+should exit copy mode.
+While in copy mode, pressing a key other than those used for scrolling will
+disable this behaviour.
+This is intended to allow fast scrolling through a pane's history, for
+example with:
+.Bd -literal -offset indent
+bind PageUp copy-mode -eu
+.Ed
+.El
+.Pp
+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
+.Ic next-layout
+(bound to
+.Ql Space
+by default); once a layout is chosen, panes within it may be moved and resized
+as normal.
+.Pp
+The following layouts are supported:
+.Bl -tag -width Ds
+.It Ic even-horizontal
+Panes are spread out evenly from left to right across the window.
+.It Ic even-vertical
+Panes are spread evenly from top to bottom.
+.It Ic main-horizontal
+A large (main) pane is shown at the top of the window and the remaining panes
+are spread from left to right in the leftover space at the bottom.
+Use the
+.Em main-pane-height
+window option to specify the height of the top pane.
+.It Ic main-vertical
+Similar to
+.Ic main-horizontal
+but the large pane is placed on the left and the others spread from top to
+bottom along the right.
+See the
+.Em main-pane-width
+window option.
+.It Ic tiled
+Panes are spread out as evenly as possible over the window in both rows and
+columns.
+.El
+.Pp
+In addition,
+.Ic select-layout
+may be used to apply a previously used layout - the
+.Ic list-windows
+command displays the layout of each window in a form suitable for use with
+.Ic select-layout .
+For example:
+.Bd -literal -offset indent
+$ 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}
+.Ed
+.Pp
+.Nm
+automatically adjusts the size of the layout for the current window size.
+Note that a layout cannot be applied to a window with more panes than that
+from which the layout was originally defined.
+.Pp
+Commands related to windows and panes are as follows:
+.Bl -tag -width Ds
+.Tg breakp
+.It Xo Ic break-pane
+.Op Fl abdP
+.Op Fl F Ar format
+.Op Fl n Ar window-name
+.Op Fl s Ar src-pane
+.Op Fl t Ar dst-window
+.Xc
+.D1 Pq alias: Ic breakp
+Break
+.Ar src-pane
+off from its containing window to make it the only pane in
+.Ar dst-window .
+With
+.Fl a
+or
+.Fl b ,
+the window is moved to the next index after or before (existing windows are
+moved if necessary).
+If
+.Fl d
+is given, the new window does not become the current window.
+The
+.Fl P
+option prints information about the new window after it has been created.
+By default, it uses the format
+.Ql #{session_name}:#{window_index}.#{pane_index}
+but a different format may be specified with
+.Fl F .
+.Tg capturep
+.It Xo Ic capture-pane
+.Op Fl aepPqCJN
+.Op Fl b Ar buffer-name
+.Op Fl E Ar end-line
+.Op Fl S Ar start-line
+.Op Fl t Ar target-pane
+.Xc
+.D1 Pq alias: Ic capturep
+Capture the contents of a pane.
+If
+.Fl p
+is given, the output goes to stdout, otherwise to the buffer specified with
+.Fl b
+or a new buffer if omitted.
+If
+.Fl a
+is given, the alternate screen is used, and the history is not accessible.
+If no alternate screen exists, an error will be returned unless
+.Fl q
+is given.
+If
+.Fl e
+is given, the output includes escape sequences for text and background
+attributes.
+.Fl C
+also escapes non-printable characters as octal \exxx.
+.Fl N
+preserves trailing spaces at each line's end and
+.Fl J
+preserves trailing spaces and joins any wrapped lines.
+.Fl P
+captures only any output that the pane has received that is the beginning of an
+as-yet incomplete escape sequence.
+.Pp
+.Fl S
+and
+.Fl E
+specify the starting and ending line numbers, zero is the first line of the
+visible pane and negative numbers are lines in the history.
+.Ql -
+to
+.Fl S
+is the start of the history and to
+.Fl E
+the end of the visible pane.
+The default is to capture only the visible contents of the pane.
+.It Xo
+.Ic choose-client
+.Op Fl NrZ
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Op Fl K Ar key-format
+.Op Fl O Ar sort-order
+.Op Fl t Ar target-pane
+.Op Ar template
+.Xc
+Put a pane into client mode, allowing a client to be selected interactively from
+a list.
+Each client is shown on one line.
+A shortcut key is shown on the left in brackets allowing for immediate choice,
+or the list may be navigated and an item chosen or otherwise manipulated using
+the keys below.
+.Fl Z
+zooms the pane.
+The following keys may be used in client mode:
+.Bl -column "Key" "Function" -offset indent
+.It Sy "Key" Ta Sy "Function"
+.It Li "Enter" Ta "Choose selected client"
+.It Li "Up" Ta "Select previous client"
+.It Li "Down" Ta "Select next client"
+.It Li "C-s" Ta "Search by name"
+.It Li "n" Ta "Repeat last search"
+.It Li "t" Ta "Toggle if client is tagged"
+.It Li "T" Ta "Tag no clients"
+.It Li "C-t" Ta "Tag all clients"
+.It Li "d" Ta "Detach selected client"
+.It Li "D" Ta "Detach tagged clients"
+.It Li "x" Ta "Detach and HUP selected client"
+.It Li "X" Ta "Detach and HUP tagged clients"
+.It Li "z" Ta "Suspend selected client"
+.It Li "Z" Ta "Suspend tagged clients"
+.It Li "f" Ta "Enter a format to filter items"
+.It Li "O" Ta "Change sort field"
+.It Li "r" Ta "Reverse sort order"
+.It Li "v" Ta "Toggle preview"
+.It Li "q" Ta "Exit mode"
+.El
+.Pp
+After a client is chosen,
+.Ql %%
+is replaced by the client name in
+.Ar template
+and the result executed as a command.
+If
+.Ar template
+is not given, "detach-client -t '%%'" is used.
+.Pp
+.Fl O
+specifies the initial sort field: one of
+.Ql name ,
+.Ql size ,
+.Ql creation ,
+or
+.Ql activity .
+.Fl r
+reverses the sort order.
+.Fl f
+specifies an initial filter: the filter is a format - if it evaluates to zero,
+the item in the list is not shown, otherwise it is shown.
+If a filter would lead to an empty list, it is ignored.
+.Fl F
+specifies the format for each item in the list and
+.Fl K
+a format for each shortcut key; both are evaluated once for each line.
+.Fl N
+starts without the preview.
+This command works only if at least one client is attached.
+.It Xo
+.Ic choose-tree
+.Op Fl GNrswZ
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Op Fl K Ar key-format
+.Op Fl O Ar sort-order
+.Op Fl t Ar target-pane
+.Op Ar template
+.Xc
+Put a pane into tree mode, where a session, window or pane may be chosen
+interactively from a tree.
+Each session, window or pane is shown on one line.
+A shortcut key is shown on the left in brackets allowing for immediate choice,
+or the tree may be navigated and an item chosen or otherwise manipulated using
+the keys below.
+.Fl s
+starts with sessions collapsed and
+.Fl w
+with windows collapsed.
+.Fl Z
+zooms the pane.
+The following keys may be used in tree mode:
+.Bl -column "Key" "Function" -offset indent
+.It Sy "Key" Ta Sy "Function"
+.It Li "Enter" Ta "Choose selected item"
+.It Li "Up" Ta "Select previous item"
+.It Li "Down" Ta "Select next item"
+.It Li "+" Ta "Expand selected item"
+.It Li "-" Ta "Collapse selected item"
+.It Li "M-+" Ta "Expand all items"
+.It Li "M--" Ta "Collapse all items"
+.It Li "x" Ta "Kill selected item"
+.It Li "X" Ta "Kill tagged items"
+.It Li "<" Ta "Scroll list of previews left"
+.It Li ">" Ta "Scroll list of previews right"
+.It Li "C-s" Ta "Search by name"
+.It Li "m" Ta "Set the marked pane"
+.It Li "M" Ta "Clear the marked pane"
+.It Li "n" Ta "Repeat last search"
+.It Li "t" Ta "Toggle if item is tagged"
+.It Li "T" Ta "Tag no items"
+.It Li "C-t" Ta "Tag all items"
+.It Li "\&:" Ta "Run a command for each tagged item"
+.It Li "f" Ta "Enter a format to filter items"
+.It Li "H" Ta "Jump to the starting pane"
+.It Li "O" Ta "Change sort field"
+.It Li "r" Ta "Reverse sort order"
+.It Li "v" Ta "Toggle preview"
+.It Li "q" Ta "Exit mode"
+.El
+.Pp
+After a session, window or pane is chosen, the first instance of
+.Ql %%
+and all instances of
+.Ql %1
+are replaced by the target in
+.Ar template
+and the result executed as a command.
+If
+.Ar template
+is not given, "switch-client -t '%%'" is used.
+.Pp
+.Fl O
+specifies the initial sort field: one of
+.Ql index ,
+.Ql name ,
+or
+.Ql time .
+.Fl r
+reverses the sort order.
+.Fl f
+specifies an initial filter: the filter is a format - if it evaluates to zero,
+the item in the list is not shown, otherwise it is shown.
+If a filter would lead to an empty list, it is ignored.
+.Fl F
+specifies the format for each item in the tree and
+.Fl K
+a format for each shortcut key; both are evaluated once for each line.
+.Fl N
+starts without the preview.
+.Fl G
+includes all sessions in any session groups in the tree rather than only the
+first.
+This command works only if at least one client is attached.
+.It Xo
+.Ic customize-mode
+.Op Fl NZ
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Op Fl t Ar target-pane
+.Op Ar template
+.Xc
+Put a pane into customize mode, where options and key bindings may be browsed
+and modified from a list.
+Option values in the list are shown for the active pane in the current window.
+.Fl Z
+zooms the pane.
+The following keys may be used in customize mode:
+.Bl -column "Key" "Function" -offset indent
+.It Sy "Key" Ta Sy "Function"
+.It Li "Enter" Ta "Set pane, window, session or global option value"
+.It Li "Up" Ta "Select previous item"
+.It Li "Down" Ta "Select next item"
+.It Li "+" Ta "Expand selected item"
+.It Li "-" Ta "Collapse selected item"
+.It Li "M-+" Ta "Expand all items"
+.It Li "M--" Ta "Collapse all items"
+.It Li "s" Ta "Set option value or key attribute"
+.It Li "S" Ta "Set global option value"
+.It Li "w" Ta "Set window option value, if option is for pane and window"
+.It Li "d" Ta "Set an option or key to the default"
+.It Li "D" Ta "Set tagged options and tagged keys to the default"
+.It Li "u" Ta "Unset an option (set to default value if global) or unbind a key"
+.It Li "U" Ta "Unset tagged options and unbind tagged keys"
+.It Li "C-s" Ta "Search by name"
+.It Li "n" Ta "Repeat last search"
+.It Li "t" Ta "Toggle if item is tagged"
+.It Li "T" Ta "Tag no items"
+.It Li "C-t" Ta "Tag all items"
+.It Li "f" Ta "Enter a format to filter items"
+.It Li "v" Ta "Toggle option information"
+.It Li "q" Ta "Exit mode"
+.El
+.Pp
+.Fl f
+specifies an initial filter: the filter is a format - if it evaluates to zero,
+the item in the list is not shown, otherwise it is shown.
+If a filter would lead to an empty list, it is ignored.
+.Fl F
+specifies the format for each item in the tree.
+.Fl N
+starts without the option information.
+This command works only if at least one client is attached.
+.It Xo
+.Tg displayp
+.Ic display-panes
+.Op Fl bN
+.Op Fl d Ar duration
+.Op Fl t Ar target-client
+.Op Ar template
+.Xc
+.D1 Pq alias: Ic displayp
+Display a visible indicator of each pane shown by
+.Ar target-client .
+See the
+.Ic display-panes-colour
+and
+.Ic display-panes-active-colour
+session options.
+The indicator is closed when a key is pressed (unless
+.Fl N
+is given) or
+.Ar duration
+milliseconds have passed.
+If
+.Fl d
+is not given,
+.Ic display-panes-time
+is used.
+A duration of zero means the indicator stays until a key is pressed.
+While the indicator is on screen, a pane may be chosen with the
+.Ql 0
+to
+.Ql 9
+keys, which will cause
+.Ar template
+to be executed as a command with
+.Ql %%
+substituted by the pane ID.
+The default
+.Ar template
+is "select-pane -t '%%'".
+With
+.Fl b ,
+other commands are not blocked from running until the indicator is closed.
+.Tg findw
+.It Xo Ic find-window
+.Op Fl iCNrTZ
+.Op Fl t Ar target-pane
+.Ar match-string
+.Xc
+.D1 Pq alias: Ic findw
+Search for a
+.Xr fnmatch 3
+pattern or, with
+.Fl r ,
+regular expression
+.Ar match-string
+in window names, titles, and visible content (but not history).
+The flags control matching behavior:
+.Fl C
+matches only visible window contents,
+.Fl N
+matches only the window name and
+.Fl T
+matches only the window title.
+.Fl i
+makes the search ignore case.
+The default is
+.Fl CNT .
+.Fl Z
+zooms the pane.
+.Pp
+This command works only if at least one client is attached.
+.Tg joinp
+.It Xo Ic join-pane
+.Op Fl bdfhv
+.Op Fl l Ar size
+.Op Fl s Ar src-pane
+.Op Fl t Ar dst-pane
+.Xc
+.D1 Pq alias: Ic joinp
+Like
+.Ic split-window ,
+but instead of splitting
+.Ar dst-pane
+and creating a new pane, split it and move
+.Ar src-pane
+into the space.
+This can be used to reverse
+.Ic break-pane .
+The
+.Fl b
+option causes
+.Ar src-pane
+to be joined to left of or above
+.Ar dst-pane .
+.Pp
+If
+.Fl s
+is omitted and a marked pane is present (see
+.Ic select-pane
+.Fl m ) ,
+the marked pane is used rather than the current pane.
+.Tg killp
+.It Xo Ic kill-pane
+.Op Fl a
+.Op Fl t Ar target-pane
+.Xc
+.D1 Pq alias: Ic killp
+Destroy the given pane.
+If no panes remain in the containing window, it is also destroyed.
+The
+.Fl a
+option kills all but the pane given with
+.Fl t .
+.Tg killw
+.It Xo Ic kill-window
+.Op Fl a
+.Op Fl t Ar target-window
+.Xc
+.D1 Pq alias: Ic killw
+Kill the current window or the window at
+.Ar target-window ,
+removing it from any sessions to which it is linked.
+The
+.Fl a
+option kills all but the window given with
+.Fl t .
+.Tg lastp
+.It Xo Ic last-pane
+.Op Fl deZ
+.Op Fl t Ar target-window
+.Xc
+.D1 Pq alias: Ic lastp
+Select the last (previously selected) pane.
+.Fl Z
+keeps the window zoomed if it was zoomed.
+.Fl e
+enables or
+.Fl d
+disables input to the pane.
+.Tg last
+.It Ic last-window Op Fl t Ar target-session
+.D1 Pq alias: Ic last
+Select the last (previously selected) window.
+If no
+.Ar target-session
+is specified, select the last window of the current session.
+.Tg link
+.It Xo Ic link-window
+.Op Fl abdk
+.Op Fl s Ar src-window
+.Op Fl t Ar dst-window
+.Xc
+.D1 Pq alias: Ic linkw
+Link the window at
+.Ar src-window
+to the specified
+.Ar dst-window .
+If
+.Ar dst-window
+is specified and no such window exists, the
+.Ar src-window
+is linked there.
+With
+.Fl a
+or
+.Fl b
+the window is moved to the next index after or before
+.Ar dst-window
+(existing windows are moved if necessary).
+If
+.Fl k
+is given and
+.Ar dst-window
+exists, it is killed, otherwise an error is generated.
+If
+.Fl d
+is given, the newly linked window is not selected.
+.Tg lsp
+.It Xo Ic list-panes
+.Op Fl as
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Op Fl t Ar target
+.Xc
+.D1 Pq alias: Ic lsp
+If
+.Fl a
+is given,
+.Ar target
+is ignored and all panes on the server are listed.
+If
+.Fl s
+is given,
+.Ar target
+is a session (or the current session).
+If neither is given,
+.Ar target
+is a window (or the current window).
+.Fl F
+specifies the format of each line and
+.Fl f
+a filter.
+Only panes for which the filter is true are shown.
+See the
+.Sx FORMATS
+section.
+.Tg lsw
+.It Xo Ic list-windows
+.Op Fl a
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Op Fl t Ar target-session
+.Xc
+.D1 Pq alias: Ic lsw
+If
+.Fl a
+is given, list all windows on the server.
+Otherwise, list windows in the current session or in
+.Ar target-session .
+.Fl F
+specifies the format of each line and
+.Fl f
+a filter.
+Only windows for which the filter is true are shown.
+See the
+.Sx FORMATS
+section.
+.Tg movep
+.It Xo Ic move-pane
+.Op Fl bdfhv
+.Op Fl l Ar size
+.Op Fl s Ar src-pane
+.Op Fl t Ar dst-pane
+.Xc
+.D1 Pq alias: Ic movep
+Does the same as
+.Ic join-pane .
+.Tg movew
+.It Xo Ic move-window
+.Op Fl abrdk
+.Op Fl s Ar src-window
+.Op Fl t Ar dst-window
+.Xc
+.D1 Pq alias: Ic movew
+This is similar to
+.Ic link-window ,
+except the window at
+.Ar src-window
+is moved to
+.Ar dst-window .
+With
+.Fl r ,
+all windows in the session are renumbered in sequential order, respecting
+the
+.Ic base-index
+option.
+.Tg neww
+.It Xo Ic new-window
+.Op Fl abdkPS
+.Op Fl c Ar start-directory
+.Op Fl e Ar environment
+.Op Fl F Ar format
+.Op Fl n Ar window-name
+.Op Fl t Ar target-window
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic neww
+Create a new window.
+With
+.Fl a
+or
+.Fl b ,
+the new window is inserted at the next index after or before the specified
+.Ar target-window ,
+moving windows up if necessary;
+otherwise
+.Ar target-window
+is the new window location.
+.Pp
+If
+.Fl d
+is given, the session does not make the new window the current window.
+.Ar target-window
+represents the window to be created; if the target already exists an error is
+shown, unless the
+.Fl k
+flag is used, in which case it is destroyed.
+If
+.Fl S
+is given and a window named
+.Ar window-name
+already exists, it is selected (unless
+.Fl d
+is also given in which case the command does nothing).
+.Pp
+.Ar shell-command
+is the command to execute.
+If
+.Ar shell-command
+is not specified, the value of the
+.Ic default-command
+option is used.
+.Fl c
+specifies the working directory in which the new window is created.
+.Pp
+When the shell command completes, the window closes.
+See the
+.Ic remain-on-exit
+option to change this behaviour.
+.Pp
+.Fl e
+takes the form
+.Ql VARIABLE=value
+and sets an environment variable for the newly created window; it may be
+specified multiple times.
+.Pp
+The
+.Ev TERM
+environment variable must be set to
+.Ql screen
+or
+.Ql tmux
+for all programs running
+.Em inside
+.Nm .
+New windows will automatically have
+.Ql TERM=screen
+added to their environment, but care must be taken not to reset this in shell
+start-up files or by the
+.Fl e
+option.
+.Pp
+The
+.Fl P
+option prints information about the new window after it has been created.
+By default, it uses the format
+.Ql #{session_name}:#{window_index}
+but a different format may be specified with
+.Fl F .
+.Tg nextl
+.It Ic next-layout Op Fl t Ar target-window
+.D1 Pq alias: Ic nextl
+Move a window to the next layout and rearrange the panes to fit.
+.Tg next
+.It Xo Ic next-window
+.Op Fl a
+.Op Fl t Ar target-session
+.Xc
+.D1 Pq alias: Ic next
+Move to the next window in the session.
+If
+.Fl a
+is used, move to the next window with an alert.
+.Tg pipep
+.It Xo Ic pipe-pane
+.Op Fl IOo
+.Op Fl t Ar target-pane
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic pipep
+Pipe output sent by the program in
+.Ar target-pane
+to a shell command or vice versa.
+A pane may only be connected to one command at a time, any existing pipe is
+closed before
+.Ar shell-command
+is executed.
+The
+.Ar shell-command
+string may contain the special character sequences supported by the
+.Ic status-left
+option.
+If no
+.Ar shell-command
+is given, the current pipe (if any) is closed.
+.Pp
+.Fl I
+and
+.Fl O
+specify which of the
+.Ar shell-command
+output streams are connected to the pane:
+with
+.Fl I
+stdout is connected (so anything
+.Ar shell-command
+prints is written to the pane as if it were typed);
+with
+.Fl O
+stdin is connected (so any output in the pane is piped to
+.Ar shell-command ) .
+Both may be used together and if neither are specified,
+.Fl O
+is used.
+.Pp
+The
+.Fl o
+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'
+.Ed
+.Tg prevl
+.It Xo Ic previous-layout
+.Op Fl t Ar target-window
+.Xc
+.D1 Pq alias: Ic prevl
+Move to the previous layout in the session.
+.Tg prev
+.It Xo Ic previous-window
+.Op Fl a
+.Op Fl t Ar target-session
+.Xc
+.D1 Pq alias: Ic prev
+Move to the previous window in the session.
+With
+.Fl a ,
+move to the previous window with an alert.
+.Tg renamew
+.It Xo Ic rename-window
+.Op Fl t Ar target-window
+.Ar new-name
+.Xc
+.D1 Pq alias: Ic renamew
+Rename the current window, or the window at
+.Ar target-window
+if specified, to
+.Ar new-name .
+.Tg resizep
+.It Xo Ic resize-pane
+.Op Fl DLMRTUZ
+.Op Fl t Ar target-pane
+.Op Fl x Ar width
+.Op Fl y Ar height
+.Op Ar adjustment
+.Xc
+.D1 Pq alias: Ic resizep
+Resize a pane, up, down, left or right by
+.Ar adjustment
+with
+.Fl U ,
+.Fl D ,
+.Fl L
+or
+.Fl R ,
+or
+to an absolute size
+with
+.Fl x
+or
+.Fl y .
+The
+.Ar adjustment
+is given in lines or columns (the default is 1);
+.Fl x
+and
+.Fl y
+may be a given as a number of lines or columns or followed by
+.Ql %
+for a percentage of the window size (for example
+.Ql -x 10% ) .
+With
+.Fl Z ,
+the active pane is toggled between zoomed (occupying the whole of the window)
+and unzoomed (its normal position in the layout).
+.Pp
+.Fl M
+begins mouse resizing (only valid if bound to a mouse key binding, see
+.Sx MOUSE SUPPORT ) .
+.Pp
+.Fl T
+trims all lines below the current cursor position and moves lines out of the
+history to replace them.
+.Tg resizew
+.It Xo Ic resize-window
+.Op Fl aADLRU
+.Op Fl t Ar target-window
+.Op Fl x Ar width
+.Op Fl y Ar height
+.Op Ar adjustment
+.Xc
+.D1 Pq alias: Ic resizew
+Resize a window, up, down, left or right by
+.Ar adjustment
+with
+.Fl U ,
+.Fl D ,
+.Fl L
+or
+.Fl R ,
+or
+to an absolute size
+with
+.Fl x
+or
+.Fl y .
+The
+.Ar adjustment
+is given in lines or cells (the default is 1).
+.Fl A
+sets the size of the largest session containing the window;
+.Fl a
+the size of the smallest.
+This command will automatically set
+.Ic window-size
+to manual in the window options.
+.Tg respawnp
+.It Xo Ic respawn-pane
+.Op Fl k
+.Op Fl c Ar start-directory
+.Op Fl e Ar environment
+.Op Fl t Ar target-pane
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic respawnp
+Reactivate a pane in which the command has exited (see the
+.Ic remain-on-exit
+window option).
+If
+.Ar shell-command
+is not given, the command used when the pane was created or last respawned is
+executed.
+The pane must be already inactive, unless
+.Fl k
+is given, in which case any existing command is killed.
+.Fl c
+specifies a new working directory for the pane.
+The
+.Fl e
+option has the same meaning as for the
+.Ic new-window
+command.
+.Tg respawnw
+.It Xo Ic respawn-window
+.Op Fl k
+.Op Fl c Ar start-directory
+.Op Fl e Ar environment
+.Op Fl t Ar target-window
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic respawnw
+Reactivate a window in which the command has exited (see the
+.Ic remain-on-exit
+window option).
+If
+.Ar shell-command
+is not given, the command used when the window was created or last respawned is
+executed.
+The window must be already inactive, unless
+.Fl k
+is given, in which case any existing command is killed.
+.Fl c
+specifies a new working directory for the window.
+The
+.Fl e
+option has the same meaning as for the
+.Ic new-window
+command.
+.Tg rotatew
+.It Xo Ic rotate-window
+.Op Fl DUZ
+.Op Fl t Ar target-window
+.Xc
+.D1 Pq alias: Ic rotatew
+Rotate the positions of the panes within a window, either upward (numerically
+lower) with
+.Fl U
+or downward (numerically higher).
+.Fl Z
+keeps the window zoomed if it was zoomed.
+.Tg selectl
+.It Xo Ic select-layout
+.Op Fl Enop
+.Op Fl t Ar target-pane
+.Op Ar layout-name
+.Xc
+.D1 Pq alias: Ic selectl
+Choose a specific layout for a window.
+If
+.Ar layout-name
+is not given, the last preset layout used (if any) is reapplied.
+.Fl n
+and
+.Fl p
+are equivalent to the
+.Ic next-layout
+and
+.Ic previous-layout
+commands.
+.Fl o
+applies the last set layout if possible (undoes the most recent layout change).
+.Fl E
+spreads the current pane and any panes next to it out evenly.
+.Tg selectp
+.It Xo Ic select-pane
+.Op Fl DdeLlMmRUZ
+.Op Fl T Ar title
+.Op Fl t Ar target-pane
+.Xc
+.D1 Pq alias: Ic selectp
+Make pane
+.Ar target-pane
+the active pane in its window.
+If one of
+.Fl D ,
+.Fl L ,
+.Fl R ,
+or
+.Fl U
+is used, respectively the pane below, to the left, to the right, or above the
+target pane is used.
+.Fl Z
+keeps the window zoomed if it was zoomed.
+.Fl l
+is the same as using the
+.Ic last-pane
+command.
+.Fl e
+enables or
+.Fl d
+disables input to the pane.
+.Fl T
+sets the pane title.
+.Pp
+.Fl m
+and
+.Fl M
+are used to set and clear the
+.Em marked pane .
+There is one marked pane at a time, setting a new marked pane clears the last.
+The marked pane is the default target for
+.Fl s
+to
+.Ic join-pane ,
+.Ic move-pane ,
+.Ic swap-pane
+and
+.Ic swap-window .
+.Tg selectw
+.It Xo Ic select-window
+.Op Fl lnpT
+.Op Fl t Ar target-window
+.Xc
+.D1 Pq alias: Ic selectw
+Select the window at
+.Ar target-window .
+.Fl l ,
+.Fl n
+and
+.Fl p
+are equivalent to the
+.Ic last-window ,
+.Ic next-window
+and
+.Ic previous-window
+commands.
+If
+.Fl T
+is given and the selected window is already the current window,
+the command behaves like
+.Ic last-window .
+.Tg splitw
+.It Xo Ic split-window
+.Op Fl bdfhIvPZ
+.Op Fl c Ar start-directory
+.Op Fl e Ar environment
+.Op Fl l Ar size
+.Op Fl t Ar target-pane
+.Op Ar shell-command
+.Op Fl F Ar format
+.Xc
+.D1 Pq alias: Ic splitw
+Create a new pane by splitting
+.Ar target-pane :
+.Fl h
+does a horizontal split and
+.Fl v
+a vertical split; if neither is specified,
+.Fl v
+is assumed.
+The
+.Fl l
+option specifies the size of the new pane in lines (for vertical split) or in
+columns (for horizontal split);
+.Ar size
+may be followed by
+.Ql %
+to specify a percentage of the available space.
+The
+.Fl b
+option causes the new pane to be created to the left of or above
+.Ar target-pane .
+The
+.Fl f
+option creates a new pane spanning the full window height (with
+.Fl h )
+or full window width (with
+.Fl v ) ,
+instead of splitting the active pane.
+.Fl Z
+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.
+Output can be sent to such a pane with the
+.Ic display-message
+command.
+The
+.Fl I
+flag (if
+.Ar shell-command
+is not specified or empty)
+will create an empty pane and forward any output from stdin to it.
+For example:
+.Bd -literal -offset indent
+$ make 2>&1|tmux splitw -dI &
+.Ed
+.Pp
+All other options have the same meaning as for the
+.Ic new-window
+command.
+.Tg swapp
+.It Xo Ic swap-pane
+.Op Fl dDUZ
+.Op Fl s Ar src-pane
+.Op Fl t Ar dst-pane
+.Xc
+.D1 Pq alias: Ic swapp
+Swap two panes.
+If
+.Fl U
+is used and no source pane is specified with
+.Fl s ,
+.Ar dst-pane
+is swapped with the previous pane (before it numerically);
+.Fl D
+swaps with the next pane (after it numerically).
+.Fl d
+instructs
+.Nm
+not to change the active pane and
+.Fl Z
+keeps the window zoomed if it was zoomed.
+.Pp
+If
+.Fl s
+is omitted and a marked pane is present (see
+.Ic select-pane
+.Fl m ) ,
+the marked pane is used rather than the current pane.
+.Tg swapw
+.It Xo Ic swap-window
+.Op Fl d
+.Op Fl s Ar src-window
+.Op Fl t Ar dst-window
+.Xc
+.D1 Pq alias: Ic swapw
+This is similar to
+.Ic link-window ,
+except the source and destination windows are swapped.
+It is an error if no window exists at
+.Ar src-window .
+If
+.Fl d
+is given, the new window does not become the current window.
+.Pp
+If
+.Fl s
+is omitted and a marked pane is present (see
+.Ic select-pane
+.Fl m ) ,
+the window containing the marked pane is used rather than the current window.
+.Tg unlinkw
+.It Xo Ic unlink-window
+.Op Fl k
+.Op Fl t Ar target-window
+.Xc
+.D1 Pq alias: Ic unlinkw
+Unlink
+.Ar target-window .
+Unless
+.Fl k
+is given, a window may be unlinked only if it is linked to multiple sessions -
+windows may not be linked to no sessions;
+if
+.Fl k
+is specified and the window is linked to only one session, it is unlinked and
+destroyed.
+.El
+.Sh KEY BINDINGS
+.Nm
+allows a command to be bound to most keys, with or without a prefix key.
+When specifying keys, most represent themselves (for example
+.Ql A
+to
+.Ql Z ) .
+Ctrl keys may be prefixed with
+.Ql C-
+or
+.Ql ^ ,
+Shift keys with
+.Ql S-
+and Alt (meta) with
+.Ql M- .
+In addition, the following special key names are accepted:
+.Em Up ,
+.Em Down ,
+.Em Left ,
+.Em Right ,
+.Em BSpace ,
+.Em BTab ,
+.Em DC
+(Delete),
+.Em End ,
+.Em Enter ,
+.Em Escape ,
+.Em F1
+to
+.Em F12 ,
+.Em Home ,
+.Em IC
+(Insert),
+.Em NPage/PageDown/PgDn ,
+.Em PPage/PageUp/PgUp ,
+.Em Space ,
+and
+.Em Tab .
+Note that to bind the
+.Ql \&"
+or
+.Ql '
+keys, quotation marks are necessary, for example:
+.Bd -literal -offset indent
+bind-key '"' split-window
+bind-key "'" new-window
+.Ed
+.Pp
+A command bound to the
+.Em Any
+key will execute for all keys which do not have a more specific binding.
+.Pp
+Commands related to key bindings are as follows:
+.Bl -tag -width Ds
+.Tg bind
+.It Xo Ic bind-key
+.Op Fl nr
+.Op Fl N Ar note
+.Op Fl T Ar key-table
+.Ar key command Op Ar arguments
+.Xc
+.D1 Pq alias: Ic bind
+Bind key
+.Ar key
+to
+.Ar command .
+Keys are bound in a key table.
+By default (without -T), the key is bound in
+the
+.Em prefix
+key table.
+This table is used for keys pressed after the prefix key (for example,
+by default
+.Ql c
+is bound to
+.Ic new-window
+in the
+.Em prefix
+table, so
+.Ql C-b c
+creates a new window).
+The
+.Em root
+table is used for keys pressed without the prefix key: binding
+.Ql c
+to
+.Ic new-window
+in the
+.Em root
+table (not recommended) means a plain
+.Ql c
+will create a new window.
+.Fl n
+is an alias
+for
+.Fl T Ar root .
+Keys may also be bound in custom key tables and the
+.Ic switch-client
+.Fl T
+command used to switch to them from a key binding.
+The
+.Fl r
+flag indicates this key may repeat, see the
+.Ic repeat-time
+option.
+.Fl N
+attaches a note to the key (shown with
+.Ic list-keys
+.Fl N ) .
+.Pp
+To view the default bindings and possible commands, see the
+.Ic list-keys
+command.
+.Tg lsk
+.It Xo Ic list-keys
+.Op Fl 1aN
+.Op Fl P Ar prefix-string Fl T Ar key-table
+.Op Ar key
+.Xc
+.D1 Pq alias: Ic lsk
+List key bindings.
+There are two forms: the default lists keys as
+.Ic bind-key
+commands;
+.Fl N
+lists only keys with attached notes and shows only the key and note for each
+key.
+.Pp
+With the default form, all key tables are listed by default.
+.Fl T
+lists only keys in
+.Ar key-table .
+.Pp
+With the
+.Fl N
+form, only keys in the
+.Em root
+and
+.Em prefix
+key tables are listed by default;
+.Fl T
+also lists only keys in
+.Ar key-table .
+.Fl P
+specifies a prefix to print before each key and
+.Fl 1
+lists only the first matching key.
+.Fl a
+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 N Ar repeat-count
+.Op Fl t Ar target-pane
+.Ar key Ar ...
+.Xc
+.D1 Pq alias: Ic send
+Send a key or keys to a window.
+Each argument
+.Ar key
+is the name of the key (such as
+.Ql C-a
+or
+.Ql NPage )
+to send; if the string is not recognised as a key, it is sent as a series of
+characters.
+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
+The
+.Fl l
+flag disables key name lookup and processes the keys as literal UTF-8
+characters.
+The
+.Fl H
+flag expects each key to be a hexadecimal number for an ASCII character.
+.Pp
+The
+.Fl R
+flag causes the terminal state to be reset.
+.Pp
+.Fl M
+passes through a mouse event (only valid if bound to a mouse key binding, see
+.Sx MOUSE SUPPORT ) .
+.Pp
+.Fl X
+is used to send a command into copy mode - see
+the
+.Sx WINDOWS AND PANES
+section.
+.Fl N
+specifies a repeat count and
+.Fl F
+expands formats in arguments where appropriate.
+.It Xo Ic send-prefix
+.Op Fl 2
+.Op Fl t Ar target-pane
+.Xc
+Send the prefix key, or with
+.Fl 2
+the secondary prefix key, to a window as if it was pressed.
+.Tg unbind
+.It Xo Ic unbind-key
+.Op Fl anq
+.Op Fl T Ar key-table
+.Ar key
+.Xc
+.D1 Pq alias: Ic unbind
+Unbind the command bound to
+.Ar key .
+.Fl n
+and
+.Fl T
+are the same as for
+.Ic bind-key .
+If
+.Fl a
+is present, all key bindings are removed.
+The
+.Fl q
+option prevents errors being returned.
+.El
+.Sh OPTIONS
+The appearance and behaviour of
+.Nm
+may be modified by changing the value of various options.
+There are four types of option:
+.Em server options ,
+.Em session options ,
+.Em window options ,
+and
+.Em pane options .
+.Pp
+The
+.Nm
+server has a set of global server options which do not apply to any particular
+window or session or pane.
+These are altered with the
+.Ic set-option
+.Fl s
+command, or displayed with the
+.Ic show-options
+.Fl s
+command.
+.Pp
+In addition, each individual session may have a set of session options, and
+there is a separate set of global session options.
+Sessions which do not have a particular option configured inherit the value
+from the global session options.
+Session options are set or unset with the
+.Ic set-option
+command and may be listed with the
+.Ic show-options
+command.
+The available server and session options are listed under the
+.Ic set-option
+command.
+.Pp
+Similarly, a set of window options is attached to each window and a set of pane
+options to each pane.
+Pane options inherit from window options.
+This means any pane option may be set as a window option to apply the option to
+all panes in the window without the option set, for example these commands will
+set the background colour to red for all panes except pane 0:
+.Bd -literal -offset indent
+set -w window-style bg=red
+set -pt:.0 window-style bg=blue
+.Ed
+.Pp
+There is also a set of global window options from which any unset window or
+pane options are inherited.
+Window and pane options are altered with
+.Ic set-option
+.Fl w
+and
+.Fl p
+commands and displayed with
+.Ic show-option
+.Fl w
+and
+.Fl p .
+.Pp
+.Nm
+also supports user options which are prefixed with a
+.Ql \&@ .
+User options may have any name, so long as they are prefixed with
+.Ql \&@ ,
+and be set to any string.
+For example:
+.Bd -literal -offset indent
+$ tmux set -wq @foo "abc123"
+$ tmux show -wv @foo
+abc123
+.Ed
+.Pp
+Commands which set options are as follows:
+.Bl -tag -width Ds
+.Tg set
+.It Xo Ic set-option
+.Op Fl aFgopqsuUw
+.Op Fl t Ar target-pane
+.Ar option Ar value
+.Xc
+.D1 Pq alias: Ic set
+Set a pane option with
+.Fl p ,
+a window option with
+.Fl w ,
+a server option with
+.Fl s ,
+otherwise a session option.
+If the option is not a user option,
+.Fl w
+or
+.Fl s
+may be unnecessary -
+.Nm
+will infer the type from the option name, assuming
+.Fl w
+for pane options.
+If
+.Fl g
+is given, the global session or window option is set.
+.Pp
+.Fl F
+expands formats in the option value.
+The
+.Fl u
+flag unsets an option, so a session inherits the option from the global
+options (or with
+.Fl g ,
+restores a global option to the default).
+.Fl U
+unsets an option (like
+.Fl u )
+but if the option is a pane option also unsets the option on any panes in the
+window.
+.Ar value
+depends on the option and may be a number, a string, or a flag (on, off, or
+omitted to toggle).
+.Pp
+The
+.Fl o
+flag prevents setting an option that is already set and the
+.Fl q
+flag suppresses errors about unknown or ambiguous options.
+.Pp
+With
+.Fl a ,
+and if the option expects a string or a style,
+.Ar value
+is appended to the existing setting.
+For example:
+.Bd -literal -offset indent
+set -g status-left "foo"
+set -ag status-left "bar"
+.Ed
+.Pp
+Will result in
+.Ql foobar .
+And:
+.Bd -literal -offset indent
+set -g status-style "bg=red"
+set -ag status-style "fg=blue"
+.Ed
+.Pp
+Will result in a red background
+.Em and
+blue foreground.
+Without
+.Fl a ,
+the result would be the default background and a blue foreground.
+.Tg show
+.It Xo Ic show-options
+.Op Fl AgHpqsvw
+.Op Fl t Ar target-pane
+.Op Ar option
+.Xc
+.D1 Pq alias: Ic show
+Show the pane options (or a single option if
+.Ar option
+is provided) with
+.Fl p ,
+the window options with
+.Fl w ,
+the server options with
+.Fl s ,
+otherwise the session options.
+If the option is not a user option,
+.Fl w
+or
+.Fl s
+may be unnecessary -
+.Nm
+will infer the type from the option name, assuming
+.Fl w
+for pane options.
+Global session or window options are listed if
+.Fl g
+is used.
+.Fl v
+shows only the option value, not the name.
+If
+.Fl q
+is set, no error will be returned if
+.Ar option
+is unset.
+.Fl H
+includes hooks (omitted by default).
+.Fl A
+includes options inherited from a parent set of options, such options are
+marked with an asterisk.
+.El
+.Pp
+Available server options are:
+.Bl -tag -width Ds
+.It Ic backspace Ar key
+Set the key sent by
+.Nm
+for backspace.
+.It Ic buffer-limit Ar number
+Set the number of buffers; as new buffers are added to the top of the stack,
+old ones are removed from the bottom if necessary to maintain this maximum
+length.
+.It Xo Ic command-alias[]
+.Ar name=value
+.Xc
+This is an array of custom aliases for commands.
+If an unknown command matches
+.Ar name ,
+it is replaced with
+.Ar value .
+For example, after:
+.Pp
+.Dl set -s command-alias[100] zoom='resize-pane -Z'
+.Pp
+Using:
+.Pp
+.Dl zoom -t:.1
+.Pp
+Is equivalent to:
+.Pp
+.Dl resize-pane -Z -t:.1
+.Pp
+Note that aliases are expanded when a command is parsed rather than when it is
+executed, so binding an alias with
+.Ic bind-key
+will bind the expanded form.
+.It Ic default-terminal Ar terminal
+Set the default terminal for new windows created in this session - the
+default value of the
+.Ev TERM
+environment variable.
+For
+.Nm
+to work correctly, this
+.Em must
+be set to
+.Ql screen ,
+.Ql tmux
+or a derivative of them.
+.It Ic copy-command Ar shell-command
+Give the command to pipe to if the
+.Ic copy-pipe
+copy mode command is used without arguments.
+.It Ic escape-time Ar time
+Set the time in milliseconds for which
+.Nm
+waits after an escape is input to determine if it is part of a function or meta
+key sequences.
+The default is 500 milliseconds.
+.It Ic editor Ar shell-command
+Set the command used when
+.Nm
+runs an editor.
+.It Xo Ic exit-empty
+.Op Ic on | off
+.Xc
+If enabled (the default), the server will exit when there are no active
+sessions.
+.It Xo Ic exit-unattached
+.Op Ic on | off
+.Xc
+If enabled, the server will exit when there are no attached clients.
+.It Xo Ic extended-keys
+.Op Ic on | off | always
+.Xc
+When
+.Ic on
+or
+.Ic always ,
+the escape sequence to enable extended keys is sent to the terminal, if
+.Nm
+knows that it is supported.
+.Nm
+always recognises extended keys itself.
+If this option is
+.Ic on ,
+.Nm
+will only forward extended keys to applications when they request them; if
+.Ic always ,
+.Nm
+will always forward the keys.
+.It Xo Ic focus-events
+.Op Ic on | off
+.Xc
+When enabled, focus events are requested from the terminal if supported and
+passed through to applications running in
+.Nm .
+Attached clients should be detached and attached again after changing this
+option.
+.It Ic history-file Ar path
+If not empty, a file to which
+.Nm
+will write command prompt history on exit and load it from on start.
+.It Ic message-limit Ar number
+Set the number of error or information messages to save in the message log for
+each client.
+.It Ic prompt-history-limit Ar number
+Set the number of history items to save in the history file for each type of
+command prompt.
+.It Xo Ic set-clipboard
+.Op Ic on | external | off
+.Xc
+Attempt to set the terminal clipboard content using the
+.Xr xterm 1
+escape sequence, if there is an
+.Em \&Ms
+entry in the
+.Xr terminfo 5
+description (see the
+.Sx TERMINFO EXTENSIONS
+section).
+.Pp
+If set to
+.Ic on ,
+.Nm
+will both accept the escape sequence to create a buffer and attempt to set
+the terminal clipboard.
+If set to
+.Ic external ,
+.Nm
+will attempt to set the terminal clipboard but ignore attempts
+by applications to set
+.Nm
+buffers.
+If
+.Ic off ,
+.Nm
+will neither accept the clipboard escape sequence nor attempt to set the
+clipboard.
+.Pp
+Note that this feature needs to be enabled in
+.Xr xterm 1
+by setting the resource:
+.Bd -literal -offset indent
+disallowedWindowOps: 20,21,SetXprop
+.Ed
+.Pp
+Or changing this property from the
+.Xr xterm 1
+interactive menu when required.
+.It Ic terminal-features[] Ar string
+Set terminal features for terminal types read from
+.Xr terminfo 5 .
+.Nm
+has a set of named terminal features.
+Each will apply appropriate changes to the
+.Xr terminfo 5
+entry in use.
+.Pp
+.Nm
+can detect features for a few common terminals; this option can be used to
+easily tell tmux about features supported by terminals it cannot detect.
+The
+.Ic terminal-overrides
+option allows individual
+.Xr terminfo 5
+capabilities to be set instead,
+.Ic terminal-features
+is intended for classes of functionality supported in a standard way but not
+reported by
+.Xr terminfo 5 .
+Care must be taken to configure this only with features the terminal actually
+supports.
+.Pp
+This is an array option where each entry is a colon-separated string made up
+of a terminal type pattern (matched using
+.Xr fnmatch 3 )
+followed by a list of terminal features.
+The available features are:
+.Bl -tag -width Ds
+.It 256
+Supports 256 colours with the SGR escape sequences.
+.It clipboard
+Allows setting the system clipboard.
+.It ccolour
+Allows setting the cursor colour.
+.It cstyle
+Allows setting the cursor style.
+.It extkeys
+Supports extended keys.
+.It focus
+Supports focus reporting.
+.It margins
+Supports DECSLRM margins.
+.It mouse
+Supports
+.Xr xterm 1
+mouse sequences.
+.It osc7
+Supports the OSC 7 working directory extension.
+.It overline
+Supports the overline SGR attribute.
+.It rectfill
+Supports the DECFRA rectangle fill escape sequence.
+.It RGB
+Supports RGB colour with the SGR escape sequences.
+.It strikethrough
+Supports the strikethrough SGR escape sequence.
+.It sync
+Supports synchronized updates.
+.It title
+Supports
+.Xr xterm 1
+title setting.
+.It usstyle
+Allows underscore style and colour to be set.
+.El
+.It Ic terminal-overrides[] Ar string
+Allow terminal descriptions read using
+.Xr terminfo 5
+to be overridden.
+Each entry is a colon-separated string made up of a terminal type pattern
+(matched using
+.Xr fnmatch 3 )
+and a set of
+.Em name=value
+entries.
+.Pp
+For example, to set the
+.Ql clear
+.Xr terminfo 5
+entry to
+.Ql \ee[H\ee[2J
+for all terminal types matching
+.Ql rxvt* :
+.Pp
+.Dl "rxvt*:clear=\ee[H\ee[2J"
+.Pp
+The terminal entry value is passed through
+.Xr strunvis 3
+before interpretation.
+.It Ic user-keys[] Ar key
+Set list of user-defined key escape sequences.
+Each item is associated with a key named
+.Ql User0 ,
+.Ql User1 ,
+and so on.
+.Pp
+For example:
+.Bd -literal -offset indent
+set -s user-keys[0] "\ee[5;30012~"
+bind User0 resize-pane -L 3
+.Ed
+.El
+.Pp
+Available session options are:
+.Bl -tag -width Ds
+.It Xo Ic activity-action
+.Op Ic any | none | current | other
+.Xc
+Set action on window activity when
+.Ic monitor-activity
+is on.
+.Ic any
+means activity in any window linked to a session causes a bell or message
+(depending on
+.Ic visual-activity )
+in the current window of that session,
+.Ic none
+means all activity is ignored (equivalent to
+.Ic monitor-activity
+being off),
+.Ic current
+means only activity in windows other than the current window are ignored and
+.Ic other
+means activity in the current window is ignored but not those in other windows.
+.It Ic assume-paste-time Ar milliseconds
+If keys are entered faster than one in
+.Ar milliseconds ,
+they are assumed to have been pasted rather than typed and
+.Nm
+key bindings are not processed.
+The default is one millisecond and zero disables.
+.It Ic base-index Ar index
+Set the base index from which an unused index should be searched when a new
+window is created.
+The default is zero.
+.It Xo Ic bell-action
+.Op Ic any | none | current | other
+.Xc
+Set action on a bell in a window when
+.Ic monitor-bell
+is on.
+The values are the same as those for
+.Ic activity-action .
+.It Ic default-command Ar shell-command
+Set the command used for new windows (if not specified when the window is
+created) to
+.Ar shell-command ,
+which may be any
+.Xr sh 1
+command.
+The default is an empty string, which instructs
+.Nm
+to create a login shell using the value of the
+.Ic default-shell
+option.
+.It Ic default-shell Ar path
+Specify the default shell.
+This is used as the login shell for new windows when the
+.Ic default-command
+option is set to empty, and must be the full path of the executable.
+When started
+.Nm
+tries to set a default value from the first suitable of the
+.Ev SHELL
+environment variable, the shell returned by
+.Xr getpwuid 3 ,
+or
+.Pa /bin/sh .
+This option should be configured when
+.Nm
+is used as a login shell.
+.It Ic default-size Ar XxY
+Set the default size of new windows when the
+.Ic window-size
+option is set to manual or when a session is created with
+.Ic new-session
+.Fl d .
+The value is the width and height separated by an
+.Ql x
+character.
+The default is 80x24.
+.It Xo Ic destroy-unattached
+.Op Ic on | off
+.Xc
+If enabled and the session is no longer attached to any clients, it is
+destroyed.
+.It Xo Ic detach-on-destroy
+.Op Ic off | on | no-detached
+.Xc
+If 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
+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.
+.It Ic display-panes-active-colour Ar colour
+Set the colour used by the
+.Ic display-panes
+command to show the indicator for the active pane.
+.It Ic display-panes-colour Ar colour
+Set the colour used by the
+.Ic display-panes
+command to show the indicators for inactive panes.
+.It Ic display-panes-time Ar time
+Set the time in milliseconds for which the indicators shown by the
+.Ic display-panes
+command appear.
+.It Ic display-time Ar time
+Set the amount of time for which status line messages and other on-screen
+indicators are displayed.
+If set to 0, messages and indicators are displayed until a key is pressed.
+.Ar time
+is in milliseconds.
+.It Ic history-limit Ar lines
+Set the maximum number of lines held in window history.
+This setting applies only to new windows - existing window histories are not
+resized and retain the limit at the point they were created.
+.It Ic key-table Ar key-table
+Set the default key table to
+.Ar key-table
+instead of
+.Em root .
+.It Ic lock-after-time Ar number
+Lock the session (like the
+.Ic lock-session
+command) after
+.Ar number
+seconds of inactivity.
+The default is not to lock (set to 0).
+.It Ic lock-command Ar shell-command
+Command to run when locking each client.
+The default is to run
+.Xr lock 1
+with
+.Fl np .
+.It Ic message-command-style Ar style
+Set status line message command style.
+This is used for the command prompt with
+.Xr vi 1
+keys when in command mode.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.It Ic message-style Ar style
+Set status line message style.
+This is used for messages and for the command prompt.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.It Xo Ic mouse
+.Op Ic on | off
+.Xc
+If on,
+.Nm
+captures the mouse and allows mouse events to be bound as key bindings.
+See the
+.Sx MOUSE SUPPORT
+section for details.
+.It Ic prefix Ar key
+Set the key accepted as a prefix key.
+In addition to the standard keys described under
+.Sx KEY BINDINGS ,
+.Ic prefix
+can be set to the special key
+.Ql None
+to set no prefix.
+.It Ic prefix2 Ar key
+Set a secondary key accepted as a prefix key.
+Like
+.Ic prefix ,
+.Ic prefix2
+can be set to
+.Ql None .
+.It Xo Ic renumber-windows
+.Op Ic on | off
+.Xc
+If on, when a window is closed in a session, automatically renumber the other
+windows in numerical order.
+This respects the
+.Ic base-index
+option if it has been set.
+If off, do not renumber the windows.
+.It Ic repeat-time Ar time
+Allow multiple commands to be entered without pressing the prefix-key again
+in the specified
+.Ar time
+milliseconds (the default is 500).
+Whether a key repeats may be set when it is bound using the
+.Fl r
+flag to
+.Ic bind-key .
+Repeat is enabled for the default keys bound to the
+.Ic resize-pane
+command.
+.It Xo Ic set-titles
+.Op Ic on | off
+.Xc
+Attempt to set the client terminal title using the
+.Em tsl
+and
+.Em fsl
+.Xr terminfo 5
+entries if they exist.
+.Nm
+automatically sets these to the \ee]0;...\e007 sequence if
+the terminal appears to be
+.Xr xterm 1 .
+This option is off by default.
+.It Ic set-titles-string Ar string
+String used to set the client terminal title if
+.Ic set-titles
+is on.
+Formats are expanded, see the
+.Sx FORMATS
+section.
+.It Xo Ic silence-action
+.Op Ic any | none | current | other
+.Xc
+Set action on window silence when
+.Ic monitor-silence
+is on.
+The values are the same as those for
+.Ic activity-action .
+.It Xo Ic status
+.Op Ic off | on | 2 | 3 | 4 | 5
+.Xc
+Show or hide the status line or specify its size.
+Using
+.Ic on
+gives a status line one row in height;
+.Ic 2 ,
+.Ic 3 ,
+.Ic 4
+or
+.Ic 5
+more rows.
+.It Ic status-format[] Ar format
+Specify the format to be used for each line of the status line.
+The default builds the top status line from the various individual status
+options below.
+.It Ic status-interval Ar interval
+Update the status line every
+.Ar interval
+seconds.
+By default, updates will occur every 15 seconds.
+A setting of zero disables redrawing at interval.
+.It Xo Ic status-justify
+.Op Ic left | centre | right | absolute-centre
+.Xc
+Set the position of the window list in the status line: left, centre or right.
+centre puts the window list in the relative centre of the available free space;
+absolute-centre uses the centre of the entire horizontal space.
+.It Xo Ic status-keys
+.Op Ic vi | emacs
+.Xc
+Use vi or emacs-style
+key bindings in the status line, for example at the command prompt.
+The default is emacs, unless the
+.Ev VISUAL
+or
+.Ev EDITOR
+environment variables are set and contain the string
+.Ql vi .
+.It Ic status-left Ar string
+Display
+.Ar string
+(by default the session name) to the left of the status line.
+.Ar string
+will be passed through
+.Xr strftime 3 .
+Also see the
+.Sx FORMATS
+and
+.Sx STYLES
+sections.
+.Pp
+For details on how the names and titles can be set see the
+.Sx "NAMES AND TITLES"
+section.
+.Pp
+Examples are:
+.Bd -literal -offset indent
+#(sysctl vm.loadavg)
+#[fg=yellow,bold]#(apm -l)%%#[default] [#S]
+.Ed
+.Pp
+The default is
+.Ql "[#S] " .
+.It Ic status-left-length Ar length
+Set the maximum
+.Ar length
+of the left component of the status line.
+The default is 10.
+.It Ic status-left-style Ar style
+Set the style of the left part of the status line.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.It Xo Ic status-position
+.Op Ic top | bottom
+.Xc
+Set the position of the status line.
+.It Ic status-right Ar string
+Display
+.Ar string
+to the right of the status line.
+By default, the current pane title in double quotes, the date and the time
+are shown.
+As with
+.Ic status-left ,
+.Ar string
+will be passed to
+.Xr strftime 3
+and character pairs are replaced.
+.It Ic status-right-length Ar length
+Set the maximum
+.Ar length
+of the right component of the status line.
+The default is 40.
+.It Ic status-right-style Ar style
+Set the style of the right part of the status line.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.It Ic status-style Ar style
+Set status line style.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.It Ic update-environment[] Ar variable
+Set list of environment variables to be copied into the session environment
+when a new session is created or an existing session is attached.
+Any variables that do not exist in the source environment are set to be
+removed from the session environment (as if
+.Fl r
+was given to the
+.Ic set-environment
+command).
+.It Xo Ic visual-activity
+.Op Ic on | off | both
+.Xc
+If on, display a message instead of sending a bell when activity occurs in a
+window for which the
+.Ic monitor-activity
+window option is enabled.
+If set to both, a bell and a message are produced.
+.It Xo Ic visual-bell
+.Op Ic on | off | both
+.Xc
+If on, a message is shown on a bell in a window for which the
+.Ic monitor-bell
+window option is enabled instead of it being passed through to the
+terminal (which normally makes a sound).
+If set to both, a bell and a message are produced.
+Also see the
+.Ic bell-action
+option.
+.It Xo Ic visual-silence
+.Op Ic on | off | both
+.Xc
+If
+.Ic monitor-silence
+is enabled, prints a message after the interval has expired on a given window
+instead of sending a bell.
+If set to both, a bell and a message are produced.
+.It Ic word-separators Ar string
+Sets the session's conception of what characters are considered word
+separators, for the purposes of the next and previous word commands in
+copy mode.
+.El
+.Pp
+Available window options are:
+.Pp
+.Bl -tag -width Ds -compact
+.It Xo Ic aggressive-resize
+.Op Ic on | off
+.Xc
+Aggressively resize the chosen window.
+This means that
+.Nm
+will resize the window to the size of the smallest or largest session
+(see the
+.Ic window-size
+option) for which it is the current window, rather than the session to
+which it is attached.
+The window may resize when the current window is changed on another
+session; this option is good for full-screen programs which support
+.Dv SIGWINCH
+and poor for interactive programs such as shells.
+.Pp
+.It Xo Ic automatic-rename
+.Op Ic on | off
+.Xc
+Control automatic window renaming.
+When this setting is enabled,
+.Nm
+will rename the window automatically using the format specified by
+.Ic automatic-rename-format .
+This flag is automatically disabled for an individual window when a name
+is specified at creation with
+.Ic new-window
+or
+.Ic new-session ,
+or later with
+.Ic rename-window ,
+or with a terminal escape sequence.
+It may be switched off globally with:
+.Bd -literal -offset indent
+set-option -wg automatic-rename off
+.Ed
+.Pp
+.It Ic automatic-rename-format Ar format
+The format (see
+.Sx FORMATS )
+used when the
+.Ic automatic-rename
+option is enabled.
+.Pp
+.It Ic clock-mode-colour Ar colour
+Set clock colour.
+.Pp
+.It Xo Ic clock-mode-style
+.Op Ic 12 | 24
+.Xc
+Set clock hour format.
+.Pp
+.It Ic fill-character Ar character
+Set the character used to fill areas of the terminal unused by a window.
+.Pp
+.It Ic main-pane-height Ar height
+.It Ic main-pane-width Ar width
+Set the width or height of the main (left or top) pane in the
+.Ic main-horizontal
+or
+.Ic main-vertical
+layouts.
+If suffixed by
+.Ql % ,
+this is a percentage of the window size.
+.Pp
+.It Ic copy-mode-match-style Ar style
+Set the style of search matches in copy mode.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic copy-mode-mark-style Ar style
+Set the style of the line containing the mark in copy mode.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic copy-mode-current-match-style Ar style
+Set the style of the current search match in copy mode.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Xo Ic mode-keys
+.Op Ic vi | emacs
+.Xc
+Use vi or emacs-style key bindings in copy mode.
+The default is emacs, unless
+.Ev VISUAL
+or
+.Ev EDITOR
+contains
+.Ql vi .
+.Pp
+.It Ic mode-style Ar style
+Set window modes style.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Xo Ic monitor-activity
+.Op Ic on | off
+.Xc
+Monitor for activity in the window.
+Windows with activity are highlighted in the status line.
+.Pp
+.It Xo Ic monitor-bell
+.Op Ic on | off
+.Xc
+Monitor for a bell in the window.
+Windows with a bell are highlighted in the status line.
+.Pp
+.It Xo Ic monitor-silence
+.Op Ic interval
+.Xc
+Monitor for silence (no activity) in the window within
+.Ic interval
+seconds.
+Windows that have been silent for the interval are highlighted in the
+status line.
+An interval of zero disables the monitoring.
+.Pp
+.It Ic other-pane-height Ar height
+Set the height of the other panes (not the main pane) in the
+.Ic main-horizontal
+layout.
+If this option is set to 0 (the default), it will have no effect.
+If both the
+.Ic main-pane-height
+and
+.Ic other-pane-height
+options are set, the main pane will grow taller to make the other panes the
+specified height, but will never shrink to do so.
+If suffixed by
+.Ql % ,
+this is a percentage of the window size.
+.Pp
+.It Ic other-pane-width Ar width
+Like
+.Ic other-pane-height ,
+but set the width of other panes in the
+.Ic main-vertical
+layout.
+.Pp
+.It Ic pane-active-border-style Ar style
+Set the pane border style for the currently active pane.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+Attributes are ignored.
+.Pp
+.It Ic pane-base-index Ar index
+Like
+.Ic base-index ,
+but set the starting index for pane numbers.
+.Pp
+.It Ic pane-border-format Ar format
+Set the text shown in pane border status lines.
+.Pp
+.It Xo Ic pane-border-indicators
+.Op Ic off | colour | arrows | both
+.Xc
+Indicate active pane by colouring only half of the border in windows with
+exactly two panes, by displaying arrow markers, by drawing both or neither.
+.Pp
+.It Ic pane-border-lines Ar type
+Set the type of characters used for drawing pane borders.
+.Ar type
+may be one of:
+.Bl -tag -width Ds
+.It single
+single lines using ACS or UTF-8 characters
+.It double
+double lines using UTF-8 characters
+.It heavy
+heavy lines using UTF-8 characters
+.It simple
+simple ASCII characters
+.It number
+the pane number
+.El
+.Pp
+.Ql double
+and
+.Ql heavy
+will fall back to standard ACS line drawing when UTF-8 is not supported.
+.Pp
+.It Xo Ic pane-border-status
+.Op Ic off | top | bottom
+.Xc
+Turn pane border status lines off or set their position.
+.Pp
+.It Ic pane-border-style Ar style
+Set the pane border style for panes aside from the active pane.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+Attributes are ignored.
+.Pp
+.It Ic popup-style Ar style
+Set the popup style.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+Attributes are ignored.
+.Pp
+.It Ic popup-border-style Ar style
+Set the popup border style.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+Attributes are ignored.
+.Pp
+.It Ic popup-border-lines Ar type
+Set the type of characters used for drawing popup borders.
+.Ar type
+may be one of:
+.Bl -tag -width Ds
+.It single
+single lines using ACS or UTF-8 characters (default)
+.It rounded
+variation of single with rounded corners using UTF-8 characters
+.It double
+double lines using UTF-8 characters
+.It heavy
+heavy lines using UTF-8 characters
+.It simple
+simple ASCII characters
+.It padded
+simple ASCII space character
+.It none
+no border
+.El
+.Pp
+.Ql double
+and
+.Ql heavy
+will fall back to standard ACS line drawing when UTF-8 is not supported.
+.Pp
+.It Ic window-status-activity-style Ar style
+Set status line style for windows with an activity alert.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic window-status-bell-style Ar style
+Set status line style for windows with a bell alert.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic window-status-current-format Ar string
+Like
+.Ar window-status-format ,
+but is the format used when the window is the current window.
+.Pp
+.It Ic window-status-current-style Ar style
+Set status line style for the currently active window.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic window-status-format Ar string
+Set the format in which the window is displayed in the status line window list.
+See the
+.Sx FORMATS
+and
+.Sx STYLES
+sections.
+.Pp
+.It Ic window-status-last-style Ar style
+Set status line style for the last active window.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic window-status-separator Ar string
+Sets the separator drawn between windows in the status line.
+The default is a single space character.
+.Pp
+.It Ic window-status-style Ar style
+Set status line style for a single window.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Xo Ic window-size
+.Ar largest | Ar smallest | Ar manual | Ar latest
+.Xc
+Configure how
+.Nm
+determines the window size.
+If set to
+.Ar largest ,
+the size of the largest attached session is used; if
+.Ar smallest ,
+the size of the smallest.
+If
+.Ar manual ,
+the size of a new window is set from the
+.Ic default-size
+option and windows are resized automatically.
+With
+.Ar latest ,
+.Nm
+uses the size of the client that had the most recent activity.
+See also the
+.Ic resize-window
+command and the
+.Ic aggressive-resize
+option.
+.Pp
+.It Xo Ic wrap-search
+.Op Ic on | off
+.Xc
+If this option is set, searches will wrap around the end of the pane contents.
+The default is on.
+.El
+.Pp
+Available pane options are:
+.Pp
+.Bl -tag -width Ds -compact
+.It Xo Ic allow-passthrough
+.Op Ic on | off
+.Xc
+Allow programs in the pane to bypass
+.Nm
+using a terminal escape sequence (\eePtmux;...\ee\e\e).
+.Pp
+.It Xo Ic allow-rename
+.Op Ic on | off
+.Xc
+Allow programs in the pane to change the window name using a terminal escape
+sequence (\eek...\ee\e\e).
+.Pp
+.It Xo Ic alternate-screen
+.Op Ic on | off
+.Xc
+This option configures whether programs running inside the pane may use the
+terminal alternate screen feature, which allows the
+.Em smcup
+and
+.Em rmcup
+.Xr terminfo 5
+capabilities.
+The alternate screen feature preserves the contents of the window when an
+interactive application starts and restores it on exit, so that any output
+visible before the application starts reappears unchanged after it exits.
+.Pp
+.It Ic cursor-colour Ar colour
+Set the colour of the cursor.
+.Pp
+.It Ic pane-colours[] Ar colour
+The default colour palette.
+Each entry in the array defines the colour
+.Nm
+uses when the colour with that index is requested.
+The index may be from zero to 255.
+.Pp
+.It Ic cursor-style Ar style
+Set the style of the cursor.
+Available styles are:
+.Ic default ,
+.Ic blinking-block ,
+.Ic block ,
+.Ic blinking-underline ,
+.Ic underline ,
+.Ic blinking-bar ,
+.Ic bar .
+.Pp
+.It Xo Ic remain-on-exit
+.Op Ic on | off | failed
+.Xc
+A pane with this flag set is not destroyed when the program running in it
+exits.
+If set to
+.Ic failed ,
+then only when the program exit status is not zero.
+The pane may be reactivated with the
+.Ic respawn-pane
+command.
+.Pp
+.It Ic remain-on-exit-format Ar string
+Set the text shown at the bottom of exited panes when
+.Ic remain-on-exit
+is enabled.
+.Pp
+.It Xo Ic scroll-on-clear
+.Op Ic on | off
+.Xc
+When the entire screen is cleared and this option is on, scroll the contents of
+the screen into history before clearing it.
+.Pp
+.It Xo Ic synchronize-panes
+.Op Ic on | off
+.Xc
+Duplicate input to all other panes in the same window where this option is also
+on (only for panes that are not in any mode).
+.Pp
+.It Ic window-active-style Ar style
+Set the pane style when it is the active pane.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.It Ic window-style Ar style
+Set the pane style.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.El
+.Sh HOOKS
+.Nm
+allows commands to run on various triggers, called
+.Em hooks .
+Most
+.Nm
+commands have an
+.Em after
+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.
+Hooks may be configured with the
+.Ic set-hook
+or
+.Ic set-option
+commands and displayed with
+.Ic show-hooks
+or
+.Ic show-options
+.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'
+.Ed
+.Pp
+Setting a hook without specifying an array index clears the hook and sets the
+first member of the array.
+.Pp
+A command's after
+hook is run after it completes, except when the command is run as part of a hook
+itself.
+They are named with an
+.Ql after-
+prefix.
+For example, the following command adds a hook to select the even-vertical
+layout after every
+.Ic split-window :
+.Bd -literal -offset indent
+set-hook -g after-split-window "selectl even-vertical"
+.Ed
+.Pp
+All the notifications listed in the
+.Sx CONTROL MODE
+section are hooks (without any arguments), except
+.Ic %exit .
+The following additional hooks are available:
+.Bl -tag -width "XXXXXXXXXXXXXXXXXXXXXX"
+.It alert-activity
+Run when a window has activity.
+See
+.Ic monitor-activity .
+.It alert-bell
+Run when a window has received a bell.
+See
+.Ic monitor-bell .
+.It alert-silence
+Run when a window has been silent.
+See
+.Ic monitor-silence .
+.It client-active
+Run when a client becomes the latest active client of its session.
+.It client-attached
+Run when a client is attached.
+.It client-detached
+Run when a client is detached
+.It client-focus-in
+Run when focus enters a client
+.It client-focus-out
+Run when focus exits a client
+.It client-resized
+Run when a client is resized.
+.It client-session-changed
+Run when a client's attached session is changed.
+.It pane-died
+Run when the program running in a pane exits, but
+.Ic remain-on-exit
+is on so the pane has not closed.
+.It pane-exited
+Run when the program running in a pane exits.
+.It pane-focus-in
+Run when the focus enters a pane, if the
+.Ic focus-events
+option is on.
+.It pane-focus-out
+Run when the focus exits a pane, if the
+.Ic focus-events
+option is on.
+.It pane-set-clipboard
+Run when the terminal clipboard is set using the
+.Xr xterm 1
+escape sequence.
+.It session-created
+Run when a new session created.
+.It session-closed
+Run when a session closed.
+.It session-renamed
+Run when a session is renamed.
+.It window-linked
+Run when a window is linked into a session.
+.It window-renamed
+Run when a window is renamed.
+.It window-resized
+Run when a window is resized.
+This may be after the
+.Ar client-resized
+hook is run.
+.It window-unlinked
+Run when a window is unlinked from a session.
+.El
+.Pp
+Hooks are managed with these commands:
+.Bl -tag -width Ds
+.It Xo Ic set-hook
+.Op Fl agpRuw
+.Op Fl t Ar target-pane
+.Ar hook-name
+.Ar command
+.Xc
+Without
+.Fl R ,
+sets (or with
+.Fl u
+unsets) hook
+.Ar hook-name
+to
+.Ar command .
+The flags are the same as for
+.Ic set-option .
+.Pp
+With
+.Fl R ,
+run
+.Ar hook-name
+immediately.
+.It Xo Ic show-hooks
+.Op Fl gpw
+.Op Fl t Ar target-pane
+.Xc
+Shows hooks.
+The flags are the same as for
+.Ic show-options .
+.El
+.Sh MOUSE SUPPORT
+If the
+.Ic mouse
+option is on (the default is off),
+.Nm
+allows mouse events to be bound as keys.
+The name of each key is made up of a mouse event (such as
+.Ql MouseUp1 )
+and a location suffix, one of the following:
+.Bl -column "XXXXXXXXXXXXX" -offset indent
+.It Li "Pane" Ta "the contents of a pane"
+.It Li "Border" Ta "a pane border"
+.It Li "Status" Ta "the status line window list"
+.It Li "StatusLeft" Ta "the left part of the status line"
+.It Li "StatusRight" Ta "the right part of the status line"
+.It Li "StatusDefault" Ta "any other part of the status line"
+.El
+.Pp
+The following mouse events are available:
+.Bl -column "MouseDown1" "MouseDrag1" "WheelDown" -offset indent
+.It Li "WheelUp" Ta "WheelDown" Ta ""
+.It Li "MouseDown1" Ta "MouseUp1" Ta "MouseDrag1" Ta "MouseDragEnd1"
+.It Li "MouseDown2" Ta "MouseUp2" Ta "MouseDrag2" Ta "MouseDragEnd2"
+.It Li "MouseDown3" Ta "MouseUp3" Ta "MouseDrag3" Ta "MouseDragEnd3"
+.It Li "SecondClick1" Ta "SecondClick2" Ta "SecondClick3"
+.It Li "DoubleClick1" Ta "DoubleClick2" Ta "DoubleClick3"
+.It Li "TripleClick1" Ta "TripleClick2" Ta "TripleClick3"
+.El
+.Pp
+The
+.Ql SecondClick
+events are fired for the second click of a double click, even if there may be a
+third click which will fire
+.Ql TripleClick
+instead of
+.Ql DoubleClick .
+.Pp
+Each should be suffixed with a location, for example
+.Ql MouseDown1Status .
+.Pp
+The special token
+.Ql {mouse}
+or
+.Ql =
+may be used as
+.Ar target-window
+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
+.Ql MouseUp1Status
+binding, or the pane over which the wheel was scrolled for a
+.Ql WheelDownPane
+binding).
+.Pp
+The
+.Ic send-keys
+.Fl M
+flag may be used to forward a mouse event to a pane.
+.Pp
+The default key bindings allow the mouse to be used to select and resize panes,
+to copy text and to change window using the status line.
+These take effect if the
+.Ic mouse
+option is turned on.
+.Sh FORMATS
+Certain commands accept the
+.Fl F
+flag with a
+.Ar format
+argument.
+This is a string which controls the output format of the command.
+Format variables are enclosed in
+.Ql #{
+and
+.Ql } ,
+for example
+.Ql #{session_name} .
+The possible variables are listed in the table below, or the name of a
+.Nm
+option may be used for an option's value.
+Some variables have a shorter alias such as
+.Ql #S ;
+.Ql ##
+is replaced by a single
+.Ql # ,
+.Ql #,
+by a
+.Ql \&,
+and
+.Ql #}
+by a
+.Ql } .
+.Pp
+Conditionals are available by prefixing with
+.Ql \&?
+and separating two alternatives with a comma;
+if the specified variable exists and is not zero, the first alternative
+is chosen, otherwise the second is used.
+For example
+.Ql #{?session_attached,attached,not attached}
+will include the string
+.Ql attached
+if the session is attached and the string
+.Ql not attached
+if it is unattached, or
+.Ql #{?automatic-rename,yes,no}
+will include
+.Ql yes
+if
+.Ic automatic-rename
+is enabled, or
+.Ql no
+if not.
+Conditionals can be nested arbitrarily.
+Inside a conditional,
+.Ql \&,
+and
+.Ql }
+must be escaped as
+.Ql #,
+and
+.Ql #} ,
+unless they are part of a
+.Ql #{...}
+replacement.
+For example:
+.Bd -literal -offset indent
+#{?pane_in_mode,#[fg=white#,bg=red],#[fg=red#,bg=white]}#W .
+.Ed
+.Pp
+String comparisons may be expressed by prefixing two comma-separated
+alternatives by
+.Ql == ,
+.Ql != ,
+.Ql < ,
+.Ql > ,
+.Ql <=
+or
+.Ql >=
+and a colon.
+For example
+.Ql #{==:#{host},myhost}
+will be replaced by
+.Ql 1
+if running on
+.Ql myhost ,
+otherwise by
+.Ql 0 .
+.Ql ||
+and
+.Ql &&
+evaluate to true if either or both of two comma-separated alternatives are
+true, for example
+.Ql #{||:#{pane_in_mode},#{alternate_on}} .
+.Pp
+An
+.Ql m
+specifies an
+.Xr fnmatch 3
+or regular expression comparison.
+The first argument is the pattern and the second the string to compare.
+An optional argument specifies flags:
+.Ql r
+means the pattern is a regular expression instead of the default
+.Xr fnmatch 3
+pattern, and
+.Ql i
+means to ignore case.
+For example:
+.Ql #{m:*foo*,#{host}}
+or
+.Ql #{m/ri:^A,MYVAR} .
+A
+.Ql C
+performs a search for an
+.Xr fnmatch 3
+pattern or regular expression in the pane content and evaluates to zero if not
+found, or a line number if found.
+Like
+.Ql m ,
+an
+.Ql r
+flag means search for a regular expression and
+.Ql i
+ignores case.
+For example:
+.Ql #{C/r:^Start}
+.Pp
+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.
+The available operators are:
+addition
+.Ql + ,
+subtraction
+.Ql - ,
+multiplication
+.Ql * ,
+division
+.Ql / ,
+modulus
+.Ql m
+or
+.Ql %
+(note that
+.Ql %
+must be escaped as
+.Ql %%
+in formats which are also expanded by
+.Xr strftime 3 )
+and numeric comparison operators
+.Ql == ,
+.Ql != ,
+.Ql < ,
+.Ql <= ,
+.Ql >
+and
+.Ql >= .
+For example,
+.Ql #{e|*|f|4:5.5,3}
+multiplies 5.5 by 3 for a result with four decimal places and
+.Ql #{e|%%:7,3}
+returns the modulus of 7 and 3.
+.Ql a
+replaces a numeric argument by its ASCII equivalent, so
+.Ql #{a:98}
+results in
+.Ql b .
+.Ql c
+replaces a
+.Nm
+colour by its six-digit hexadecimal RGB value.
+.Pp
+A limit may be placed on the length of the resultant string by prefixing it
+by an
+.Ql = ,
+a number and a colon.
+Positive numbers count from the start of the string and negative from the end,
+so
+.Ql #{=5:pane_title}
+will include at most the first five characters of the pane title, or
+.Ql #{=-5:pane_title}
+the last five characters.
+A suffix or prefix may be given as a second argument - if provided then it is
+appended or prepended to the string if the length has been trimmed, for example
+.Ql #{=/5/...:pane_title}
+will append
+.Ql ...
+if the pane title is more than five characters.
+Similarly,
+.Ql p
+pads the string to a given width, for example
+.Ql #{p10:pane_title}
+will result in a width of at least 10 characters.
+A positive width pads on the left, a negative on the right.
+.Ql n
+expands to the length of the variable and
+.Ql w
+to its width when displayed, for example
+.Ql #{n:window_name} .
+.Pp
+Prefixing a time variable with
+.Ql t:\&
+will convert it to a string, so if
+.Ql #{window_activity}
+gives
+.Ql 1445765102 ,
+.Ql #{t:window_activity}
+gives
+.Ql Sun Oct 25 09:25:02 2015 .
+Adding
+.Ql p (
+.Ql `t/p` )
+will use shorter but less accurate time format for times in the past.
+A custom format may be given using an
+.Ql f
+suffix (note that
+.Ql %
+must be escaped as
+.Ql %%
+if the format is separately being passed through
+.Xr strftime 3 ,
+for example in the
+.Ic status-left
+option):
+.Ql #{t/f/%%H#:%%M:window_activity} ,
+see
+.Xr strftime 3 .
+.Pp
+The
+.Ql b:\&
+and
+.Ql d:\&
+prefixes are
+.Xr basename 3
+and
+.Xr dirname 3
+of the variable respectively.
+.Ql q:\&
+will escape
+.Xr sh 1
+special characters or with a
+.Ql h
+suffix, escape hash characters (so
+.Ql #
+becomes
+.Ql ## ) .
+.Ql E:\&
+will expand the format twice, for example
+.Ql #{E:status-left}
+is the result of expanding the content of the
+.Ic status-left
+option rather than the option itself.
+.Ql T:\&
+is like
+.Ql E:\&
+but also expands
+.Xr strftime 3
+specifiers.
+.Ql S:\& ,
+.Ql W:\&
+or
+.Ql P:\&
+will loop over each session, window or pane 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.
+For example, to get a list of windows formatted like the status line:
+.Bd -literal -offset indent
+#{W:#{E:window-status-format} ,#{E:window-status-current-format} }
+.Ed
+.Pp
+.Ql N:\&
+checks if a window (without any suffix or with the
+.Ql w
+suffix) or a session (with the
+.Ql s
+suffix) name exists, for example
+.Ql `N/w:foo`
+is replaced with 1 if a window named
+.Ql foo
+exists.
+.Pp
+A prefix of the form
+.Ql s/foo/bar/:\&
+will substitute
+.Ql foo
+with
+.Ql bar
+throughout.
+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:\&
+would change
+.Ql abABab
+into
+.Ql bxBxbx .
+.Pp
+In addition, the last line of a shell command's output may be inserted using
+.Ql #() .
+For example,
+.Ql #(uptime)
+will insert the system's uptime.
+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 are executed using
+.Pa /bin/sh
+and with the
+.Nm
+global environment set (see the
+.Sx GLOBAL AND SESSION ENVIRONMENT
+section).
+.Pp
+An
+.Ql l
+specifies that a string should be interpreted literally and not expanded.
+For example
+.Ql #{l:#{?pane_in_mode,yes,no}}
+will be replaced by
+.Ql #{?pane_in_mode,yes,no} .
+.Pp
+The following variables are available, where appropriate:
+.Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX"
+.It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with"
+.It Li "active_window_index" Ta "" Ta "Index of active window in session"
+.It Li "alternate_on" Ta "" Ta "1 if pane is in alternate screen"
+.It Li "alternate_saved_x" Ta "" Ta "Saved cursor X in alternate screen"
+.It Li "alternate_saved_y" Ta "" Ta "Saved cursor Y in alternate screen"
+.It Li "buffer_created" Ta "" Ta "Time buffer created"
+.It Li "buffer_name" Ta "" Ta "Name of buffer"
+.It Li "buffer_sample" Ta "" Ta "Sample of start of buffer"
+.It Li "buffer_size" Ta "" Ta "Size of the specified buffer in bytes"
+.It Li "client_activity" Ta "" Ta "Time client last had activity"
+.It Li "client_cell_height" Ta "" Ta "Height of each client cell in pixels"
+.It Li "client_cell_width" Ta "" Ta "Width of each client cell in pixels"
+.It Li "client_control_mode" Ta "" Ta "1 if client is in control mode"
+.It Li "client_created" Ta "" Ta "Time client created"
+.It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind"
+.It Li "client_flags" Ta "" Ta "List of client flags"
+.It Li "client_height" Ta "" Ta "Height of client"
+.It Li "client_key_table" Ta "" Ta "Current key table"
+.It Li "client_last_session" Ta "" Ta "Name of the client's last session"
+.It Li "client_name" Ta "" Ta "Name of client"
+.It Li "client_pid" Ta "" Ta "PID of client process"
+.It Li "client_prefix" Ta "" Ta "1 if prefix key has been pressed"
+.It Li "client_readonly" Ta "" Ta "1 if client is read-only"
+.It Li "client_session" Ta "" Ta "Name of the client's session"
+.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any"
+.It Li "client_termname" Ta "" Ta "Terminal name of client"
+.It Li "client_termtype" Ta "" Ta "Terminal type of client, if available"
+.It Li "client_tty" Ta "" Ta "Pseudo terminal of client"
+.It Li "client_uid" Ta "" Ta "UID of client process"
+.It Li "client_user" Ta "" Ta "User of client process"
+.It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8"
+.It Li "client_width" Ta "" Ta "Width of client"
+.It Li "client_written" Ta "" Ta "Bytes written to client"
+.It Li "command" Ta "" Ta "Name of command in use, if any"
+.It Li "command_list_alias" Ta "" Ta "Command alias if listing commands"
+.It Li "command_list_name" Ta "" Ta "Command name if listing commands"
+.It Li "command_list_usage" Ta "" Ta "Command usage if listing commands"
+.It Li "config_files" Ta "" Ta "List of configuration files loaded"
+.It Li "copy_cursor_line" Ta "" Ta "Line the cursor is on in copy mode"
+.It Li "copy_cursor_word" Ta "" Ta "Word under cursor in copy mode"
+.It Li "copy_cursor_x" Ta "" Ta "Cursor X position in copy mode"
+.It Li "copy_cursor_y" Ta "" Ta "Cursor Y position in copy mode"
+.It Li "current_file" Ta "" Ta "Current configuration file"
+.It Li "cursor_character" Ta "" Ta "Character at cursor in pane"
+.It Li "cursor_flag" Ta "" Ta "Pane cursor flag"
+.It Li "cursor_x" Ta "" Ta "Cursor X position in pane"
+.It Li "cursor_y" Ta "" Ta "Cursor Y position in pane"
+.It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
+.It Li "history_limit" Ta "" Ta "Maximum window history lines"
+.It Li "history_size" Ta "" Ta "Size of history in lines"
+.It Li "hook" Ta "" Ta "Name of running hook, if any"
+.It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any"
+.It Li "hook_pane" Ta "" Ta "ID of pane where hook was run, if any"
+.It Li "hook_session" Ta "" Ta "ID of session where hook was run, if any"
+.It Li "hook_session_name" Ta "" Ta "Name of session where hook was run, if any"
+.It Li "hook_window" Ta "" Ta "ID of window where hook was run, if any"
+.It Li "hook_window_name" Ta "" Ta "Name of window where hook was run, if any"
+.It Li "host" Ta "#H" Ta "Hostname of local host"
+.It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)"
+.It Li "insert_flag" Ta "" Ta "Pane insert flag"
+.It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag"
+.It Li "keypad_flag" Ta "" Ta "Pane keypad flag"
+.It Li "last_window_index" Ta "" Ta "Index of last window in session"
+.It Li "line" Ta "" Ta "Line number in the list"
+.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_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_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"
+.It Li "mouse_y" Ta "" Ta "Mouse Y position, if any"
+.It Li "next_session_id" Ta "" Ta "Unique session ID for next new session"
+.It Li "origin_flag" Ta "" Ta "Pane origin flag"
+.It Li "pane_active" Ta "" Ta "1 if active pane"
+.It Li "pane_at_bottom" Ta "" Ta "1 if pane is at the bottom of window"
+.It Li "pane_at_left" Ta "" Ta "1 if pane is at the left of window"
+.It Li "pane_at_right" Ta "" Ta "1 if pane is at the right of window"
+.It Li "pane_at_top" Ta "" Ta "1 if pane is at the top of window"
+.It Li "pane_bg" Ta "" Ta "Pane background colour"
+.It Li "pane_bottom" Ta "" Ta "Bottom of pane"
+.It Li "pane_current_command" Ta "" Ta "Current command if available"
+.It Li "pane_current_path" Ta "" Ta "Current path if available"
+.It Li "pane_dead" Ta "" Ta "1 if pane is dead"
+.It Li "pane_dead_signal" Ta "" Ta "Exit signal of process in dead pane"
+.It Li "pane_dead_status" Ta "" Ta "Exit status of process in dead pane"
+.It Li "pane_dead_time" Ta "" Ta "Exit time of process in dead pane"
+.It Li "pane_fg" Ta "" Ta "Pane foreground colour"
+.It Li "pane_format" Ta "" Ta "1 if format is for a pane"
+.It Li "pane_height" Ta "" Ta "Height of pane"
+.It Li "pane_id" Ta "#D" Ta "Unique pane ID"
+.It Li "pane_in_mode" Ta "" Ta "1 if pane is in a mode"
+.It Li "pane_index" Ta "#P" Ta "Index of pane"
+.It Li "pane_input_off" Ta "" Ta "1 if input to pane is disabled"
+.It Li "pane_last" Ta "" Ta "1 if last pane"
+.It Li "pane_left" Ta "" Ta "Left of pane"
+.It Li "pane_marked" Ta "" Ta "1 if this is the marked pane"
+.It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set"
+.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any"
+.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)"
+.It Li "pane_pid" Ta "" Ta "PID of first process in pane"
+.It Li "pane_pipe" Ta "" Ta "1 if pane is being piped"
+.It Li "pane_right" Ta "" Ta "Right of pane"
+.It Li "pane_search_string" Ta "" Ta "Last search string in copy mode"
+.It Li "pane_start_command" Ta "" Ta "Command pane started with"
+.It Li "pane_start_path" Ta "" Ta "Path pane started with"
+.It Li "pane_synchronized" Ta "" Ta "1 if pane is synchronized"
+.It Li "pane_tabs" Ta "" Ta "Pane tab positions"
+.It Li "pane_title" Ta "#T" Ta "Title of pane (can be set by application)"
+.It Li "pane_top" Ta "" Ta "Top of pane"
+.It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane"
+.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"
+.It Li "scroll_position" Ta "" Ta "Scroll position in copy mode"
+.It Li "scroll_region_lower" Ta "" Ta "Bottom of scroll region in pane"
+.It Li "scroll_region_upper" Ta "" Ta "Top of scroll region in pane"
+.It Li "search_match" Ta "" Ta "Search match if any"
+.It Li "search_present" Ta "" Ta "1 if search started in copy mode"
+.It Li "selection_active" Ta "" Ta "1 if selection started and changes with the cursor in copy mode"
+.It Li "selection_end_x" Ta "" Ta "X position of the end of the selection"
+.It Li "selection_end_y" Ta "" Ta "Y position of the end of the selection"
+.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 "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"
+.It Li "session_attached_list" Ta "" Ta "List of clients session is attached to"
+.It Li "session_created" Ta "" Ta "Time session created"
+.It Li "session_format" Ta "" Ta "1 if format is for a session"
+.It Li "session_group" Ta "" Ta "Name of session group"
+.It Li "session_group_attached" Ta "" Ta "Number of clients sessions in group are attached to"
+.It Li "session_group_attached_list" Ta "" Ta "List of clients sessions in group are attached to"
+.It Li "session_group_list" Ta "" Ta "List of sessions in group"
+.It Li "session_group_many_attached" Ta "" Ta "1 if multiple clients attached to sessions in group"
+.It Li "session_group_size" Ta "" Ta "Size of session group"
+.It Li "session_grouped" Ta "" Ta "1 if session in a group"
+.It Li "session_id" Ta "" Ta "Unique session ID"
+.It Li "session_last_attached" Ta "" Ta "Time session last attached"
+.It Li "session_many_attached" Ta "" Ta "1 if multiple clients attached"
+.It Li "session_marked" Ta "" Ta "1 if this session contains the marked pane"
+.It Li "session_name" Ta "#S" Ta "Name of session"
+.It Li "session_path" Ta "" Ta "Working directory of session"
+.It Li "session_stack" Ta "" Ta "Window indexes in most recent order"
+.It Li "session_windows" Ta "" Ta "Number of windows in session"
+.It Li "socket_path" Ta "" Ta "Server socket path"
+.It Li "start_time" Ta "" Ta "Server start time"
+.It Li "uid" Ta "" Ta "Server UID"
+.It Li "user" Ta "" Ta "Server user"
+.It Li "version" Ta "" Ta "Server version"
+.It Li "window_active" Ta "" Ta "1 if window active"
+.It Li "window_active_clients" Ta "" Ta "Number of clients viewing this window"
+.It Li "window_active_clients_list" Ta "" Ta "List of clients viewing this window"
+.It Li "window_active_sessions" Ta "" Ta "Number of sessions on which this window is active"
+.It Li "window_active_sessions_list" Ta "" Ta "List of sessions on which this window is active"
+.It Li "window_activity" Ta "" Ta "Time of window last activity"
+.It Li "window_activity_flag" Ta "" Ta "1 if window has activity"
+.It Li "window_bell_flag" Ta "" Ta "1 if window has bell"
+.It Li "window_bigger" Ta "" Ta "1 if window is larger than client"
+.It Li "window_cell_height" Ta "" Ta "Height of each cell in pixels"
+.It Li "window_cell_width" Ta "" Ta "Width of each cell in pixels"
+.It Li "window_end_flag" Ta "" Ta "1 if window has the highest index"
+.It Li "window_flags" Ta "#F" Ta "Window flags with # escaped as ##"
+.It Li "window_format" Ta "" Ta "1 if format is for a window"
+.It Li "window_height" Ta "" Ta "Height of window"
+.It Li "window_id" Ta "" Ta "Unique window ID"
+.It Li "window_index" Ta "#I" Ta "Index of window"
+.It Li "window_last_flag" Ta "" Ta "1 if window is the last used"
+.It Li "window_layout" Ta "" Ta "Window layout description, ignoring zoomed window panes"
+.It Li "window_linked" Ta "" Ta "1 if window is linked across sessions"
+.It Li "window_linked_sessions" Ta "" Ta "Number of sessions this window is linked to"
+.It Li "window_linked_sessions_list" Ta "" Ta "List of sessions this window is linked to"
+.It Li "window_marked_flag" Ta "" Ta "1 if window contains the marked pane"
+.It Li "window_name" Ta "#W" Ta "Name of window"
+.It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client"
+.It Li "window_offset_y" Ta "" Ta "Y offset into window if larger than client"
+.It Li "window_panes" Ta "" Ta "Number of panes in window"
+.It Li "window_raw_flags" Ta "" Ta "Window flags with nothing escaped"
+.It Li "window_silence_flag" Ta "" Ta "1 if window has silence alert"
+.It Li "window_stack_index" Ta "" Ta "Index in session most recent stack"
+.It Li "window_start_flag" Ta "" Ta "1 if window has the lowest index"
+.It Li "window_visible_layout" Ta "" Ta "Window layout description, respecting zoomed window panes"
+.It Li "window_width" Ta "" Ta "Width of window"
+.It Li "window_zoomed_flag" Ta "" Ta "1 if window is zoomed"
+.It Li "wrap_flag" Ta "" Ta "Pane wrap flag"
+.El
+.Sh STYLES
+.Nm
+offers various options to specify the colour and attributes of aspects of the
+interface, for example
+.Ic status-style
+for the status line.
+In addition, embedded styles may be specified in format options, such as
+.Ic status-left ,
+by enclosing them in
+.Ql #[
+and
+.Ql \&] .
+.Pp
+A style may be the single term
+.Ql default
+to specify the default style (which may come from an option, for example
+.Ic status-style
+in the status line) or a space
+or comma separated list of the following:
+.Bl -tag -width Ds
+.It Ic fg=colour
+Set the foreground colour.
+The colour is one of:
+.Ic black ,
+.Ic red ,
+.Ic green ,
+.Ic yellow ,
+.Ic blue ,
+.Ic magenta ,
+.Ic cyan ,
+.Ic white ;
+if supported the bright variants
+.Ic brightred ,
+.Ic brightgreen ,
+.Ic brightyellow ;
+.Ic colour0
+to
+.Ic colour255
+from the 256-colour set;
+.Ic default
+for the default colour;
+.Ic terminal
+for the terminal default colour; or a hexadecimal RGB string such as
+.Ql #ffffff .
+.It Ic bg=colour
+Set the background colour.
+.It Ic none
+Set no attributes (turn off any active attributes).
+.It Xo Ic acs ,
+.Ic bright
+(or
+.Ic bold ) ,
+.Ic dim ,
+.Ic underscore ,
+.Ic blink ,
+.Ic reverse ,
+.Ic hidden ,
+.Ic italics ,
+.Ic overline ,
+.Ic strikethrough ,
+.Ic double-underscore ,
+.Ic curly-underscore ,
+.Ic dotted-underscore ,
+.Ic dashed-underscore
+.Xc
+Set an attribute.
+Any of the attributes may be prefixed with
+.Ql no
+to unset.
+.Ic acs
+is the terminal alternate character set.
+.It Xo Ic align=left
+(or
+.Ic noalign ) ,
+.Ic align=centre ,
+.Ic align=right
+.Xc
+Align text to the left, centre or right of the available space if appropriate.
+.It Ic fill=colour
+Fill the available space with a background colour if appropriate.
+.It Xo Ic list=on ,
+.Ic list=focus ,
+.Ic list=left-marker ,
+.Ic list=right-marker ,
+.Ic nolist
+.Xc
+Mark the position of the various window list components in the
+.Ic status-format
+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);
+.Ic list=left-marker
+and
+.Ic list=right-marker
+mark the text to be used to mark that text has been trimmed from the left or
+right of the list if there is not enough space.
+.It Xo Ic push-default ,
+.Ic pop-default
+.Xc
+Store the current colours and attributes as the default or reset to the previous
+default.
+A
+.Ic push-default
+affects any subsequent use of the
+.Ic default
+term until a
+.Ic pop-default .
+Only one default may be pushed (each
+.Ic push-default
+replaces the previous saved default).
+.It Xo Ic range=left ,
+.Ic range=right ,
+.Ic range=window|X ,
+.Ic norange
+.Xc
+Mark a range in the
+.Ic status-format
+option.
+.Ic range=left
+and
+.Ic range=right
+are the text used for the
+.Ql StatusLeft
+and
+.Ql StatusRight
+mouse keys.
+.Ic range=window|X
+is the range for a window passed to the
+.Ql Status
+mouse key, where
+.Ql X
+is a window index.
+.El
+.Pp
+Examples are:
+.Bd -literal -offset indent
+fg=yellow bold underscore blink
+bg=black,fg=default,noreverse
+.Ed
+.Sh NAMES AND TITLES
+.Nm
+distinguishes between names and titles.
+Windows and sessions have names, which may be used to specify them in targets
+and are displayed in the status line and various lists: the name is the
+.Nm
+identifier for a window or session.
+Only panes have titles.
+A pane's title is typically set by the program running inside the pane using
+an escape sequence (like it would set the
+.Xr xterm 1
+window title in
+.Xr X 7 ) .
+Windows themselves do not have titles - a window's title is the title of its
+active pane.
+.Nm
+itself may set the title of the terminal in which the client is running, see
+the
+.Ic set-titles
+option.
+.Pp
+A session's name is set with the
+.Ic new-session
+and
+.Ic rename-session
+commands.
+A window's name is set with one of:
+.Bl -enum -width Ds
+.It
+A command argument (such as
+.Fl n
+for
+.Ic new-window
+or
+.Ic new-session ) .
+.It
+An escape sequence (if the
+.Ic allow-rename
+option is turned on):
+.Bd -literal -offset indent
+$ printf '\e033kWINDOW_NAME\e033\e\e'
+.Ed
+.It
+Automatic renaming, which sets the name to the active command in the window's
+active pane.
+See the
+.Ic automatic-rename
+option.
+.El
+.Pp
+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'
+.Ed
+.Pp
+It can also be modified with the
+.Ic select-pane
+.Fl T
+command.
+.Sh GLOBAL AND SESSION ENVIRONMENT
+When the server is started,
+.Nm
+copies the environment into the
+.Em global environment ;
+in addition, each session has a
+.Em session environment .
+When a window is created, the session and global environments are merged.
+If a variable exists in both, the value from the session environment is used.
+The result is the initial environment passed to the new process.
+.Pp
+The
+.Ic update-environment
+session option may be used to update the session environment from the client
+when a new session is created or an old reattached.
+.Nm
+also initialises the
+.Ev TMUX
+variable with some internal information to allow commands to be executed
+from inside, and the
+.Ev TERM
+variable with the correct terminal setting of
+.Ql screen .
+.Pp
+Variables in both session and global environments may be marked as hidden.
+Hidden variables are not passed into the environment of new processes and
+instead can only be used by tmux itself (for example in formats, see the
+.Sx FORMATS
+section).
+.Pp
+Commands to alter and view the environment are:
+.Bl -tag -width Ds
+.Tg setenv
+.It Xo Ic set-environment
+.Op Fl Fhgru
+.Op Fl t Ar target-session
+.Ar name Op Ar value
+.Xc
+.D1 Pq alias: Ic setenv
+Set or unset an environment variable.
+If
+.Fl g
+is used, the change is made in the global environment; otherwise, it is applied
+to the session environment for
+.Ar target-session .
+If
+.Fl F
+is present, then
+.Ar value
+is expanded as a format.
+The
+.Fl u
+flag unsets a variable.
+.Fl r
+indicates the variable is to be removed from the environment before starting a
+new process.
+.Fl h
+marks the variable as hidden.
+.Tg showenv
+.It Xo Ic show-environment
+.Op Fl hgs
+.Op Fl t Ar target-session
+.Op Ar variable
+.Xc
+.D1 Pq alias: Ic showenv
+Display the environment for
+.Ar target-session
+or the global environment with
+.Fl g .
+If
+.Ar variable
+is omitted, all variables are shown.
+Variables removed from the environment are prefixed with
+.Ql - .
+If
+.Fl s
+is used, the output is formatted as a set of Bourne shell commands.
+.Fl h
+shows hidden variables (omitted by default).
+.El
+.Sh STATUS LINE
+.Nm
+includes an optional status line which is displayed in the bottom line of each
+terminal.
+.Pp
+By default, the status line is enabled and one line in height (it may be
+disabled or made multiple lines with the
+.Ic status
+session option) and contains, from left-to-right: the name of the current
+session in square brackets; the window list; the title of the active pane
+in double quotes; and the time and date.
+.Pp
+Each line of the status line is configured with the
+.Ic status-format
+option.
+The default is made of three parts: configurable left and right sections (which
+may contain dynamic content such as the time or output from a shell command,
+see the
+.Ic status-left ,
+.Ic status-left-length ,
+.Ic status-right ,
+and
+.Ic status-right-length
+options below), and a central window list.
+By default, the window list shows the index, name and (if any) flag of the
+windows present in the current session in ascending numerical order.
+It may be customised with the
+.Ar window-status-format
+and
+.Ar window-status-current-format
+options.
+The flag is one of the following symbols appended to the window name:
+.Bl -column "Symbol" "Meaning" -offset indent
+.It Sy "Symbol" Ta Sy "Meaning"
+.It Li "*" Ta "Denotes the current window."
+.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 "M" Ta "The window contains the marked pane."
+.It Li "Z" Ta "The window's active pane is zoomed."
+.El
+.Pp
+The # symbol relates to the
+.Ic monitor-activity
+window option.
+The window name is printed in inverted colours if an alert (bell, activity or
+silence) is present.
+.Pp
+The colour and attributes of the status line may be configured, the entire
+status line using the
+.Ic status-style
+session option and individual windows using the
+.Ic window-status-style
+window option.
+.Pp
+The status line is automatically refreshed at interval if it has changed, the
+interval may be controlled with the
+.Ic status-interval
+session option.
+.Pp
+Commands related to the status line are as follows:
+.Bl -tag -width Ds
+.Tg clearphist
+.It Xo Ic clear-prompt-history
+.Op Fl T Ar prompt-type
+.Xc
+.D1 Pq alias: Ic clearphist
+Clear status prompt history for prompt type
+.Ar prompt-type .
+If
+.Fl T
+is omitted, then clear history for all types.
+See
+.Ic command-prompt
+for possible values for
+.Ar prompt-type .
+.It Xo Ic command-prompt
+.Op Fl 1bFikN
+.Op Fl I Ar inputs
+.Op Fl p Ar prompts
+.Op Fl t Ar target-client
+.Op Fl T Ar prompt-type
+.Op Ar template
+.Xc
+Open the command prompt in a client.
+This may be used from inside
+.Nm
+to execute commands interactively.
+.Pp
+If
+.Ar template
+is specified, it is used as the command.
+With
+.Fl F ,
+.Ar template
+is expanded as a format.
+.Pp
+If present,
+.Fl I
+is a comma-separated list of the initial text for each prompt.
+If
+.Fl p
+is given,
+.Ar prompts
+is a comma-separated list of prompts which are displayed in order; otherwise
+a single prompt is displayed, constructed from
+.Ar template
+if it is present, or
+.Ql \&:
+if not.
+.Pp
+Before the command is executed, the first occurrence of the string
+.Ql %%
+and all occurrences of
+.Ql %1
+are replaced by the response to the first prompt, all
+.Ql %2
+are replaced with the response to the second prompt, and so on for further
+prompts.
+Up to nine prompt responses may be replaced
+.Po
+.Ql %1
+to
+.Ql %9
+.Pc .
+.Ql %%%
+is like
+.Ql %%
+but any quotation marks are escaped.
+.Pp
+.Fl 1
+makes the prompt only accept one key press, in this case the resulting input
+is a single character.
+.Fl k
+is like
+.Fl 1
+but the key press is translated to a key name.
+.Fl N
+makes the prompt only accept numeric key presses.
+.Fl i
+executes the command every time the prompt input changes instead of when the
+user exits the command prompt.
+.Pp
+.Fl T
+tells
+.Nm
+the prompt type.
+This affects what completions are offered when
+.Em Tab
+is pressed.
+Available types are:
+.Ql command ,
+.Ql search ,
+.Ql target
+and
+.Ql window-target .
+.Pp
+The following keys have a special meaning in the command prompt, depending
+on the value of the
+.Ic status-keys
+option:
+.Bl -column "FunctionXXXXXXXXXXXXXXXXXXXXXXXXX" "viXXXX" "emacsX" -offset indent
+.It Sy "Function" Ta Sy "vi" Ta Sy "emacs"
+.It Li "Cancel command prompt" Ta "q" Ta "Escape"
+.It Li "Delete from cursor to start of word" Ta "" Ta "C-w"
+.It Li "Delete entire command" Ta "d" Ta "C-u"
+.It Li "Delete from cursor to end" Ta "D" Ta "C-k"
+.It Li "Execute command" Ta "Enter" Ta "Enter"
+.It Li "Get next command from history" Ta "" Ta "Down"
+.It Li "Get previous command from history" Ta "" Ta "Up"
+.It Li "Insert top paste buffer" Ta "p" Ta "C-y"
+.It Li "Look for completions" Ta "Tab" Ta "Tab"
+.It Li "Move cursor left" Ta "h" Ta "Left"
+.It Li "Move cursor right" Ta "l" Ta "Right"
+.It Li "Move cursor to end" Ta "$" Ta "C-e"
+.It Li "Move cursor to next word" Ta "w" Ta "M-f"
+.It Li "Move cursor to previous word" Ta "b" Ta "M-b"
+.It Li "Move cursor to start" Ta "0" Ta "C-a"
+.It Li "Transpose characters" Ta "" Ta "C-t"
+.El
+.Pp
+With
+.Fl b ,
+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 p Ar prompt
+.Op Fl t Ar target-client
+.Ar command
+.Xc
+.D1 Pq alias: Ic confirm
+Ask for confirmation before executing
+.Ar command .
+If
+.Fl p
+is given,
+.Ar prompt
+is the prompt to display; otherwise a prompt is constructed from
+.Ar command .
+It may contain the special character sequences supported by the
+.Ic status-left
+option.
+With
+.Fl b ,
+the prompt is shown in the background and the invoking client does not exit
+until it is dismissed.
+.Tg menu
+.It Xo Ic display-menu
+.Op Fl O
+.Op Fl c Ar target-client
+.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 ...
+.Xc
+.D1 Pq alias: Ic menu
+Display a menu on
+.Ar target-client .
+.Ar target-pane
+gives the target for any commands run from the menu.
+.Pp
+A menu is passed as a series of arguments: first the menu item name,
+second the key shortcut (or empty for none) and third the command
+to run when the menu item is chosen.
+The name and command are formats, see the
+.Sx FORMATS
+and
+.Sx STYLES
+sections.
+If the name begins with a hyphen (-), then the item is disabled (shown dim) and
+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 T
+is a format for the menu title (see
+.Sx FORMATS ) .
+.Pp
+.Fl x
+and
+.Fl y
+give the position of the menu.
+Both may be a row or column number, or one of the following special values:
+.Bl -column "XXXXX" "XXXX" -offset indent
+.It Sy "Value" Ta Sy "Flag" Ta Sy "Meaning"
+.It Li "C" Ta "Both" Ta "The centre of the terminal"
+.It Li "R" Ta Fl x Ta "The right side of the terminal"
+.It Li "P" Ta "Both" Ta "The bottom left of the pane"
+.It Li "M" Ta "Both" Ta "The mouse position"
+.It Li "W" Ta "Both" Ta "The window position on the status line"
+.It Li "S" Ta Fl y Ta "The line above or below the status line"
+.El
+.Pp
+Or a format, which is expanded including the following additional variables:
+.Bl -column "XXXXXXXXXXXXXXXXXXXXXXXXXX" -offset indent
+.It Sy "Variable name" Ta Sy "Replaced with"
+.It Li "popup_centre_x" Ta "Centered in the client"
+.It Li "popup_centre_y" Ta "Centered in the client"
+.It Li "popup_height" Ta "Height of menu or popup"
+.It Li "popup_mouse_bottom" Ta "Bottom of at the mouse"
+.It Li "popup_mouse_centre_x" Ta "Horizontal centre at the mouse"
+.It Li "popup_mouse_centre_y" Ta "Vertical centre at the mouse"
+.It Li "popup_mouse_top" Ta "Top at the mouse"
+.It Li "popup_mouse_x" Ta "Mouse X position"
+.It Li "popup_mouse_y" Ta "Mouse Y position"
+.It Li "popup_pane_bottom" Ta "Bottom of the pane"
+.It Li "popup_pane_left" Ta "Left of the pane"
+.It Li "popup_pane_right" Ta "Right of the pane"
+.It Li "popup_pane_top" Ta "Top of the pane"
+.It Li "popup_status_line_y" Ta "Above or below the status line"
+.It Li "popup_width" Ta "Width of menu or popup"
+.It Li "popup_window_status_line_x" Ta "At the window position in status line"
+.It Li "popup_window_status_line_y" Ta "At the status line showing the window"
+.El
+.Pp
+Each menu consists of items followed by a key shortcut shown in brackets.
+If the menu is too large to fit on the terminal, it is not displayed.
+Pressing the key shortcut chooses the corresponding item.
+If the mouse is enabled and the menu is opened from a mouse key binding,
+releasing the mouse button with an item selected chooses that item and
+releasing the mouse button without an item selected closes the menu.
+.Fl O
+changes this behaviour so that the menu does not close when the mouse button is
+released without an item selected the menu is not closed and a mouse button
+must be clicked to choose an item.
+.Pp
+The following keys are also available:
+.Bl -column "Key" "Function" -offset indent
+.It Sy "Key" Ta Sy "Function"
+.It Li "Enter" Ta "Choose selected item"
+.It Li "Up" Ta "Select previous item"
+.It Li "Down" Ta "Select next item"
+.It Li "q" Ta "Exit menu"
+.El
+.Tg display
+.It Xo Ic display-message
+.Op Fl aINpv
+.Op Fl c Ar target-client
+.Op Fl d Ar delay
+.Op Fl t Ar target-pane
+.Op Ar message
+.Xc
+.D1 Pq alias: Ic display
+Display a message.
+If
+.Fl p
+is given, the output is printed to stdout, otherwise it is displayed in the
+.Ar target-client
+status line for up to
+.Ar delay
+milliseconds.
+If
+.Ar delay
+is not given, the
+.Ic display-time
+option is used; a delay of zero waits for a key press.
+.Ql N
+ignores key presses and closes only after the delay expires.
+The format of
+.Ar message
+is described in the
+.Sx FORMATS
+section; information is taken from
+.Ar target-pane
+if
+.Fl t
+is given, otherwise the active pane.
+.Pp
+.Fl v
+prints verbose logging as the format is parsed and
+.Fl a
+lists the format variables and their values.
+.Pp
+.Fl I
+forwards any input read from stdin to the empty pane given by
+.Ar target-pane .
+.Tg popup
+.It Xo Ic display-popup
+.Op Fl BCE
+.Op Fl b Ar border-lines
+.Op Fl c Ar target-client
+.Op Fl d Ar start-directory
+.Op Fl e Ar environment
+.Op Fl h Ar height
+.Op Fl s Ar style
+.Op Fl S Ar border-style
+.Op Fl t Ar target-pane
+.Op Fl T Ar title
+.Op Fl w Ar width
+.Op Fl x Ar position
+.Op Fl y Ar position
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic popup
+Display a popup running
+.Ar shell-command
+on
+.Ar target-client .
+A popup is a rectangular box drawn over the top of any panes.
+Panes are not updated while a popup is present.
+.Pp
+.Fl E
+closes the popup automatically when
+.Ar shell-command
+exits.
+Two
+.Fl E
+closes the popup only if
+.Ar shell-command
+exited with success.
+.Pp
+.Fl x
+and
+.Fl y
+give the position of the popup, they have the same meaning as for the
+.Ic display-menu
+command.
+.Fl w
+and
+.Fl h
+give the width and height - both may be a percentage (followed by
+.Ql % ) .
+If omitted, half of the terminal size is used.
+.Pp
+.Fl B
+does not surround the popup by a border.
+.Pp
+.Fl b
+sets the type of border line for the popup.
+When
+.Fl B
+is specified, the
+.Fl b
+option is ignored.
+See
+.Ic popup-border-lines
+for possible values for
+.Ar border-lines .
+.Pp
+.Fl s
+sets the style for the popup and
+.Fl S
+sets the style for the popup border.
+For how to specify
+.Ar style ,
+see the
+.Sx STYLES
+section.
+.Pp
+.Fl e
+takes the form
+.Ql VARIABLE=value
+and sets an environment variable for the popup; it may be specified multiple
+times.
+.Pp
+.Fl T
+is a format for the popup title (see
+.Sx FORMATS ) .
+.Pp
+The
+.Fl C
+flag closes any popup on the client.
+.Tg showphist
+.It Xo Ic show-prompt-history
+.Op Fl T Ar prompt-type
+.Xc
+.D1 Pq alias: Ic showphist
+Display status prompt history for prompt type
+.Ar prompt-type .
+If
+.Fl T
+is omitted, then show history for all types.
+See
+.Ic command-prompt
+for possible values for
+.Ar prompt-type .
+.El
+.Sh BUFFERS
+.Nm
+maintains a set of named
+.Em paste buffers .
+Each buffer may be either explicitly or automatically named.
+Explicitly named buffers are named when created with the
+.Ic set-buffer
+or
+.Ic load-buffer
+commands, or by renaming an automatically named buffer with
+.Ic set-buffer
+.Fl n .
+Automatically named buffers are given a name such as
+.Ql buffer0001 ,
+.Ql buffer0002
+and so on.
+When the
+.Ic buffer-limit
+option is reached, the oldest automatically named buffer is deleted.
+Explicitly named buffers are not subject to
+.Ic buffer-limit
+and may be deleted with the
+.Ic delete-buffer
+command.
+.Pp
+Buffers may be added using
+.Ic copy-mode
+or the
+.Ic set-buffer
+and
+.Ic load-buffer
+commands, and pasted into a window using the
+.Ic paste-buffer
+command.
+If a buffer command is used and no buffer is specified, the most
+recently added automatically named buffer is assumed.
+.Pp
+A configurable history buffer is also maintained for each window.
+By default, up to 2000 lines are kept; this can be altered with the
+.Ic history-limit
+option (see the
+.Ic set-option
+command above).
+.Pp
+The buffer commands are as follows:
+.Bl -tag -width Ds
+.It Xo
+.Ic choose-buffer
+.Op Fl NZr
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Op Fl K Ar key-format
+.Op Fl O Ar sort-order
+.Op Fl t Ar target-pane
+.Op Ar template
+.Xc
+Put a pane into buffer mode, where a buffer may be chosen interactively from
+a list.
+Each buffer is shown on one line.
+A shortcut key is shown on the left in brackets allowing for immediate choice,
+or the list may be navigated and an item chosen or otherwise manipulated using
+the keys below.
+.Fl Z
+zooms the pane.
+The following keys may be used in buffer mode:
+.Bl -column "Key" "Function" -offset indent
+.It Sy "Key" Ta Sy "Function"
+.It Li "Enter" Ta "Paste selected buffer"
+.It Li "Up" Ta "Select previous buffer"
+.It Li "Down" Ta "Select next buffer"
+.It Li "C-s" Ta "Search by name or content"
+.It Li "n" Ta "Repeat last search"
+.It Li "t" Ta "Toggle if buffer is tagged"
+.It Li "T" Ta "Tag no buffers"
+.It Li "C-t" Ta "Tag all buffers"
+.It Li "p" Ta "Paste selected buffer"
+.It Li "P" Ta "Paste tagged buffers"
+.It Li "d" Ta "Delete selected buffer"
+.It Li "D" Ta "Delete tagged buffers"
+.It Li "e" Ta "Open the buffer in an editor"
+.It Li "f" Ta "Enter a format to filter items"
+.It Li "O" Ta "Change sort field"
+.It Li "r" Ta "Reverse sort order"
+.It Li "v" Ta "Toggle preview"
+.It Li "q" Ta "Exit mode"
+.El
+.Pp
+After a buffer is chosen,
+.Ql %%
+is replaced by the buffer name in
+.Ar template
+and the result executed as a command.
+If
+.Ar template
+is not given, "paste-buffer -b '%%'" is used.
+.Pp
+.Fl O
+specifies the initial sort field: one of
+.Ql time ,
+.Ql name
+or
+.Ql size .
+.Fl r
+reverses the sort order.
+.Fl f
+specifies an initial filter: the filter is a format - if it evaluates to zero,
+the item in the list is not shown, otherwise it is shown.
+If a filter would lead to an empty list, it is ignored.
+.Fl F
+specifies the format for each item in the list and
+.Fl K
+a format for each shortcut key; both are evaluated once for each line.
+.Fl N
+starts without the preview.
+This command works only if at least one client is attached.
+.Tg clearhist
+.It Ic clear-history Op Fl t Ar target-pane
+.D1 Pq alias: Ic clearhist
+Remove and free the history for the specified pane.
+.Tg deleteb
+.It Ic delete-buffer Op Fl b Ar buffer-name
+.D1 Pq alias: Ic deleteb
+Delete the buffer named
+.Ar buffer-name ,
+or the most recently added automatically named buffer if not specified.
+.Tg lsb
+.It Xo Ic list-buffers
+.Op Fl F Ar format
+.Op Fl f Ar filter
+.Xc
+.D1 Pq alias: Ic lsb
+List the global buffers.
+.Fl F
+specifies the format of each line and
+.Fl f
+a filter.
+Only buffers for which the filter is true are shown.
+See the
+.Sx FORMATS
+section.
+.It Xo Ic load-buffer
+.Op Fl w
+.Op Fl b Ar buffer-name
+.Op Fl t Ar target-client
+.Ar path
+.Xc
+.Tg loadb
+.D1 Pq alias: Ic loadb
+Load the contents of the specified paste buffer from
+.Ar path .
+If
+.Fl w
+is given, the buffer is also sent to the clipboard for
+.Ar target-client
+using the
+.Xr xterm 1
+escape sequence, if possible.
+.Tg pasteb
+.It Xo Ic paste-buffer
+.Op Fl dpr
+.Op Fl b Ar buffer-name
+.Op Fl s Ar separator
+.Op Fl t Ar target-pane
+.Xc
+.D1 Pq alias: Ic pasteb
+Insert the contents of a paste buffer into the specified pane.
+If not specified, paste into the current one.
+With
+.Fl d ,
+also delete the paste buffer.
+When output, any linefeed (LF) characters in the paste buffer are replaced with
+a separator, by default carriage return (CR).
+A custom separator may be specified using the
+.Fl s
+flag.
+The
+.Fl r
+flag means to do no replacement (equivalent to a separator of LF).
+If
+.Fl p
+is specified, paste bracket control codes are inserted around the
+buffer if the application has requested bracketed paste mode.
+.Tg saveb
+.It Xo Ic save-buffer
+.Op Fl a
+.Op Fl b Ar buffer-name
+.Ar path
+.Xc
+.D1 Pq alias: Ic saveb
+Save the contents of the specified paste buffer to
+.Ar path .
+The
+.Fl a
+option appends to rather than overwriting the file.
+.It Xo Ic set-buffer
+.Op Fl aw
+.Op Fl b Ar buffer-name
+.Op Fl t Ar target-client
+.Tg setb
+.Op Fl n Ar new-buffer-name
+.Ar data
+.Xc
+.D1 Pq alias: Ic setb
+Set the contents of the specified buffer to
+.Ar data .
+If
+.Fl w
+is given, the buffer is also sent to the clipboard for
+.Ar target-client
+using the
+.Xr xterm 1
+escape sequence, if possible.
+The
+.Fl a
+option appends to rather than overwriting the buffer.
+The
+.Fl n
+option renames the buffer to
+.Ar new-buffer-name .
+.Tg showb
+.It Xo Ic show-buffer
+.Op Fl b Ar buffer-name
+.Xc
+.D1 Pq alias: Ic showb
+Display the contents of the specified buffer.
+.El
+.Sh MISCELLANEOUS
+Miscellaneous commands are as follows:
+.Bl -tag -width Ds
+.It Ic clock-mode Op Fl t Ar target-pane
+Display a large clock.
+.Tg if
+.It Xo Ic if-shell
+.Op Fl bF
+.Op Fl t Ar target-pane
+.Ar shell-command command
+.Op Ar command
+.Xc
+.D1 Pq alias: Ic if
+Execute the first
+.Ar command
+if
+.Ar shell-command
+(run with
+.Pa /bin/sh )
+returns success or the second
+.Ar command
+otherwise.
+Before being executed,
+.Ar shell-command
+is expanded using the rules specified in the
+.Sx FORMATS
+section, including those relevant to
+.Ar target-pane .
+With
+.Fl b ,
+.Ar shell-command
+is run in the background.
+.Pp
+If
+.Fl F
+is given,
+.Ar shell-command
+is not executed but considered success if neither empty nor zero (after formats
+are expanded).
+.Tg lock
+.It Ic lock-server
+.D1 Pq alias: Ic lock
+Lock each client individually by running the command specified by the
+.Ic lock-command
+option.
+.Tg run
+.It Xo Ic run-shell
+.Op Fl bC
+.Op Fl d Ar delay
+.Op Fl t Ar target-pane
+.Op Ar shell-command
+.Xc
+.D1 Pq alias: Ic run
+Execute
+.Ar shell-command
+using
+.Pa /bin/sh
+or (with
+.Fl C )
+a
+.Nm
+command in the background without creating a window.
+Before being executed,
+.Ar shell-command
+is expanded using the rules specified in the
+.Sx FORMATS
+section.
+With
+.Fl b ,
+the command is run in the background.
+.Fl d
+waits for
+.Ar delay
+seconds before starting the command.
+If
+.Fl C
+is not given, any output to stdout is displayed in view mode (in the pane
+specified by
+.Fl t
+or the current pane if omitted) after the command finishes.
+If the command fails, the exit status is also displayed.
+.Tg wait
+.It Xo Ic wait-for
+.Op Fl L | S | U
+.Ar channel
+.Xc
+.D1 Pq alias: Ic wait
+When used without options, prevents the client from exiting until woken using
+.Ic wait-for
+.Fl S
+with the same channel.
+When
+.Fl L
+is used, the channel is locked and any clients that try to lock the same
+channel are made to wait until the channel is unlocked with
+.Ic wait-for
+.Fl U .
+.El
+.Sh EXIT MESSAGES
+When a
+.Nm
+client detaches, it prints a message.
+This may be one of:
+.Bl -tag -width Ds
+.It detached (from session ...)
+The client was detached normally.
+.It detached and SIGHUP
+The client was detached and its parent sent the
+.Dv SIGHUP
+signal (for example with
+.Ic detach-client
+.Fl P ) .
+.It lost tty
+The client's
+.Xr tty 4
+or
+.Xr pty 4
+was unexpectedly destroyed.
+.It terminated
+The client was killed with
+.Dv SIGTERM .
+.It too far behind
+The client is in control mode and became unable to keep up with the data from
+.Nm .
+.It exited
+The server exited when it had no sessions.
+.It server exited
+The server exited when it received
+.Dv SIGTERM .
+.It server exited unexpectedly
+The server crashed or otherwise exited without telling the client the reason.
+.El
+.Sh TERMINFO EXTENSIONS
+.Nm
+understands some unofficial extensions to
+.Xr terminfo 5 .
+It is not normally necessary to set these manually, instead the
+.Ic terminal-features
+option should be used.
+.Bl -tag -width Ds
+.It Em \&AX
+An existing extension that tells
+.Nm
+the terminal supports default colours.
+.It Em \&Bidi
+Tell
+.Nm
+that the terminal supports the VTE bidirectional text extensions.
+.It Em \&Cs , Cr
+Set the cursor colour.
+The first takes a single string argument and is used to set the colour;
+the second takes no arguments and restores the default cursor colour.
+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'
+.Ed
+.Pp
+The colour is an
+.Xr X 7
+colour, see
+.Xr XParseColor 3 .
+.It Em \&Cmg, \&Clmg, \&Dsmg , \&Enmg
+Set, clear, disable or enable DECSLRM margins.
+These are set automatically if the terminal reports it is
+.Em VT420
+compatible.
+.It Em \&Dsbp , \&Enbp
+Disable and enable bracketed paste.
+These are set automatically if the
+.Em XT
+capability is present.
+.It Em \&Dseks , \&Eneks
+Disable and enable extended keys.
+.It Em \&Dsfcs , \&Enfcs
+Disable and enable focus reporting.
+These are set automatically if the
+.Em XT
+capability is present.
+.It Em \&Rect
+Tell
+.Nm
+that the terminal supports rectangle operations.
+.It Em \&Smol
+Enable the overline attribute.
+.It Em \&Smulx
+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
+Set the underscore colour or reset to the default.
+The 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'
+.Ed
+.Pp
+If
+.Em Se
+is not set, \&Ss with argument 0 will be used to reset the cursor style instead.
+.It Em \&Swd
+Set the opening sequence for the working directory notification.
+The sequence is terminated using the standard
+.Em fsl
+capability.
+.It Em \&Sync
+Start (parameter is 1) or end (parameter is 2) a synchronized update.
+.It Em \&Tc
+Indicate that the terminal supports the
+.Ql direct colour
+RGB escape sequence (for example, \ee[38;2;255;255;255m).
+.Pp
+If supported, this is used for the initialize colour escape sequence (which
+may be enabled by adding the
+.Ql initc
+and
+.Ql ccc
+capabilities to the
+.Nm
+.Xr terminfo 5
+entry).
+.Pp
+This is equivalent to the
+.Em RGB
+.Xr terminfo 5
+capability.
+.It Em \&Ms
+Store the current buffer in the host terminal's selection (clipboard).
+See the
+.Em set-clipboard
+option above and the
+.Xr xterm 1
+man page.
+.It Em \&XT
+This is an existing extension capability that tmux uses to mean that the
+terminal supports the
+.Xr xterm 1
+title set sequences and to automatically set some of the capabilities above.
+.El
+.Sh CONTROL MODE
+.Nm
+offers a textual interface called
+.Em control mode .
+This allows applications to communicate with
+.Nm
+using a simple text-only protocol.
+.Pp
+In control mode, a client sends
+.Nm
+commands or command sequences terminated by newlines on standard input.
+Each command will produce one block of output on standard output.
+An output block consists of a
+.Em %begin
+line followed by the output (which may be empty).
+The output block ends with a
+.Em %end
+or
+.Em %error .
+.Em %begin
+and matching
+.Em %end
+or
+.Em %error
+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
+0: ksh* (1 panes) [80x24] [layout b25f,80x24,0,0,2] @2 (active)
+%end 1363006971 2 1
+.Ed
+.Pp
+The
+.Ic refresh-client
+.Fl C
+command may be used to set the size of a client in control mode.
+.Pp
+In control mode,
+.Nm
+outputs notifications.
+A notification will never occur inside an output block.
+.Pp
+The following notifications are defined:
+.Bl -tag -width Ds
+.It Ic %client-detached Ar client
+The client has detached.
+.It Ic %client-session-changed Ar client session-id name
+The client is now attached to the session with ID
+.Ar session-id ,
+which is named
+.Ar name .
+.It Ic %continue Ar pane-id
+The pane has been continued after being paused (if the
+.Ar pause-after
+flag is set, see
+.Ic refresh-client
+.Fl A ) .
+.It Ic %exit Op Ar reason
+The
+.Nm
+client is exiting immediately, either because it is not attached to any session
+or an error occurred.
+If present,
+.Ar reason
+describes why the client exited.
+.It Ic %extended-output Ar pane-id Ar age Ar ... \& : Ar value
+New form of
+.Ic %output
+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.
+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
+The layout of a window with ID
+.Ar window-id
+changed.
+The new layout is
+.Ar window-layout .
+The window's visible layout is
+.Ar window-visible-layout
+and the window flags are
+.Ar window-flags .
+.It Ic %output Ar pane-id Ar value
+A window pane produced output.
+.Ar value
+escapes non-printable characters and backslash as octal \\xxx.
+.It Ic %pane-mode-changed Ar pane-id
+The pane with ID
+.Ar pane-id
+has changed mode.
+.It Ic %pause Ar pane-id
+The pane has been paused (if the
+.Ar pause-after
+flag is set).
+.It Ic %session-changed Ar session-id Ar name
+The client is now attached to the session with ID
+.Ar session-id ,
+which is named
+.Ar name .
+.It Ic %session-renamed Ar name
+The current session was renamed to
+.Ar name .
+.It Ic %session-window-changed Ar session-id Ar window-id
+The session with ID
+.Ar session-id
+changed its active window to the window with ID
+.Ar window-id .
+.It Ic %sessions-changed
+A session was created or destroyed.
+.It Xo Ic %subscription-changed
+.Ar name
+.Ar session-id
+.Ar window-id
+.Ar window-index
+.Ar pane-id ... \& :
+.Ar value
+.Xc
+The value of the format associated with subscription
+.Ar name
+has changed to
+.Ar value .
+See
+.Ic refresh-client
+.Fl B .
+Any arguments after
+.Ar pane-id
+up until a single
+.Ql \&:
+are for future use and should be ignored.
+.It Ic %unlinked-window-add Ar window-id
+The window with ID
+.Ar window-id
+was created but is not linked to the current session.
+.It Ic %unlinked-window-close Ar window-id
+The window with ID
+.Ar window-id ,
+which is not linked to the current session, was closed.
+.It Ic %unlinked-window-renamed Ar window-id
+The window with ID
+.Ar window-id ,
+which is not linked to the current session, was renamed.
+.It Ic %window-add Ar window-id
+The window with ID
+.Ar window-id
+was linked to the current session.
+.It Ic %window-close Ar window-id
+The window with ID
+.Ar window-id
+closed.
+.It Ic %window-pane-changed Ar window-id Ar pane-id
+The active pane in the window with ID
+.Ar window-id
+changed to the pane with ID
+.Ar pane-id .
+.It Ic %window-renamed Ar window-id Ar name
+The window with ID
+.Ar window-id
+was renamed to
+.Ar name .
+.El
+.Sh ENVIRONMENT
+When
+.Nm
+is started, it inspects the following environment variables:
+.Bl -tag -width LC_CTYPE
+.It Ev EDITOR
+If the command specified in this variable contains the string
+.Ql vi
+and
+.Ev VISUAL
+is unset, use vi-style key bindings.
+Overridden by the
+.Ic mode-keys
+and
+.Ic status-keys
+options.
+.It Ev HOME
+The user's login directory.
+If unset, the
+.Xr passwd 5
+database is consulted.
+.It Ev LC_CTYPE
+The character encoding
+.Xr locale 1 .
+It is used for two separate purposes.
+For output to the terminal, UTF-8 is used if the
+.Fl u
+option is given or if
+.Ev LC_CTYPE
+contains
+.Qq UTF-8
+or
+.Qq UTF8 .
+Otherwise, only ASCII characters are written and non-ASCII characters
+are replaced with underscores
+.Pq Ql _ .
+For input,
+.Nm
+always runs with a UTF-8 locale.
+If en_US.UTF-8 is provided by the operating system, it is used and
+.Ev LC_CTYPE
+is ignored for input.
+Otherwise,
+.Ev LC_CTYPE
+tells
+.Nm
+what the UTF-8 locale is called on the current system.
+If the locale specified by
+.Ev LC_CTYPE
+is not available or is not a UTF-8 locale,
+.Nm
+exits with an error message.
+.It Ev LC_TIME
+The date and time format
+.Xr locale 1 .
+It is used for locale-dependent
+.Xr strftime 3
+format specifiers.
+.It Ev PWD
+The current working directory to be set in the global environment.
+This may be useful if it contains symbolic links.
+If the value of the variable does not match the current working
+directory, the variable is ignored and the result of
+.Xr getcwd 3
+is used instead.
+.It Ev SHELL
+The absolute path to the default shell for new windows.
+See the
+.Ic default-shell
+option for details.
+.It Ev TMUX_TMPDIR
+The parent directory of the directory containing the server sockets.
+See the
+.Fl L
+option for details.
+.It Ev VISUAL
+If the command specified in this variable contains the string
+.Ql vi ,
+use vi-style key bindings.
+Overridden by the
+.Ic mode-keys
+and
+.Ic status-keys
+options.
+.El
+.Sh FILES
+.Bl -tag -width "@SYSCONFDIR@/tmux.confXXX" -compact
+.It Pa ~/.tmux.conf
+.It Pa $XDG_CONFIG_HOME/tmux/tmux.conf
+.It Pa ~/.config/tmux/tmux.conf
+Default
+.Nm
+configuration file.
+.It Pa @SYSCONFDIR@/tmux.conf
+System-wide configuration file.
+.El
+.Sh EXAMPLES
+To create a new
+.Nm
+session running
+.Xr vi 1 :
+.Pp
+.Dl $ tmux new-session vi
+.Pp
+Most commands have a shorter form, known as an alias.
+For new-session, this is
+.Ic new :
+.Pp
+.Dl $ tmux new vi
+.Pp
+Alternatively, the shortest unambiguous form of a command is accepted.
+If there are several options, they are listed:
+.Bd -literal -offset indent
+$ tmux n
+ambiguous command: n, could be: new-session, new-window, next-window
+.Ed
+.Pp
+Within an active session, a new window may be created by typing
+.Ql C-b c
+(Ctrl
+followed by the
+.Ql b
+key
+followed by the
+.Ql c
+key).
+.Pp
+Windows may be navigated with:
+.Ql C-b 0
+(to select window 0),
+.Ql C-b 1
+(to select window 1), and so on;
+.Ql C-b n
+to select the next window; and
+.Ql C-b p
+to select the previous window.
+.Pp
+A session may be detached using
+.Ql C-b d
+(or by an external event such as
+.Xr ssh 1
+disconnection) and reattached with:
+.Pp
+.Dl $ tmux attach-session
+.Pp
+Typing
+.Ql C-b \&?
+lists the current key bindings in the current window; up and down may be used
+to navigate the list or
+.Ql q
+to exit from it.
+.Pp
+Commands to be run when the
+.Nm
+server is started may be placed in the
+.Pa ~/.tmux.conf
+configuration file.
+Common examples include:
+.Pp
+Changing the default prefix key:
+.Bd -literal -offset indent
+set-option -g prefix C-a
+unbind-key C-b
+bind-key C-a send-prefix
+.Ed
+.Pp
+Turning the status line off, or changing its colour:
+.Bd -literal -offset indent
+set-option -g status off
+set-option -g status-style bg=blue
+.Ed
+.Pp
+Setting other options, such as the default command,
+or locking after 30 minutes of inactivity:
+.Bd -literal -offset indent
+set-option -g default-command "exec /bin/ksh"
+set-option -g lock-after-time 1800
+.Ed
+.Pp
+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'"
+.Ed
+.Sh SEE ALSO
+.Xr pty 4
+.Sh AUTHORS
+.An Nicholas Marriott Aq Mt nicholas.marriott@gmail.com
diff --git a/tmux.c b/tmux.c
new file mode 100644
index 0000000..b9f2be3
--- /dev/null
+++ b/tmux.c
@@ -0,0 +1,520 @@
+/* $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 <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+struct options *global_options; /* server options */
+struct options *global_s_options; /* session options */
+struct options *global_w_options; /* window options */
+struct environ *global_environ;
+
+struct timeval start_time;
+const char *socket_path;
+int ptm_fd = -1;
+const char *shell_command;
+
+static __dead void usage(void);
+static char *make_label(const char *, char **);
+
+static int areshell(const char *);
+static const char *getshell(void);
+
+static __dead void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: %s [-2CDlNuvV] [-c shell-command] [-f file] [-L socket-name]\n"
+ " [-S socket-path] [-T features] [command [flags]]\n",
+ getprogname());
+ exit(1);
+}
+
+static const char *
+getshell(void)
+{
+ struct passwd *pw;
+ const char *shell;
+
+ shell = getenv("SHELL");
+ if (checkshell(shell))
+ return (shell);
+
+ pw = getpwuid(getuid());
+ if (pw != NULL && checkshell(pw->pw_shell))
+ return (pw->pw_shell);
+
+ return (_PATH_BSHELL);
+}
+
+int
+checkshell(const char *shell)
+{
+ if (shell == NULL || *shell != '/')
+ return (0);
+ if (areshell(shell))
+ return (0);
+ if (access(shell, X_OK) != 0)
+ return (0);
+ return (1);
+}
+
+static int
+areshell(const char *shell)
+{
+ const char *progname, *ptr;
+
+ if ((ptr = strrchr(shell, '/')) != NULL)
+ ptr++;
+ else
+ ptr = shell;
+ progname = getprogname();
+ if (*progname == '-')
+ progname++;
+ if (strcmp(ptr, progname) == 0)
+ return (1);
+ return (0);
+}
+
+static char *
+expand_path(const char *path, const char *home)
+{
+ char *expanded, *name;
+ const char *end;
+ struct environ_entry *value;
+
+ if (strncmp(path, "~/", 2) == 0) {
+ if (home == NULL)
+ return (NULL);
+ xasprintf(&expanded, "%s%s", home, path + 1);
+ return (expanded);
+ }
+
+ if (*path == '$') {
+ end = strchr(path, '/');
+ if (end == NULL)
+ name = xstrdup(path + 1);
+ else
+ name = xstrndup(path + 1, end - path - 1);
+ value = environ_find(global_environ, name);
+ free(name);
+ if (value == NULL)
+ return (NULL);
+ if (end == NULL)
+ end = "";
+ xasprintf(&expanded, "%s%s", value->value, end);
+ return (expanded);
+ }
+
+ return (xstrdup(path));
+}
+
+static void
+expand_paths(const char *s, char ***paths, u_int *n, int ignore_errors)
+{
+ const char *home = find_home();
+ char *copy, *next, *tmp, resolved[PATH_MAX], *expanded;
+ char *path;
+ u_int i;
+
+ *paths = NULL;
+ *n = 0;
+
+ copy = tmp = xstrdup(s);
+ while ((next = strsep(&tmp, ":")) != NULL) {
+ expanded = expand_path(next, home);
+ if (expanded == NULL) {
+ log_debug("%s: invalid path: %s", __func__, next);
+ continue;
+ }
+ if (realpath(expanded, resolved) == NULL) {
+ log_debug("%s: realpath(\"%s\") failed: %s", __func__,
+ expanded, strerror(errno));
+ if (ignore_errors) {
+ free(expanded);
+ continue;
+ }
+ path = expanded;
+ } else {
+ path = xstrdup(resolved);
+ free(expanded);
+ }
+ for (i = 0; i < *n; i++) {
+ if (strcmp(path, (*paths)[i]) == 0)
+ break;
+ }
+ if (i != *n) {
+ log_debug("%s: duplicate path: %s", __func__, path);
+ free(path);
+ continue;
+ }
+ *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths);
+ (*paths)[(*n)++] = path;
+ }
+ free(copy);
+}
+
+static char *
+make_label(const char *label, char **cause)
+{
+ char **paths, *path, *base;
+ u_int i, n;
+ struct stat sb;
+ uid_t uid;
+
+ *cause = NULL;
+ if (label == NULL)
+ label = "default";
+ uid = getuid();
+
+ expand_paths(TMUX_SOCK, &paths, &n, 1);
+ if (n == 0) {
+ xasprintf(cause, "no suitable socket path");
+ return (NULL);
+ }
+ path = paths[0]; /* can only have one socket! */
+ for (i = 1; i < n; i++)
+ free(paths[i]);
+ free(paths);
+
+ xasprintf(&base, "%s/tmux-%ld", path, (long)uid);
+ free(path);
+ if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) {
+ xasprintf(cause, "couldn't create directory %s (%s)", base,
+ strerror(errno));
+ goto fail;
+ }
+ if (lstat(base, &sb) != 0) {
+ xasprintf(cause, "couldn't read directory %s (%s)", base,
+ strerror(errno));
+ goto fail;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ xasprintf(cause, "%s is not a directory", base);
+ goto fail;
+ }
+ if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) {
+ xasprintf(cause, "directory %s has unsafe permissions", base);
+ goto fail;
+ }
+ xasprintf(&path, "%s/%s", base, label);
+ free(base);
+ return (path);
+
+fail:
+ free(base);
+ return (NULL);
+}
+
+void
+setblocking(int fd, int state)
+{
+ int mode;
+
+ if ((mode = fcntl(fd, F_GETFL)) != -1) {
+ if (!state)
+ mode |= O_NONBLOCK;
+ else
+ mode &= ~O_NONBLOCK;
+ fcntl(fd, F_SETFL, mode);
+ }
+}
+
+uint64_t
+get_timer(void)
+{
+ struct timespec ts;
+
+ /*
+ * We want a timestamp in milliseconds suitable for time measurement,
+ * so prefer the monotonic clock.
+ */
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+ clock_gettime(CLOCK_REALTIME, &ts);
+ return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL));
+}
+
+const char *
+sig2name(int signo)
+{
+ static char s[11];
+
+#ifdef HAVE_SYS_SIGNAME
+ if (signo > 0 && signo < NSIG)
+ return (sys_signame[signo]);
+#endif
+ xsnprintf(s, sizeof s, "%d", signo);
+ return (s);
+}
+
+const char *
+find_cwd(void)
+{
+ char resolved1[PATH_MAX], resolved2[PATH_MAX];
+ static char cwd[PATH_MAX];
+ const char *pwd;
+
+ if (getcwd(cwd, sizeof cwd) == NULL)
+ return (NULL);
+ if ((pwd = getenv("PWD")) == NULL || *pwd == '\0')
+ return (cwd);
+
+ /*
+ * We want to use PWD so that symbolic links are maintained,
+ * but only if it matches the actual working directory.
+ */
+ if (realpath(pwd, resolved1) == NULL)
+ return (cwd);
+ if (realpath(cwd, resolved2) == NULL)
+ return (cwd);
+ if (strcmp(resolved1, resolved2) != 0)
+ return (cwd);
+ return (pwd);
+}
+
+const char *
+find_home(void)
+{
+ struct passwd *pw;
+ static const char *home;
+
+ if (home != NULL)
+ return (home);
+
+ home = getenv("HOME");
+ if (home == NULL || *home == '\0') {
+ pw = getpwuid(getuid());
+ if (pw != NULL)
+ home = pw->pw_dir;
+ else
+ home = NULL;
+ }
+
+ return (home);
+}
+
+const char *
+getversion(void)
+{
+ return (TMUX_VERSION);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *path = NULL, *label = NULL;
+ char *cause, **var;
+ const char *s, *cwd;
+ int opt, keys, feat = 0, fflag = 0;
+ uint64_t flags = 0;
+ const struct options_table_entry *oe;
+ u_int i;
+
+ if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
+ setlocale(LC_CTYPE, "C.UTF-8") == NULL) {
+ if (setlocale(LC_CTYPE, "") == NULL)
+ errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
+ s = nl_langinfo(CODESET);
+ if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
+ errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
+ }
+
+ setlocale(LC_TIME, "");
+ tzset();
+
+ if (**argv == '-')
+ flags = CLIENT_LOGIN;
+
+ global_environ = environ_create();
+ for (var = environ; *var != NULL; var++)
+ environ_put(global_environ, *var, 0);
+ if ((cwd = find_cwd()) != NULL)
+ environ_set(global_environ, "PWD", 0, "%s", cwd);
+ expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1);
+
+ while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) {
+ switch (opt) {
+ case '2':
+ tty_add_features(&feat, "256", ":,");
+ break;
+ case 'c':
+ shell_command = optarg;
+ break;
+ case 'D':
+ flags |= CLIENT_NOFORK;
+ break;
+ case 'C':
+ if (flags & CLIENT_CONTROL)
+ flags |= CLIENT_CONTROLCONTROL;
+ else
+ flags |= CLIENT_CONTROL;
+ break;
+ case 'f':
+ if (!fflag) {
+ fflag = 1;
+ for (i = 0; i < cfg_nfiles; i++)
+ free(cfg_files[i]);
+ cfg_nfiles = 0;
+ }
+ cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1,
+ sizeof *cfg_files);
+ cfg_files[cfg_nfiles++] = xstrdup(optarg);
+ cfg_quiet = 0;
+ break;
+ case 'V':
+ printf("%s %s\n", getprogname(), getversion());
+ exit(0);
+ case 'l':
+ flags |= CLIENT_LOGIN;
+ break;
+ case 'L':
+ free(label);
+ label = xstrdup(optarg);
+ break;
+ case 'N':
+ flags |= CLIENT_NOSTARTSERVER;
+ break;
+ case 'q':
+ break;
+ case 'S':
+ free(path);
+ path = xstrdup(optarg);
+ break;
+ case 'T':
+ tty_add_features(&feat, optarg, ":,");
+ break;
+ case 'u':
+ flags |= CLIENT_UTF8;
+ break;
+ case 'v':
+ log_add_level();
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (shell_command != NULL && argc != 0)
+ usage();
+ if ((flags & CLIENT_NOFORK) && argc != 0)
+ usage();
+
+ if ((ptm_fd = getptmfd()) == -1)
+ err(1, "getptmfd");
+ if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
+ "recvfd proc exec tty ps", NULL) != 0)
+ err(1, "pledge");
+
+ /*
+ * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
+ * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
+ * UTF-8, it is a safe assumption that either they are using a UTF-8
+ * terminal, or if not they know that output from UTF-8-capable
+ * programs may be wrong.
+ */
+ if (getenv("TMUX") != NULL)
+ flags |= CLIENT_UTF8;
+ else {
+ s = getenv("LC_ALL");
+ if (s == NULL || *s == '\0')
+ s = getenv("LC_CTYPE");
+ if (s == NULL || *s == '\0')
+ s = getenv("LANG");
+ if (s == NULL || *s == '\0')
+ s = "";
+ if (strcasestr(s, "UTF-8") != NULL ||
+ strcasestr(s, "UTF8") != NULL)
+ flags |= CLIENT_UTF8;
+ }
+
+ global_options = options_create(NULL);
+ global_s_options = options_create(NULL);
+ global_w_options = options_create(NULL);
+ for (oe = options_table; oe->name != NULL; oe++) {
+ if (oe->scope & OPTIONS_TABLE_SERVER)
+ options_default(global_options, oe);
+ if (oe->scope & OPTIONS_TABLE_SESSION)
+ options_default(global_s_options, oe);
+ if (oe->scope & OPTIONS_TABLE_WINDOW)
+ options_default(global_w_options, oe);
+ }
+
+ /*
+ * The default shell comes from SHELL or from the user's passwd entry
+ * if available.
+ */
+ options_set_string(global_s_options, "default-shell", 0, "%s",
+ getshell());
+
+ /* Override keys to vi if VISUAL or EDITOR are set. */
+ if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
+ options_set_string(global_options, "editor", 0, "%s", s);
+ if (strrchr(s, '/') != NULL)
+ s = strrchr(s, '/') + 1;
+ if (strstr(s, "vi") != NULL)
+ keys = MODEKEY_VI;
+ else
+ keys = MODEKEY_EMACS;
+ options_set_number(global_s_options, "status-keys", keys);
+ options_set_number(global_w_options, "mode-keys", keys);
+ }
+
+ /*
+ * If socket is specified on the command-line with -S or -L, it is
+ * used. Otherwise, $TMUX is checked and if that fails "default" is
+ * used.
+ */
+ if (path == NULL && label == NULL) {
+ s = getenv("TMUX");
+ if (s != NULL && *s != '\0' && *s != ',') {
+ path = xstrdup(s);
+ path[strcspn(path, ",")] = '\0';
+ }
+ }
+ if (path == NULL) {
+ if ((path = make_label(label, &cause)) == NULL) {
+ if (cause != NULL) {
+ fprintf(stderr, "%s\n", cause);
+ free(cause);
+ }
+ exit(1);
+ }
+ flags |= CLIENT_DEFAULTSOCKET;
+ }
+ socket_path = path;
+ free(label);
+
+ /* Pass control to the client. */
+ exit(client_main(osdep_event_init(), argc, argv, flags, feat));
+}
diff --git a/tmux.h b/tmux.h
new file mode 100644
index 0000000..53084b8
--- /dev/null
+++ b/tmux.h
@@ -0,0 +1,3291 @@
+/* $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.
+ */
+
+#ifndef TMUX_H
+#define TMUX_H
+
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <termios.h>
+
+#ifdef HAVE_UTEMPTER
+#include <utempter.h>
+#endif
+
+#include "compat.h"
+#include "tmux-protocol.h"
+#include "xmalloc.h"
+
+extern char **environ;
+
+struct args;
+struct args_command_state;
+struct client;
+struct cmd;
+struct cmd_find_state;
+struct cmdq_item;
+struct cmdq_list;
+struct cmdq_state;
+struct cmds;
+struct control_state;
+struct environ;
+struct format_job_tree;
+struct format_tree;
+struct input_ctx;
+struct job;
+struct menu_data;
+struct mode_tree_data;
+struct mouse_event;
+struct options;
+struct options_array_item;
+struct options_entry;
+struct screen_write_citem;
+struct screen_write_cline;
+struct screen_write_ctx;
+struct session;
+struct tty_ctx;
+struct tty_code;
+struct tty_key;
+struct tmuxpeer;
+struct tmuxproc;
+struct winlink;
+
+/* Default configuration files and socket paths. */
+#ifndef TMUX_CONF
+#define TMUX_CONF "/etc/tmux.conf:~/.tmux.conf"
+#endif
+#ifndef TMUX_SOCK
+#define TMUX_SOCK "$TMUX_TMPDIR:" _PATH_TMP
+#endif
+#ifndef TMUX_TERM
+#define TMUX_TERM "screen"
+#endif
+
+/* Minimum layout cell size, NOT including border lines. */
+#define PANE_MINIMUM 1
+
+/* Minimum and maximum window size. */
+#define WINDOW_MINIMUM PANE_MINIMUM
+#define WINDOW_MAXIMUM 10000
+
+/* Automatic name refresh interval, in microseconds. Must be < 1 second. */
+#define NAME_INTERVAL 500000
+
+/* Default pixel cell sizes. */
+#define DEFAULT_XPIXEL 16
+#define DEFAULT_YPIXEL 32
+
+/* Attribute to make GCC check printf-like arguments. */
+#define printflike(a, b) __attribute__ ((format (printf, a, b)))
+
+/* Number of items in array. */
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+/* Alert option values. */
+#define ALERT_NONE 0
+#define ALERT_ANY 1
+#define ALERT_CURRENT 2
+#define ALERT_OTHER 3
+
+/* Visual option values. */
+#define VISUAL_OFF 0
+#define VISUAL_ON 1
+#define VISUAL_BOTH 2
+
+/* No key or unknown key. */
+#define KEYC_NONE 0x000ff000000000ULL
+#define KEYC_UNKNOWN 0x000fe000000000ULL
+
+/*
+ * Base for special (that is, not Unicode) keys. An enum must be at most a
+ * signed int, so these are based in the highest Unicode PUA.
+ */
+#define KEYC_BASE 0x0000000010e000ULL
+#define KEYC_USER 0x0000000010f000ULL
+
+/* Key modifier bits. */
+#define KEYC_META 0x00100000000000ULL
+#define KEYC_CTRL 0x00200000000000ULL
+#define KEYC_SHIFT 0x00400000000000ULL
+
+/* Key flag bits. */
+#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
+
+/* Masks for key bits. */
+#define KEYC_MASK_MODIFIERS 0x00f00000000000ULL
+#define KEYC_MASK_FLAGS 0xff000000000000ULL
+#define KEYC_MASK_KEY 0x000fffffffffffULL
+
+/* Available user keys. */
+#define KEYC_NUSER 1000
+
+/* Is this a mouse key? */
+#define KEYC_IS_MOUSE(key) \
+ (((key) & KEYC_MASK_KEY) >= KEYC_MOUSE && \
+ ((key) & KEYC_MASK_KEY) < KEYC_BSPACE)
+
+/* Is this a Unicode key? */
+#define KEYC_IS_UNICODE(key) \
+ (((key) & KEYC_MASK_KEY) > 0x7f && \
+ (((key) & KEYC_MASK_KEY) < KEYC_BASE || \
+ ((key) & KEYC_MASK_KEY) >= KEYC_BASE_END))
+
+/* Multiple click timeout. */
+#define KEYC_CLICK_TIMEOUT 300
+
+/* Mouse key codes. */
+#define KEYC_MOUSE_KEY(name) \
+ KEYC_ ## name ## _PANE, \
+ KEYC_ ## name ## _STATUS, \
+ KEYC_ ## name ## _STATUS_LEFT, \
+ KEYC_ ## name ## _STATUS_RIGHT, \
+ KEYC_ ## name ## _STATUS_DEFAULT, \
+ KEYC_ ## name ## _BORDER
+#define KEYC_MOUSE_STRING(name, s) \
+ { #s "Pane", KEYC_ ## name ## _PANE }, \
+ { #s "Status", KEYC_ ## name ## _STATUS }, \
+ { #s "StatusLeft", KEYC_ ## name ## _STATUS_LEFT }, \
+ { #s "StatusRight", KEYC_ ## name ## _STATUS_RIGHT }, \
+ { #s "StatusDefault", KEYC_ ## name ## _STATUS_DEFAULT }, \
+ { #s "Border", KEYC_ ## name ## _BORDER }
+
+/*
+ * A single key. This can be ASCII or Unicode or one of the keys between
+ * KEYC_BASE and KEYC_BASE_END.
+ */
+typedef unsigned long long key_code;
+
+/* Special key codes. */
+enum {
+ /* Focus events. */
+ KEYC_FOCUS_IN = KEYC_BASE,
+ KEYC_FOCUS_OUT,
+
+ /* "Any" key, used if not found in key table. */
+ KEYC_ANY,
+
+ /* Paste brackets. */
+ KEYC_PASTE_START,
+ KEYC_PASTE_END,
+
+ /* Mouse keys. */
+ KEYC_MOUSE, /* unclassified mouse event */
+ KEYC_DRAGGING, /* dragging in progress */
+ KEYC_DOUBLECLICK, /* double click complete */
+ KEYC_MOUSE_KEY(MOUSEMOVE),
+ KEYC_MOUSE_KEY(MOUSEDOWN1),
+ KEYC_MOUSE_KEY(MOUSEDOWN2),
+ KEYC_MOUSE_KEY(MOUSEDOWN3),
+ KEYC_MOUSE_KEY(MOUSEDOWN6),
+ KEYC_MOUSE_KEY(MOUSEDOWN7),
+ KEYC_MOUSE_KEY(MOUSEDOWN8),
+ KEYC_MOUSE_KEY(MOUSEDOWN9),
+ KEYC_MOUSE_KEY(MOUSEDOWN10),
+ KEYC_MOUSE_KEY(MOUSEDOWN11),
+ KEYC_MOUSE_KEY(MOUSEUP1),
+ KEYC_MOUSE_KEY(MOUSEUP2),
+ KEYC_MOUSE_KEY(MOUSEUP3),
+ KEYC_MOUSE_KEY(MOUSEUP6),
+ KEYC_MOUSE_KEY(MOUSEUP7),
+ KEYC_MOUSE_KEY(MOUSEUP8),
+ KEYC_MOUSE_KEY(MOUSEUP9),
+ KEYC_MOUSE_KEY(MOUSEUP10),
+ KEYC_MOUSE_KEY(MOUSEUP11),
+ KEYC_MOUSE_KEY(MOUSEDRAG1),
+ KEYC_MOUSE_KEY(MOUSEDRAG2),
+ KEYC_MOUSE_KEY(MOUSEDRAG3),
+ KEYC_MOUSE_KEY(MOUSEDRAG6),
+ KEYC_MOUSE_KEY(MOUSEDRAG7),
+ KEYC_MOUSE_KEY(MOUSEDRAG8),
+ KEYC_MOUSE_KEY(MOUSEDRAG9),
+ KEYC_MOUSE_KEY(MOUSEDRAG10),
+ KEYC_MOUSE_KEY(MOUSEDRAG11),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND1),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND2),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND3),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND6),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND7),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND8),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND9),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND10),
+ KEYC_MOUSE_KEY(MOUSEDRAGEND11),
+ KEYC_MOUSE_KEY(WHEELUP),
+ KEYC_MOUSE_KEY(WHEELDOWN),
+ KEYC_MOUSE_KEY(SECONDCLICK1),
+ KEYC_MOUSE_KEY(SECONDCLICK2),
+ KEYC_MOUSE_KEY(SECONDCLICK3),
+ KEYC_MOUSE_KEY(SECONDCLICK6),
+ KEYC_MOUSE_KEY(SECONDCLICK7),
+ KEYC_MOUSE_KEY(SECONDCLICK8),
+ KEYC_MOUSE_KEY(SECONDCLICK9),
+ KEYC_MOUSE_KEY(SECONDCLICK10),
+ KEYC_MOUSE_KEY(SECONDCLICK11),
+ KEYC_MOUSE_KEY(DOUBLECLICK1),
+ KEYC_MOUSE_KEY(DOUBLECLICK2),
+ KEYC_MOUSE_KEY(DOUBLECLICK3),
+ KEYC_MOUSE_KEY(DOUBLECLICK6),
+ KEYC_MOUSE_KEY(DOUBLECLICK7),
+ KEYC_MOUSE_KEY(DOUBLECLICK8),
+ KEYC_MOUSE_KEY(DOUBLECLICK9),
+ KEYC_MOUSE_KEY(DOUBLECLICK10),
+ KEYC_MOUSE_KEY(DOUBLECLICK11),
+ KEYC_MOUSE_KEY(TRIPLECLICK1),
+ KEYC_MOUSE_KEY(TRIPLECLICK2),
+ KEYC_MOUSE_KEY(TRIPLECLICK3),
+ KEYC_MOUSE_KEY(TRIPLECLICK6),
+ KEYC_MOUSE_KEY(TRIPLECLICK7),
+ KEYC_MOUSE_KEY(TRIPLECLICK8),
+ KEYC_MOUSE_KEY(TRIPLECLICK9),
+ KEYC_MOUSE_KEY(TRIPLECLICK10),
+ KEYC_MOUSE_KEY(TRIPLECLICK11),
+
+ /* Backspace key. */
+ KEYC_BSPACE,
+
+ /* Function keys. */
+ KEYC_F1,
+ KEYC_F2,
+ KEYC_F3,
+ KEYC_F4,
+ KEYC_F5,
+ KEYC_F6,
+ KEYC_F7,
+ KEYC_F8,
+ KEYC_F9,
+ KEYC_F10,
+ KEYC_F11,
+ KEYC_F12,
+ KEYC_IC,
+ KEYC_DC,
+ KEYC_HOME,
+ KEYC_END,
+ KEYC_NPAGE,
+ KEYC_PPAGE,
+ KEYC_BTAB,
+
+ /* Arrow keys. */
+ KEYC_UP,
+ KEYC_DOWN,
+ KEYC_LEFT,
+ KEYC_RIGHT,
+
+ /* Numeric keypad. */
+ KEYC_KP_SLASH,
+ KEYC_KP_STAR,
+ KEYC_KP_MINUS,
+ KEYC_KP_SEVEN,
+ KEYC_KP_EIGHT,
+ KEYC_KP_NINE,
+ KEYC_KP_PLUS,
+ KEYC_KP_FOUR,
+ KEYC_KP_FIVE,
+ KEYC_KP_SIX,
+ KEYC_KP_ONE,
+ KEYC_KP_TWO,
+ KEYC_KP_THREE,
+ KEYC_KP_ENTER,
+ KEYC_KP_ZERO,
+ KEYC_KP_PERIOD,
+
+ /* End of special keys. */
+ KEYC_BASE_END
+};
+
+/* Termcap codes. */
+enum tty_code_code {
+ TTYC_ACSC,
+ TTYC_AM,
+ TTYC_AX,
+ TTYC_BCE,
+ TTYC_BEL,
+ TTYC_BIDI,
+ TTYC_BLINK,
+ TTYC_BOLD,
+ TTYC_CIVIS,
+ TTYC_CLEAR,
+ TTYC_CLMG,
+ TTYC_CMG,
+ TTYC_CNORM,
+ TTYC_COLORS,
+ TTYC_CR,
+ TTYC_CS,
+ TTYC_CSR,
+ TTYC_CUB,
+ TTYC_CUB1,
+ TTYC_CUD,
+ TTYC_CUD1,
+ TTYC_CUF,
+ TTYC_CUF1,
+ TTYC_CUP,
+ TTYC_CUU,
+ TTYC_CUU1,
+ TTYC_CVVIS,
+ TTYC_DCH,
+ TTYC_DCH1,
+ TTYC_DIM,
+ TTYC_DL,
+ TTYC_DL1,
+ TTYC_DSBP,
+ TTYC_DSEKS,
+ TTYC_DSFCS,
+ TTYC_DSMG,
+ TTYC_E3,
+ TTYC_ECH,
+ TTYC_ED,
+ TTYC_EL,
+ TTYC_EL1,
+ TTYC_ENACS,
+ TTYC_ENBP,
+ TTYC_ENEKS,
+ TTYC_ENFCS,
+ TTYC_ENMG,
+ TTYC_FSL,
+ TTYC_HOME,
+ TTYC_HPA,
+ TTYC_ICH,
+ TTYC_ICH1,
+ TTYC_IL,
+ TTYC_IL1,
+ TTYC_INDN,
+ TTYC_INVIS,
+ TTYC_KCBT,
+ TTYC_KCUB1,
+ TTYC_KCUD1,
+ TTYC_KCUF1,
+ TTYC_KCUU1,
+ TTYC_KDC2,
+ TTYC_KDC3,
+ TTYC_KDC4,
+ TTYC_KDC5,
+ TTYC_KDC6,
+ TTYC_KDC7,
+ TTYC_KDCH1,
+ TTYC_KDN2,
+ TTYC_KDN3,
+ TTYC_KDN4,
+ TTYC_KDN5,
+ TTYC_KDN6,
+ TTYC_KDN7,
+ TTYC_KEND,
+ TTYC_KEND2,
+ TTYC_KEND3,
+ TTYC_KEND4,
+ TTYC_KEND5,
+ TTYC_KEND6,
+ TTYC_KEND7,
+ TTYC_KF1,
+ TTYC_KF10,
+ TTYC_KF11,
+ TTYC_KF12,
+ TTYC_KF13,
+ TTYC_KF14,
+ TTYC_KF15,
+ TTYC_KF16,
+ TTYC_KF17,
+ TTYC_KF18,
+ TTYC_KF19,
+ TTYC_KF2,
+ TTYC_KF20,
+ TTYC_KF21,
+ TTYC_KF22,
+ TTYC_KF23,
+ TTYC_KF24,
+ TTYC_KF25,
+ TTYC_KF26,
+ TTYC_KF27,
+ TTYC_KF28,
+ TTYC_KF29,
+ TTYC_KF3,
+ TTYC_KF30,
+ TTYC_KF31,
+ TTYC_KF32,
+ TTYC_KF33,
+ TTYC_KF34,
+ TTYC_KF35,
+ TTYC_KF36,
+ TTYC_KF37,
+ TTYC_KF38,
+ TTYC_KF39,
+ TTYC_KF4,
+ TTYC_KF40,
+ TTYC_KF41,
+ TTYC_KF42,
+ TTYC_KF43,
+ TTYC_KF44,
+ TTYC_KF45,
+ TTYC_KF46,
+ TTYC_KF47,
+ TTYC_KF48,
+ TTYC_KF49,
+ TTYC_KF5,
+ TTYC_KF50,
+ TTYC_KF51,
+ TTYC_KF52,
+ TTYC_KF53,
+ TTYC_KF54,
+ TTYC_KF55,
+ TTYC_KF56,
+ TTYC_KF57,
+ TTYC_KF58,
+ TTYC_KF59,
+ TTYC_KF6,
+ TTYC_KF60,
+ TTYC_KF61,
+ TTYC_KF62,
+ TTYC_KF63,
+ TTYC_KF7,
+ TTYC_KF8,
+ TTYC_KF9,
+ TTYC_KHOM2,
+ TTYC_KHOM3,
+ TTYC_KHOM4,
+ TTYC_KHOM5,
+ TTYC_KHOM6,
+ TTYC_KHOM7,
+ TTYC_KHOME,
+ TTYC_KIC2,
+ TTYC_KIC3,
+ TTYC_KIC4,
+ TTYC_KIC5,
+ TTYC_KIC6,
+ TTYC_KIC7,
+ TTYC_KICH1,
+ TTYC_KIND,
+ TTYC_KLFT2,
+ TTYC_KLFT3,
+ TTYC_KLFT4,
+ TTYC_KLFT5,
+ TTYC_KLFT6,
+ TTYC_KLFT7,
+ TTYC_KMOUS,
+ TTYC_KNP,
+ TTYC_KNXT2,
+ TTYC_KNXT3,
+ TTYC_KNXT4,
+ TTYC_KNXT5,
+ TTYC_KNXT6,
+ TTYC_KNXT7,
+ TTYC_KPP,
+ TTYC_KPRV2,
+ TTYC_KPRV3,
+ TTYC_KPRV4,
+ TTYC_KPRV5,
+ TTYC_KPRV6,
+ TTYC_KPRV7,
+ TTYC_KRI,
+ TTYC_KRIT2,
+ TTYC_KRIT3,
+ TTYC_KRIT4,
+ TTYC_KRIT5,
+ TTYC_KRIT6,
+ TTYC_KRIT7,
+ TTYC_KUP2,
+ TTYC_KUP3,
+ TTYC_KUP4,
+ TTYC_KUP5,
+ TTYC_KUP6,
+ TTYC_KUP7,
+ TTYC_MS,
+ TTYC_OL,
+ TTYC_OP,
+ TTYC_RECT,
+ TTYC_REV,
+ TTYC_RGB,
+ TTYC_RI,
+ TTYC_RIN,
+ TTYC_RMACS,
+ TTYC_RMCUP,
+ TTYC_RMKX,
+ TTYC_SE,
+ TTYC_SETAB,
+ TTYC_SETAF,
+ TTYC_SETAL,
+ TTYC_SETRGBB,
+ TTYC_SETRGBF,
+ TTYC_SETULC,
+ TTYC_SGR0,
+ TTYC_SITM,
+ TTYC_SMACS,
+ TTYC_SMCUP,
+ TTYC_SMKX,
+ TTYC_SMOL,
+ TTYC_SMSO,
+ TTYC_SMUL,
+ TTYC_SMULX,
+ TTYC_SMXX,
+ TTYC_SS,
+ TTYC_SWD,
+ TTYC_SYNC,
+ TTYC_TC,
+ TTYC_TSL,
+ TTYC_U8,
+ TTYC_VPA,
+ TTYC_XT
+};
+
+/* Character classes. */
+#define WHITESPACE " "
+
+/* Mode keys. */
+#define MODEKEY_EMACS 0
+#define MODEKEY_VI 1
+
+/* Modes. */
+#define MODE_CURSOR 0x1
+#define MODE_INSERT 0x2
+#define MODE_KCURSOR 0x4
+#define MODE_KKEYPAD 0x8
+#define MODE_WRAP 0x10
+#define MODE_MOUSE_STANDARD 0x20
+#define MODE_MOUSE_BUTTON 0x40
+#define MODE_CURSOR_BLINKING 0x80
+#define MODE_MOUSE_UTF8 0x100
+#define MODE_MOUSE_SGR 0x200
+#define MODE_BRACKETPASTE 0x400
+#define MODE_FOCUSON 0x800
+#define MODE_MOUSE_ALL 0x1000
+#define MODE_ORIGIN 0x2000
+#define MODE_CRLF 0x4000
+#define MODE_KEXTENDED 0x8000
+#define MODE_CURSOR_VERY_VISIBLE 0x10000
+#define MODE_CURSOR_BLINKING_SET 0x20000
+
+#define ALL_MODES 0xffffff
+#define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL)
+#define MOTION_MOUSE_MODES (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL)
+#define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)
+
+/* Mouse protocol constants. */
+#define MOUSE_PARAM_MAX 0xff
+#define MOUSE_PARAM_UTF8_MAX 0x7ff
+#define MOUSE_PARAM_BTN_OFF 0x20
+#define MOUSE_PARAM_POS_OFF 0x21
+
+/* A single UTF-8 character. */
+typedef u_int utf8_char;
+
+/*
+ * An expanded UTF-8 character. UTF8_SIZE must be big enough to hold combining
+ * characters as well. It can't be more than 32 bytes without changes to how
+ * characters are stored.
+ */
+#define UTF8_SIZE 21
+struct utf8_data {
+ u_char data[UTF8_SIZE];
+
+ u_char have;
+ u_char size;
+
+ u_char width; /* 0xff if invalid */
+};
+enum utf8_state {
+ UTF8_MORE,
+ UTF8_DONE,
+ UTF8_ERROR
+};
+
+/* Colour flags. */
+#define COLOUR_FLAG_256 0x01000000
+#define COLOUR_FLAG_RGB 0x02000000
+
+/* Special colours. */
+#define COLOUR_DEFAULT(c) ((c) == 8 || (c) == 9)
+
+/* Replacement palette. */
+struct colour_palette {
+ int fg;
+ int bg;
+
+ int *palette;
+ int *default_palette;
+};
+
+/* Grid attributes. Anything above 0xff is stored in an extended cell. */
+#define GRID_ATTR_BRIGHT 0x1
+#define GRID_ATTR_DIM 0x2
+#define GRID_ATTR_UNDERSCORE 0x4
+#define GRID_ATTR_BLINK 0x8
+#define GRID_ATTR_REVERSE 0x10
+#define GRID_ATTR_HIDDEN 0x20
+#define GRID_ATTR_ITALICS 0x40
+#define GRID_ATTR_CHARSET 0x80 /* alternative character set */
+#define GRID_ATTR_STRIKETHROUGH 0x100
+#define GRID_ATTR_UNDERSCORE_2 0x200
+#define GRID_ATTR_UNDERSCORE_3 0x400
+#define GRID_ATTR_UNDERSCORE_4 0x800
+#define GRID_ATTR_UNDERSCORE_5 0x1000
+#define GRID_ATTR_OVERLINE 0x2000
+
+/* All underscore attributes. */
+#define GRID_ATTR_ALL_UNDERSCORE \
+ (GRID_ATTR_UNDERSCORE| \
+ GRID_ATTR_UNDERSCORE_2| \
+ GRID_ATTR_UNDERSCORE_3| \
+ GRID_ATTR_UNDERSCORE_4| \
+ GRID_ATTR_UNDERSCORE_5)
+
+/* Grid flags. */
+#define GRID_FLAG_FG256 0x1
+#define GRID_FLAG_BG256 0x2
+#define GRID_FLAG_PADDING 0x4
+#define GRID_FLAG_EXTENDED 0x8
+#define GRID_FLAG_SELECTED 0x10
+#define GRID_FLAG_NOPALETTE 0x20
+#define GRID_FLAG_CLEARED 0x40
+
+/* Grid line flags. */
+#define GRID_LINE_WRAPPED 0x1
+#define GRID_LINE_EXTENDED 0x2
+#define GRID_LINE_DEAD 0x4
+
+#define CELL_INSIDE 0
+#define CELL_TOPBOTTOM 1
+#define CELL_LEFTRIGHT 2
+#define CELL_TOPLEFT 3
+#define CELL_TOPRIGHT 4
+#define CELL_BOTTOMLEFT 5
+#define CELL_BOTTOMRIGHT 6
+#define CELL_TOPJOIN 7
+#define CELL_BOTTOMJOIN 8
+#define CELL_LEFTJOIN 9
+#define CELL_RIGHTJOIN 10
+#define CELL_JOIN 11
+#define CELL_OUTSIDE 12
+
+#define CELL_BORDERS " xqlkmjwvtun~"
+#define SIMPLE_BORDERS " |-+++++++++."
+#define PADDED_BORDERS " "
+
+/* Grid cell data. */
+struct grid_cell {
+ struct utf8_data data;
+ u_short attr;
+ u_char flags;
+ int fg;
+ int bg;
+ int us;
+};
+
+/* Grid extended cell entry. */
+struct grid_extd_entry {
+ utf8_char data;
+ u_short attr;
+ u_char flags;
+ int fg;
+ int bg;
+ int us;
+} __packed;
+
+/* Grid cell entry. */
+struct grid_cell_entry {
+ u_char flags;
+ union {
+ u_int offset;
+ struct {
+ u_char attr;
+ u_char fg;
+ u_char bg;
+ u_char data;
+ } data;
+ };
+} __packed;
+
+/* Grid line. */
+struct grid_line {
+ struct grid_cell_entry *celldata;
+ u_int cellused;
+ u_int cellsize;
+
+ struct grid_extd_entry *extddata;
+ u_int extdsize;
+
+ int flags;
+};
+
+/* Entire grid of cells. */
+struct grid {
+ int flags;
+#define GRID_HISTORY 0x1 /* scroll lines into history */
+
+ u_int sx;
+ u_int sy;
+
+ u_int hscrolled;
+ u_int hsize;
+ u_int hlimit;
+
+ struct grid_line *linedata;
+};
+
+/* Virtual cursor in a grid. */
+struct grid_reader {
+ struct grid *gd;
+ u_int cx;
+ u_int cy;
+};
+
+/* Style alignment. */
+enum style_align {
+ STYLE_ALIGN_DEFAULT,
+ STYLE_ALIGN_LEFT,
+ STYLE_ALIGN_CENTRE,
+ STYLE_ALIGN_RIGHT,
+ STYLE_ALIGN_ABSOLUTE_CENTRE
+};
+
+/* Style list. */
+enum style_list {
+ STYLE_LIST_OFF,
+ STYLE_LIST_ON,
+ STYLE_LIST_FOCUS,
+ STYLE_LIST_LEFT_MARKER,
+ STYLE_LIST_RIGHT_MARKER,
+};
+
+/* Style range. */
+enum style_range_type {
+ STYLE_RANGE_NONE,
+ STYLE_RANGE_LEFT,
+ STYLE_RANGE_RIGHT,
+ STYLE_RANGE_WINDOW
+};
+struct style_range {
+ enum style_range_type type;
+ u_int argument;
+
+ u_int start;
+ u_int end; /* not included */
+
+ TAILQ_ENTRY(style_range) entry;
+};
+TAILQ_HEAD(style_ranges, style_range);
+
+/* Style default. */
+enum style_default_type {
+ STYLE_DEFAULT_BASE,
+ STYLE_DEFAULT_PUSH,
+ STYLE_DEFAULT_POP
+};
+
+/* Style option. */
+struct style {
+ struct grid_cell gc;
+ int ignore;
+
+ int fill;
+ enum style_align align;
+ enum style_list list;
+
+ enum style_range_type range_type;
+ u_int range_argument;
+
+ enum style_default_type default_type;
+};
+
+/* Cursor style. */
+enum screen_cursor_style {
+ SCREEN_CURSOR_DEFAULT,
+ SCREEN_CURSOR_BLOCK,
+ SCREEN_CURSOR_UNDERLINE,
+ SCREEN_CURSOR_BAR
+};
+
+/* Virtual screen. */
+struct screen_sel;
+struct screen_titles;
+struct screen {
+ char *title;
+ char *path;
+ struct screen_titles *titles;
+
+ struct grid *grid; /* grid data */
+
+ u_int cx; /* cursor x */
+ u_int cy; /* cursor y */
+
+ enum screen_cursor_style cstyle; /* cursor style */
+ enum screen_cursor_style default_cstyle;
+ int ccolour; /* cursor colour */
+ int default_ccolour;
+
+ u_int rupper; /* scroll region top */
+ u_int rlower; /* scroll region bottom */
+
+ int mode;
+ int default_mode;
+
+ u_int saved_cx;
+ u_int saved_cy;
+ struct grid *saved_grid;
+ struct grid_cell saved_cell;
+ int saved_flags;
+
+ bitstr_t *tabs;
+ struct screen_sel *sel;
+
+ struct screen_write_cline *write_list;
+};
+
+/* Screen write context. */
+typedef void (*screen_write_init_ctx_cb)(struct screen_write_ctx *,
+ struct tty_ctx *);
+struct screen_write_ctx {
+ struct window_pane *wp;
+ struct screen *s;
+
+ int flags;
+#define SCREEN_WRITE_SYNC 0x1
+#define SCREEN_WRITE_ZWJ 0x2
+
+ screen_write_init_ctx_cb init_ctx_cb;
+ void *arg;
+
+ struct screen_write_citem *item;
+ u_int scrolled;
+ u_int bg;
+};
+
+/* Box border lines option. */
+enum box_lines {
+ BOX_LINES_DEFAULT = -1,
+ BOX_LINES_SINGLE,
+ BOX_LINES_DOUBLE,
+ BOX_LINES_HEAVY,
+ BOX_LINES_SIMPLE,
+ BOX_LINES_ROUNDED,
+ BOX_LINES_PADDED,
+ BOX_LINES_NONE
+};
+
+/* Pane border lines option. */
+enum pane_lines {
+ PANE_LINES_SINGLE,
+ PANE_LINES_DOUBLE,
+ PANE_LINES_HEAVY,
+ PANE_LINES_SIMPLE,
+ PANE_LINES_NUMBER
+};
+
+/* Pane border indicator option. */
+#define PANE_BORDER_OFF 0
+#define PANE_BORDER_COLOUR 1
+#define PANE_BORDER_ARROWS 2
+#define PANE_BORDER_BOTH 3
+
+/* Screen redraw context. */
+struct screen_redraw_ctx {
+ struct client *c;
+
+ u_int statuslines;
+ int statustop;
+
+ int pane_status;
+ enum pane_lines pane_lines;
+
+ struct grid_cell no_pane_gc;
+ int no_pane_gc_set;
+
+ u_int sx;
+ u_int sy;
+ u_int ox;
+ u_int oy;
+};
+
+/* Screen size. */
+#define screen_size_x(s) ((s)->grid->sx)
+#define screen_size_y(s) ((s)->grid->sy)
+#define screen_hsize(s) ((s)->grid->hsize)
+#define screen_hlimit(s) ((s)->grid->hlimit)
+
+/* Menu. */
+struct menu_item {
+ const char *name;
+ key_code key;
+ const char *command;
+};
+struct menu {
+ const char *title;
+ struct menu_item *items;
+ u_int count;
+ u_int width;
+};
+typedef void (*menu_choice_cb)(struct menu *, u_int, key_code, void *);
+
+/*
+ * Window mode. Windows can be in several modes and this is used to call the
+ * right function to handle input and output.
+ */
+struct window_mode_entry;
+struct window_mode {
+ const char *name;
+ const char *default_format;
+
+ struct screen *(*init)(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+ void (*free)(struct window_mode_entry *);
+ void (*resize)(struct window_mode_entry *, u_int, u_int);
+ void (*update)(struct window_mode_entry *);
+ void (*key)(struct window_mode_entry *, struct client *,
+ struct session *, struct winlink *, key_code,
+ struct mouse_event *);
+
+ const char *(*key_table)(struct window_mode_entry *);
+ void (*command)(struct window_mode_entry *, struct client *,
+ struct session *, struct winlink *, struct args *,
+ struct mouse_event *);
+ void (*formats)(struct window_mode_entry *,
+ struct format_tree *);
+};
+
+/* Active window mode. */
+struct window_mode_entry {
+ struct window_pane *wp;
+ struct window_pane *swp;
+
+ const struct window_mode *mode;
+ void *data;
+
+ struct screen *screen;
+ u_int prefix;
+
+ TAILQ_ENTRY(window_mode_entry) entry;
+};
+
+/* Offsets into pane buffer. */
+struct window_pane_offset {
+ size_t used;
+};
+
+/* Queued pane resize. */
+struct window_pane_resize {
+ u_int sx;
+ u_int sy;
+
+ u_int osx;
+ u_int osy;
+
+ TAILQ_ENTRY(window_pane_resize) entry;
+};
+TAILQ_HEAD(window_pane_resizes, window_pane_resize);
+
+/* Child window structure. */
+struct window_pane {
+ u_int id;
+ u_int active_point;
+
+ struct window *window;
+ struct options *options;
+
+ struct layout_cell *layout_cell;
+ struct layout_cell *saved_layout_cell;
+
+ u_int sx;
+ u_int sy;
+
+ u_int xoff;
+ u_int yoff;
+
+ int flags;
+#define PANE_REDRAW 0x1
+#define PANE_DROP 0x2
+#define PANE_FOCUSED 0x4
+/* 0x8 unused */
+/* 0x10 unused */
+/* 0x20 unused */
+#define PANE_INPUTOFF 0x40
+#define PANE_CHANGED 0x80
+#define PANE_EXITED 0x100
+#define PANE_STATUSREADY 0x200
+#define PANE_STATUSDRAWN 0x400
+#define PANE_EMPTY 0x800
+#define PANE_STYLECHANGED 0x1000
+
+ int argc;
+ char **argv;
+ char *shell;
+ char *cwd;
+
+ pid_t pid;
+ char tty[TTY_NAME_MAX];
+ int status;
+ struct timeval dead_time;
+
+ int fd;
+ struct bufferevent *event;
+
+ struct window_pane_offset offset;
+ size_t base_offset;
+
+ struct window_pane_resizes resize_queue;
+ struct event resize_timer;
+
+ struct input_ctx *ictx;
+
+ struct grid_cell cached_gc;
+ struct grid_cell cached_active_gc;
+ struct colour_palette palette;
+
+ int pipe_fd;
+ struct bufferevent *pipe_event;
+ struct window_pane_offset pipe_offset;
+
+ struct screen *screen;
+ struct screen base;
+
+ struct screen status_screen;
+ size_t status_size;
+
+ TAILQ_HEAD(, window_mode_entry) modes;
+
+ char *searchstr;
+ int searchregex;
+
+ int border_gc_set;
+ struct grid_cell border_gc;
+
+ TAILQ_ENTRY(window_pane) entry;
+ RB_ENTRY(window_pane) tree_entry;
+};
+TAILQ_HEAD(window_panes, window_pane);
+RB_HEAD(window_pane_tree, window_pane);
+
+/* Window structure. */
+struct window {
+ u_int id;
+ void *latest;
+
+ char *name;
+ struct event name_event;
+ struct timeval name_time;
+
+ struct event alerts_timer;
+ struct event offset_timer;
+
+ struct timeval activity_time;
+
+ struct window_pane *active;
+ struct window_pane *last;
+ struct window_panes panes;
+
+ int lastlayout;
+ struct layout_cell *layout_root;
+ struct layout_cell *saved_layout_root;
+ char *old_layout;
+
+ u_int sx;
+ u_int sy;
+ u_int manual_sx;
+ u_int manual_sy;
+ u_int xpixel;
+ u_int ypixel;
+
+ u_int new_sx;
+ u_int new_sy;
+ u_int new_xpixel;
+ u_int new_ypixel;
+
+ struct utf8_data *fill_character;
+ int flags;
+#define WINDOW_BELL 0x1
+#define WINDOW_ACTIVITY 0x2
+#define WINDOW_SILENCE 0x4
+#define WINDOW_ZOOMED 0x8
+#define WINDOW_WASZOOMED 0x10
+#define WINDOW_RESIZE 0x20
+#define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE)
+
+ int alerts_queued;
+ TAILQ_ENTRY(window) alerts_entry;
+
+ struct options *options;
+
+ u_int references;
+ TAILQ_HEAD(, winlink) winlinks;
+
+ RB_ENTRY(window) entry;
+};
+RB_HEAD(windows, window);
+
+/* Entry on local window list. */
+struct winlink {
+ int idx;
+ struct session *session;
+ struct window *window;
+
+ int flags;
+#define WINLINK_BELL 0x1
+#define WINLINK_ACTIVITY 0x2
+#define WINLINK_SILENCE 0x4
+#define WINLINK_ALERTFLAGS (WINLINK_BELL|WINLINK_ACTIVITY|WINLINK_SILENCE)
+
+ RB_ENTRY(winlink) entry;
+ TAILQ_ENTRY(winlink) wentry;
+ TAILQ_ENTRY(winlink) sentry;
+};
+RB_HEAD(winlinks, winlink);
+TAILQ_HEAD(winlink_stack, winlink);
+
+/* Window size option. */
+#define WINDOW_SIZE_LARGEST 0
+#define WINDOW_SIZE_SMALLEST 1
+#define WINDOW_SIZE_MANUAL 2
+#define WINDOW_SIZE_LATEST 3
+
+/* Pane border status option. */
+#define PANE_STATUS_OFF 0
+#define PANE_STATUS_TOP 1
+#define PANE_STATUS_BOTTOM 2
+
+/* Layout direction. */
+enum layout_type {
+ LAYOUT_LEFTRIGHT,
+ LAYOUT_TOPBOTTOM,
+ LAYOUT_WINDOWPANE
+};
+
+/* Layout cells queue. */
+TAILQ_HEAD(layout_cells, layout_cell);
+
+/* Layout cell. */
+struct layout_cell {
+ enum layout_type type;
+
+ struct layout_cell *parent;
+
+ u_int sx;
+ u_int sy;
+
+ u_int xoff;
+ u_int yoff;
+
+ struct window_pane *wp;
+ struct layout_cells cells;
+
+ TAILQ_ENTRY(layout_cell) entry;
+};
+
+/* Environment variable. */
+struct environ_entry {
+ char *name;
+ char *value;
+
+ int flags;
+#define ENVIRON_HIDDEN 0x1
+
+ RB_ENTRY(environ_entry) entry;
+};
+
+/* Client session. */
+struct session_group {
+ const char *name;
+ TAILQ_HEAD(, session) sessions;
+
+ RB_ENTRY(session_group) entry;
+};
+RB_HEAD(session_groups, session_group);
+
+struct session {
+ u_int id;
+
+ char *name;
+ const char *cwd;
+
+ struct timeval creation_time;
+ struct timeval last_attached_time;
+ struct timeval activity_time;
+ struct timeval last_activity_time;
+
+ struct event lock_timer;
+
+ struct winlink *curw;
+ struct winlink_stack lastw;
+ struct winlinks windows;
+
+ int statusat;
+ u_int statuslines;
+
+ struct options *options;
+
+#define SESSION_PASTING 0x1
+#define SESSION_ALERTED 0x2
+ int flags;
+
+ u_int attached;
+
+ struct termios *tio;
+
+ struct environ *environ;
+
+ int references;
+
+ TAILQ_ENTRY(session) gentry;
+ RB_ENTRY(session) entry;
+};
+RB_HEAD(sessions, session);
+
+/* Mouse button masks. */
+#define MOUSE_MASK_BUTTONS 195
+#define MOUSE_MASK_SHIFT 4
+#define MOUSE_MASK_META 8
+#define MOUSE_MASK_CTRL 16
+#define MOUSE_MASK_DRAG 32
+#define MOUSE_MASK_MODIFIERS (MOUSE_MASK_SHIFT|MOUSE_MASK_META|MOUSE_MASK_CTRL)
+
+/* Mouse wheel type. */
+#define MOUSE_WHEEL_UP 64
+#define MOUSE_WHEEL_DOWN 65
+
+/* Mouse button type. */
+#define MOUSE_BUTTON_1 0
+#define MOUSE_BUTTON_2 1
+#define MOUSE_BUTTON_3 2
+#define MOUSE_BUTTON_6 66
+#define MOUSE_BUTTON_7 67
+#define MOUSE_BUTTON_8 128
+#define MOUSE_BUTTON_9 129
+#define MOUSE_BUTTON_10 130
+#define MOUSE_BUTTON_11 131
+
+/* Mouse helpers. */
+#define MOUSE_BUTTONS(b) ((b) & MOUSE_MASK_BUTTONS)
+#define MOUSE_WHEEL(b) \
+ (((b) & MOUSE_MASK_BUTTONS) == MOUSE_WHEEL_UP || \
+ ((b) & MOUSE_MASK_BUTTONS) == MOUSE_WHEEL_DOWN)
+#define MOUSE_DRAG(b) ((b) & MOUSE_MASK_DRAG)
+#define MOUSE_RELEASE(b) (((b) & MOUSE_MASK_BUTTONS) == 3)
+
+/* Mouse input. */
+struct mouse_event {
+ int valid;
+ int ignore;
+
+ key_code key;
+
+ int statusat;
+ u_int statuslines;
+
+ u_int x;
+ u_int y;
+ u_int b;
+
+ u_int lx;
+ u_int ly;
+ u_int lb;
+
+ u_int ox;
+ u_int oy;
+
+ int s;
+ int w;
+ int wp;
+
+ u_int sgr_type;
+ u_int sgr_b;
+};
+
+/* Key event. */
+struct key_event {
+ key_code key;
+ struct mouse_event m;
+};
+
+/* Terminal definition. */
+struct tty_term {
+ char *name;
+ struct tty *tty;
+ int features;
+
+ char acs[UCHAR_MAX + 1][2];
+
+ struct tty_code *codes;
+
+#define TERM_256COLOURS 0x1
+#define TERM_NOAM 0x2
+#define TERM_DECSLRM 0x4
+#define TERM_DECFRA 0x8
+#define TERM_RGBCOLOURS 0x10
+#define TERM_VT100LIKE 0x20
+ int flags;
+
+ LIST_ENTRY(tty_term) entry;
+};
+LIST_HEAD(tty_terms, tty_term);
+
+/* Client terminal. */
+struct tty {
+ struct client *client;
+ struct event start_timer;
+ struct event clipboard_timer;
+
+ u_int sx;
+ u_int sy;
+ u_int xpixel;
+ u_int ypixel;
+
+ u_int cx;
+ u_int cy;
+ enum screen_cursor_style cstyle;
+ int ccolour;
+
+ int oflag;
+ u_int oox;
+ u_int ooy;
+ u_int osx;
+ u_int osy;
+
+ int mode;
+
+ u_int rlower;
+ u_int rupper;
+
+ u_int rleft;
+ u_int rright;
+
+ struct event event_in;
+ struct evbuffer *in;
+ struct event event_out;
+ struct evbuffer *out;
+ struct event timer;
+ size_t discarded;
+
+ struct termios tio;
+
+ struct grid_cell cell;
+ struct grid_cell last_cell;
+
+#define TTY_NOCURSOR 0x1
+#define TTY_FREEZE 0x2
+#define TTY_TIMER 0x4
+#define TTY_NOBLOCK 0x8
+#define TTY_STARTED 0x10
+#define TTY_OPENED 0x20
+#define TTY_OSC52QUERY 0x40
+#define TTY_BLOCK 0x80
+#define TTY_HAVEDA 0x100
+#define TTY_HAVEXDA 0x200
+#define TTY_SYNCING 0x400
+ int flags;
+
+ struct tty_term *term;
+
+ u_int mouse_last_x;
+ u_int mouse_last_y;
+ u_int mouse_last_b;
+ int mouse_drag_flag;
+ void (*mouse_drag_update)(struct client *,
+ struct mouse_event *);
+ void (*mouse_drag_release)(struct client *,
+ struct mouse_event *);
+
+ struct event key_timer;
+ struct tty_key *key_tree;
+};
+
+/* Terminal command context. */
+typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *);
+typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *);
+struct tty_ctx {
+ struct screen *s;
+
+ tty_ctx_redraw_cb redraw_cb;
+ tty_ctx_set_client_cb set_client_cb;
+ void *arg;
+
+ const struct grid_cell *cell;
+ int wrapped;
+
+ u_int num;
+ void *ptr;
+
+ /*
+ * 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 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;
+
+ /* The background colour used for clearing (erasing). */
+ u_int bg;
+
+ /* The default colours and 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;
+};
+
+/* Saved message entry. */
+struct message_entry {
+ char *msg;
+ u_int msg_num;
+ struct timeval msg_time;
+
+ TAILQ_ENTRY(message_entry) entry;
+};
+TAILQ_HEAD(message_list, message_entry);
+
+/* Argument type. */
+enum args_type {
+ ARGS_NONE,
+ ARGS_STRING,
+ ARGS_COMMANDS
+};
+
+/* Argument value. */
+struct args_value {
+ enum args_type type;
+ union {
+ char *string;
+ struct cmd_list *cmdlist;
+ };
+ char *cached;
+ TAILQ_ENTRY(args_value) entry;
+};
+
+/* Arguments set. */
+struct args_entry;
+RB_HEAD(args_tree, args_entry);
+
+/* Arguments parsing type. */
+enum args_parse_type {
+ ARGS_PARSE_INVALID,
+ ARGS_PARSE_STRING,
+ ARGS_PARSE_COMMANDS_OR_STRING,
+ ARGS_PARSE_COMMANDS
+};
+
+/* Arguments parsing state. */
+typedef enum args_parse_type (*args_parse_cb)(struct args *, u_int, char **);
+struct args_parse {
+ const char *template;
+ int lower;
+ int upper;
+ args_parse_cb cb;
+};
+
+/* Command find structures. */
+enum cmd_find_type {
+ CMD_FIND_PANE,
+ CMD_FIND_WINDOW,
+ CMD_FIND_SESSION,
+};
+struct cmd_find_state {
+ int flags;
+ struct cmd_find_state *current;
+
+ struct session *s;
+ struct winlink *wl;
+ struct window *w;
+ struct window_pane *wp;
+ int idx;
+};
+
+/* Command find flags. */
+#define CMD_FIND_PREFER_UNATTACHED 0x1
+#define CMD_FIND_QUIET 0x2
+#define CMD_FIND_WINDOW_INDEX 0x4
+#define CMD_FIND_DEFAULT_MARKED 0x8
+#define CMD_FIND_EXACT_SESSION 0x10
+#define CMD_FIND_EXACT_WINDOW 0x20
+#define CMD_FIND_CANFAIL 0x40
+
+/* List of commands. */
+struct cmd_list {
+ int references;
+ u_int group;
+ struct cmds *list;
+};
+
+/* Command return values. */
+enum cmd_retval {
+ CMD_RETURN_ERROR = -1,
+ CMD_RETURN_NORMAL = 0,
+ CMD_RETURN_WAIT,
+ CMD_RETURN_STOP
+};
+
+/* Command parse result. */
+enum cmd_parse_status {
+ CMD_PARSE_ERROR,
+ CMD_PARSE_SUCCESS
+};
+struct cmd_parse_result {
+ enum cmd_parse_status status;
+ struct cmd_list *cmdlist;
+ char *error;
+};
+struct cmd_parse_input {
+ int flags;
+#define CMD_PARSE_QUIET 0x1
+#define CMD_PARSE_PARSEONLY 0x2
+#define CMD_PARSE_NOALIAS 0x4
+#define CMD_PARSE_VERBOSE 0x8
+#define CMD_PARSE_ONEGROUP 0x10
+
+ const char *file;
+ u_int line;
+
+ struct cmdq_item *item;
+ struct client *c;
+ struct cmd_find_state fs;
+};
+
+/* Command queue flags. */
+#define CMDQ_STATE_REPEAT 0x1
+#define CMDQ_STATE_CONTROL 0x2
+#define CMDQ_STATE_NOHOOKS 0x4
+
+/* Command queue callback. */
+typedef enum cmd_retval (*cmdq_cb) (struct cmdq_item *, void *);
+
+/* Command definition flag. */
+struct cmd_entry_flag {
+ char flag;
+ enum cmd_find_type type;
+ int flags;
+};
+
+/* Command definition. */
+struct cmd_entry {
+ const char *name;
+ const char *alias;
+
+ struct args_parse args;
+ const char *usage;
+
+ struct cmd_entry_flag source;
+ struct cmd_entry_flag target;
+
+#define CMD_STARTSERVER 0x1
+#define CMD_READONLY 0x2
+#define CMD_AFTERHOOK 0x4
+#define CMD_CLIENT_CFLAG 0x8
+#define CMD_CLIENT_TFLAG 0x10
+#define CMD_CLIENT_CANFAIL 0x20
+ int flags;
+
+ enum cmd_retval (*exec)(struct cmd *, struct cmdq_item *);
+};
+
+/* Status line. */
+#define STATUS_LINES_LIMIT 5
+struct status_line_entry {
+ char *expanded;
+ struct style_ranges ranges;
+};
+struct status_line {
+ struct event timer;
+
+ struct screen screen;
+ struct screen *active;
+ int references;
+
+ struct grid_cell style;
+ struct status_line_entry entries[STATUS_LINES_LIMIT];
+};
+
+/* Prompt type. */
+#define PROMPT_NTYPES 4
+enum prompt_type {
+ PROMPT_TYPE_COMMAND,
+ PROMPT_TYPE_SEARCH,
+ PROMPT_TYPE_TARGET,
+ PROMPT_TYPE_WINDOW_TARGET,
+ PROMPT_TYPE_INVALID = 0xff
+};
+
+/* File in client. */
+typedef void (*client_file_cb) (struct client *, const char *, int, int,
+ struct evbuffer *, void *);
+struct client_file {
+ struct client *c;
+ struct tmuxpeer *peer;
+ struct client_files *tree;
+ int references;
+ int stream;
+
+ char *path;
+ struct evbuffer *buffer;
+ struct bufferevent *event;
+
+ int fd;
+ int error;
+ int closed;
+
+ client_file_cb cb;
+ void *data;
+
+ RB_ENTRY(client_file) entry;
+};
+RB_HEAD(client_files, client_file);
+
+/* Client window. */
+struct client_window {
+ u_int window;
+ struct window_pane *pane;
+
+ u_int sx;
+ u_int sy;
+
+ RB_ENTRY(client_window) entry;
+};
+RB_HEAD(client_windows, client_window);
+
+/* Visible areas not obstructed by overlays. */
+#define OVERLAY_MAX_RANGES 3
+struct overlay_ranges {
+ u_int px[OVERLAY_MAX_RANGES];
+ u_int nx[OVERLAY_MAX_RANGES];
+};
+
+/* Client connection. */
+typedef int (*prompt_input_cb)(struct client *, void *, const char *, int);
+typedef void (*prompt_free_cb)(void *);
+typedef void (*overlay_check_cb)(struct client*, void *, u_int, u_int, u_int,
+ struct overlay_ranges *);
+typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *,
+ u_int *);
+typedef void (*overlay_draw_cb)(struct client *, void *,
+ struct screen_redraw_ctx *);
+typedef int (*overlay_key_cb)(struct client *, void *, struct key_event *);
+typedef void (*overlay_free_cb)(struct client *, void *);
+typedef void (*overlay_resize_cb)(struct client *, void *);
+struct client {
+ const char *name;
+ struct tmuxpeer *peer;
+ struct cmdq_list *queue;
+
+ struct client_windows windows;
+
+ struct control_state *control_state;
+ u_int pause_age;
+
+ pid_t pid;
+ int fd;
+ int out_fd;
+ struct event event;
+ int retval;
+
+ struct timeval creation_time;
+ struct timeval activity_time;
+
+ struct environ *environ;
+ struct format_job_tree *jobs;
+
+ char *title;
+ char *path;
+ const char *cwd;
+
+ char *term_name;
+ int term_features;
+ char *term_type;
+ char **term_caps;
+ u_int term_ncaps;
+
+ char *ttyname;
+ struct tty tty;
+
+ size_t written;
+ size_t discarded;
+ size_t redraw;
+
+ struct event repeat_timer;
+
+ struct event click_timer;
+ u_int click_button;
+ struct mouse_event click_event;
+
+ struct status_line status;
+
+#define CLIENT_TERMINAL 0x1
+#define CLIENT_LOGIN 0x2
+#define CLIENT_EXIT 0x4
+#define CLIENT_REDRAWWINDOW 0x8
+#define CLIENT_REDRAWSTATUS 0x10
+#define CLIENT_REPEAT 0x20
+#define CLIENT_SUSPENDED 0x40
+#define CLIENT_ATTACHED 0x80
+#define CLIENT_EXITED 0x100
+#define CLIENT_DEAD 0x200
+#define CLIENT_REDRAWBORDERS 0x400
+#define CLIENT_READONLY 0x800
+#define CLIENT_NOSTARTSERVER 0x1000
+#define CLIENT_CONTROL 0x2000
+#define CLIENT_CONTROLCONTROL 0x4000
+#define CLIENT_FOCUSED 0x8000
+#define CLIENT_UTF8 0x10000
+#define CLIENT_IGNORESIZE 0x20000
+#define CLIENT_IDENTIFIED 0x40000
+#define CLIENT_STATUSFORCE 0x80000
+#define CLIENT_DOUBLECLICK 0x100000
+#define CLIENT_TRIPLECLICK 0x200000
+#define CLIENT_SIZECHANGED 0x400000
+#define CLIENT_STATUSOFF 0x800000
+#define CLIENT_REDRAWSTATUSALWAYS 0x1000000
+#define CLIENT_REDRAWOVERLAY 0x2000000
+#define CLIENT_CONTROL_NOOUTPUT 0x4000000
+#define CLIENT_DEFAULTSOCKET 0x8000000
+#define CLIENT_STARTSERVER 0x10000000
+#define CLIENT_REDRAWPANES 0x20000000
+#define CLIENT_NOFORK 0x40000000
+#define CLIENT_ACTIVEPANE 0x80000000ULL
+#define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL
+#define CLIENT_CONTROL_WAITEXIT 0x200000000ULL
+#define CLIENT_WINDOWSIZECHANGED 0x400000000ULL
+#define CLIENT_CLIPBOARDBUFFER 0x800000000ULL
+#define CLIENT_ALLREDRAWFLAGS \
+ (CLIENT_REDRAWWINDOW| \
+ CLIENT_REDRAWSTATUS| \
+ CLIENT_REDRAWSTATUSALWAYS| \
+ CLIENT_REDRAWBORDERS| \
+ CLIENT_REDRAWOVERLAY| \
+ CLIENT_REDRAWPANES)
+#define CLIENT_UNATTACHEDFLAGS \
+ (CLIENT_DEAD| \
+ CLIENT_SUSPENDED| \
+ CLIENT_EXIT)
+#define CLIENT_NODETACHFLAGS \
+ (CLIENT_DEAD| \
+ CLIENT_EXIT)
+#define CLIENT_NOSIZEFLAGS \
+ (CLIENT_DEAD| \
+ CLIENT_SUSPENDED| \
+ CLIENT_EXIT)
+ uint64_t flags;
+
+ enum {
+ CLIENT_EXIT_RETURN,
+ CLIENT_EXIT_SHUTDOWN,
+ CLIENT_EXIT_DETACH
+ } exit_type;
+ enum msgtype exit_msgtype;
+ char *exit_session;
+ char *exit_message;
+
+ struct key_table *keytable;
+
+ uint64_t redraw_panes;
+
+ int message_ignore_keys;
+ int message_ignore_styles;
+ char *message_string;
+ struct event message_timer;
+
+ char *prompt_string;
+ struct utf8_data *prompt_buffer;
+ char *prompt_last;
+ size_t prompt_index;
+ prompt_input_cb prompt_inputcb;
+ prompt_free_cb prompt_freecb;
+ void *prompt_data;
+ u_int prompt_hindex[PROMPT_NTYPES];
+ enum {
+ PROMPT_ENTRY,
+ PROMPT_COMMAND
+ } prompt_mode;
+ struct utf8_data *prompt_saved;
+#define PROMPT_SINGLE 0x1
+#define PROMPT_NUMERIC 0x2
+#define PROMPT_INCREMENTAL 0x4
+#define PROMPT_NOFORMAT 0x8
+#define PROMPT_KEY 0x10
+ int prompt_flags;
+ enum prompt_type prompt_type;
+ int prompt_cursor;
+
+ struct session *session;
+ struct session *last_session;
+
+ int references;
+
+ void *pan_window;
+ u_int pan_ox;
+ u_int pan_oy;
+
+ overlay_check_cb overlay_check;
+ overlay_mode_cb overlay_mode;
+ overlay_draw_cb overlay_draw;
+ overlay_key_cb overlay_key;
+ overlay_free_cb overlay_free;
+ overlay_resize_cb overlay_resize;
+ void *overlay_data;
+ struct event overlay_timer;
+
+ struct client_files files;
+
+ u_int *clipboard_panes;
+ u_int clipboard_npanes;
+
+ TAILQ_ENTRY(client) entry;
+};
+TAILQ_HEAD(clients, client);
+
+/* Control mode subscription type. */
+enum control_sub_type {
+ CONTROL_SUB_SESSION,
+ CONTROL_SUB_PANE,
+ CONTROL_SUB_ALL_PANES,
+ CONTROL_SUB_WINDOW,
+ CONTROL_SUB_ALL_WINDOWS
+};
+
+/* Key binding and key table. */
+struct key_binding {
+ key_code key;
+ struct cmd_list *cmdlist;
+ const char *note;
+
+ int flags;
+#define KEY_BINDING_REPEAT 0x1
+
+ RB_ENTRY(key_binding) entry;
+};
+RB_HEAD(key_bindings, key_binding);
+
+struct key_table {
+ const char *name;
+ struct key_bindings key_bindings;
+ struct key_bindings default_key_bindings;
+
+ u_int references;
+
+ RB_ENTRY(key_table) entry;
+};
+RB_HEAD(key_tables, key_table);
+
+/* Option data. */
+RB_HEAD(options_array, options_array_item);
+union options_value {
+ char *string;
+ long long number;
+ struct style style;
+ struct options_array array;
+ struct cmd_list *cmdlist;
+};
+
+/* Option table entries. */
+enum options_table_type {
+ OPTIONS_TABLE_STRING,
+ OPTIONS_TABLE_NUMBER,
+ OPTIONS_TABLE_KEY,
+ OPTIONS_TABLE_COLOUR,
+ OPTIONS_TABLE_FLAG,
+ OPTIONS_TABLE_CHOICE,
+ OPTIONS_TABLE_COMMAND
+};
+
+#define OPTIONS_TABLE_NONE 0
+#define OPTIONS_TABLE_SERVER 0x1
+#define OPTIONS_TABLE_SESSION 0x2
+#define OPTIONS_TABLE_WINDOW 0x4
+#define OPTIONS_TABLE_PANE 0x8
+
+#define OPTIONS_TABLE_IS_ARRAY 0x1
+#define OPTIONS_TABLE_IS_HOOK 0x2
+#define OPTIONS_TABLE_IS_STYLE 0x4
+
+struct options_table_entry {
+ const char *name;
+ const char *alternative_name;
+ enum options_table_type type;
+ int scope;
+ int flags;
+
+ u_int minimum;
+ u_int maximum;
+ const char **choices;
+
+ const char *default_str;
+ long long default_num;
+ const char **default_arr;
+
+ const char *separator;
+ const char *pattern;
+
+ const char *text;
+ const char *unit;
+};
+
+struct options_name_map {
+ const char *from;
+ const char *to;
+};
+
+/* Common command usages. */
+#define CMD_TARGET_PANE_USAGE "[-t target-pane]"
+#define CMD_TARGET_WINDOW_USAGE "[-t target-window]"
+#define CMD_TARGET_SESSION_USAGE "[-t target-session]"
+#define CMD_TARGET_CLIENT_USAGE "[-t target-client]"
+#define CMD_SRCDST_PANE_USAGE "[-s src-pane] [-t dst-pane]"
+#define CMD_SRCDST_WINDOW_USAGE "[-s src-window] [-t dst-window]"
+#define CMD_SRCDST_SESSION_USAGE "[-s src-session] [-t dst-session]"
+#define CMD_SRCDST_CLIENT_USAGE "[-s src-client] [-t dst-client]"
+#define CMD_BUFFER_USAGE "[-b buffer-name]"
+
+/* Spawn common context. */
+struct spawn_context {
+ struct cmdq_item *item;
+
+ struct session *s;
+ struct winlink *wl;
+ struct client *tc;
+
+ struct window_pane *wp0;
+ struct layout_cell *lc;
+
+ const char *name;
+ char **argv;
+ int argc;
+ struct environ *environ;
+
+ int idx;
+ const char *cwd;
+
+ int flags;
+#define SPAWN_KILL 0x1
+#define SPAWN_DETACHED 0x2
+#define SPAWN_RESPAWN 0x4
+#define SPAWN_BEFORE 0x8
+#define SPAWN_NONOTIFY 0x10
+#define SPAWN_FULLSIZE 0x20
+#define SPAWN_EMPTY 0x40
+#define SPAWN_ZOOM 0x80
+};
+
+/* Mode tree sort order. */
+struct mode_tree_sort_criteria {
+ u_int field;
+ int reversed;
+};
+
+/* tmux.c */
+extern struct options *global_options;
+extern struct options *global_s_options;
+extern struct options *global_w_options;
+extern struct environ *global_environ;
+extern struct timeval start_time;
+extern const char *socket_path;
+extern const char *shell_command;
+extern int ptm_fd;
+extern const char *shell_command;
+int checkshell(const char *);
+void setblocking(int, int);
+uint64_t get_timer(void);
+const char *sig2name(int);
+const char *find_cwd(void);
+const char *find_home(void);
+const char *getversion(void);
+
+/* proc.c */
+struct imsg;
+int proc_send(struct tmuxpeer *, enum msgtype, int, const void *, size_t);
+struct tmuxproc *proc_start(const char *);
+void proc_loop(struct tmuxproc *, int (*)(void));
+void proc_exit(struct tmuxproc *);
+void proc_set_signals(struct tmuxproc *, void(*)(int));
+void proc_clear_signals(struct tmuxproc *, int);
+struct tmuxpeer *proc_add_peer(struct tmuxproc *, int,
+ void (*)(struct imsg *, void *), void *);
+void proc_remove_peer(struct tmuxpeer *);
+void proc_kill_peer(struct tmuxpeer *);
+void proc_flush_peer(struct tmuxpeer *);
+void proc_toggle_log(struct tmuxproc *);
+pid_t proc_fork_and_daemon(int *);
+uid_t proc_get_peer_uid(struct tmuxpeer *);
+
+/* cfg.c */
+extern int cfg_finished;
+extern struct client *cfg_client;
+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_from_buffer(const void *, size_t, const char *,
+ struct client *, struct cmdq_item *, 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 *);
+
+/* paste.c */
+struct paste_buffer;
+const char *paste_buffer_name(struct paste_buffer *);
+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 *);
+struct paste_buffer *paste_get_top(const char **);
+struct paste_buffer *paste_get_name(const char *);
+void paste_free(struct paste_buffer *);
+void paste_add(const char *, char *, size_t);
+int paste_rename(const char *, const char *, char **);
+int paste_set(char *, size_t, const char *, char **);
+void paste_replace(struct paste_buffer *, char *, size_t);
+char *paste_make_sample(struct paste_buffer *);
+
+/* format.c */
+#define FORMAT_STATUS 0x1
+#define FORMAT_FORCE 0x2
+#define FORMAT_NOJOBS 0x4
+#define FORMAT_VERBOSE 0x8
+#define FORMAT_NONE 0
+#define FORMAT_PANE 0x80000000U
+#define FORMAT_WINDOW 0x40000000U
+struct format_tree;
+struct format_modifier;
+typedef void *(*format_cb)(struct format_tree *);
+void format_tidy_jobs(void);
+const char *format_skip(const char *, const char *);
+int format_true(const char *);
+struct format_tree *format_create(struct client *, struct cmdq_item *, int,
+ int);
+void format_free(struct format_tree *);
+void format_merge(struct format_tree *, struct format_tree *);
+struct window_pane *format_get_pane(struct format_tree *);
+void printflike(3, 4) format_add(struct format_tree *, const char *,
+ const char *, ...);
+void format_add_tv(struct format_tree *, const char *,
+ struct timeval *);
+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_expand_time(struct format_tree *, const char *);
+char *format_expand(struct format_tree *, const char *);
+char *format_single(struct cmdq_item *, const char *,
+ struct client *, struct session *, struct winlink *,
+ struct window_pane *);
+char *format_single_from_state(struct cmdq_item *, const char *,
+ struct client *, struct cmd_find_state *);
+char *format_single_from_target(struct cmdq_item *, const char *);
+struct format_tree *format_create_defaults(struct cmdq_item *, struct client *,
+ struct session *, struct winlink *, struct window_pane *);
+struct format_tree *format_create_from_state(struct cmdq_item *,
+ struct client *, struct cmd_find_state *);
+struct format_tree *format_create_from_target(struct cmdq_item *);
+void format_defaults(struct format_tree *, struct client *,
+ struct session *, struct winlink *, struct window_pane *);
+void format_defaults_window(struct format_tree *, struct window *);
+void format_defaults_pane(struct format_tree *,
+ struct window_pane *);
+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_line(struct grid *, u_int);
+
+/* format-draw.c */
+void format_draw(struct screen_write_ctx *,
+ const struct grid_cell *, u_int, const char *,
+ struct style_ranges *, int);
+u_int format_width(const char *);
+char *format_trim_left(const char *, u_int);
+char *format_trim_right(const char *, u_int);
+
+/* notify.c */
+void notify_hook(struct cmdq_item *, const char *);
+void notify_client(const char *, struct client *);
+void notify_session(const char *, struct session *);
+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 *);
+
+/* options.c */
+struct options *options_create(struct options *);
+void options_free(struct options *);
+struct options *options_get_parent(struct options *);
+void options_set_parent(struct options *, struct options *);
+struct options_entry *options_first(struct options *);
+struct options_entry *options_next(struct options_entry *);
+struct options_entry *options_empty(struct options *,
+ const struct options_table_entry *);
+struct options_entry *options_default(struct options *,
+ const struct options_table_entry *);
+char *options_default_to_string(const struct options_table_entry *);
+const char *options_name(struct options_entry *);
+struct options *options_owner(struct options_entry *);
+const struct options_table_entry *options_table_entry(struct options_entry *);
+struct options_entry *options_get_only(struct options *, const char *);
+struct options_entry *options_get(struct options *, const char *);
+void options_array_clear(struct options_entry *);
+union options_value *options_array_get(struct options_entry *, u_int);
+int options_array_set(struct options_entry *, u_int, const char *,
+ int, char **);
+int options_array_assign(struct options_entry *, const char *,
+ char **);
+struct options_array_item *options_array_first(struct options_entry *);
+struct options_array_item *options_array_next(struct options_array_item *);
+u_int options_array_item_index(struct options_array_item *);
+union options_value *options_array_item_value(struct options_array_item *);
+int options_is_array(struct options_entry *);
+int options_is_string(struct options_entry *);
+char *options_to_string(struct options_entry *, int, int);
+char *options_parse(const char *, int *);
+struct options_entry *options_parse_get(struct options *, const char *, int *,
+ int);
+char *options_match(const char *, int *, int *);
+struct options_entry *options_match_get(struct options *, const char *, int *,
+ int, int *);
+const char *options_get_string(struct options *, const char *);
+long long options_get_number(struct options *, const char *);
+struct options_entry * printflike(4, 5) options_set_string(struct options *,
+ const char *, int, const char *, ...);
+struct options_entry *options_set_number(struct options *, const char *,
+ long long);
+int options_scope_from_name(struct args *, int,
+ const char *, struct cmd_find_state *, struct options **,
+ char **);
+int options_scope_from_flags(struct args *, int,
+ struct cmd_find_state *, struct options **, char **);
+struct style *options_string_to_style(struct options *, const char *,
+ struct format_tree *);
+int options_from_string(struct options *,
+ const struct options_table_entry *, const char *,
+ const char *, int, char **);
+int options_find_choice(const struct options_table_entry *,
+ const char *, char **);
+void options_push_changes(const char *);
+int options_remove_or_default(struct options_entry *, int,
+ char **);
+
+/* options-table.c */
+extern const struct options_table_entry options_table[];
+extern const struct options_name_map options_other_names[];
+
+/* job.c */
+typedef void (*job_update_cb) (struct job *);
+typedef void (*job_complete_cb) (struct job *);
+typedef void (*job_free_cb) (void *);
+#define JOB_NOWAIT 0x1
+#define JOB_KEEPWRITE 0x2
+#define JOB_PTY 0x4
+struct job *job_run(const char *, int, char **, struct environ *,
+ struct session *, const char *, job_update_cb,
+ job_complete_cb, job_free_cb, void *, int, int, int);
+void job_free(struct job *);
+int job_transfer(struct job *, pid_t *, char *, size_t);
+void job_resize(struct job *, u_int, u_int);
+void job_check_died(pid_t, int);
+int job_get_status(struct job *);
+void *job_get_data(struct job *);
+struct bufferevent *job_get_event(struct job *);
+void job_kill_all(void);
+int job_still_running(void);
+void job_print_summary(struct cmdq_item *, int);
+
+/* environ.c */
+struct environ *environ_create(void);
+void environ_free(struct environ *);
+struct environ_entry *environ_first(struct environ *);
+struct environ_entry *environ_next(struct environ_entry *);
+void environ_copy(struct environ *, struct environ *);
+struct environ_entry *environ_find(struct environ *, const char *);
+void printflike(4, 5) environ_set(struct environ *, const char *, int,
+ const char *, ...);
+void environ_clear(struct environ *, const char *);
+void environ_put(struct environ *, const char *, int);
+void environ_unset(struct environ *, const char *);
+void environ_update(struct options *, struct environ *, struct environ *);
+void environ_push(struct environ *);
+void printflike(2, 3) environ_log(struct environ *, const char *, ...);
+struct environ *environ_for_session(struct session *, int);
+
+/* tty.c */
+void tty_create_log(void);
+int tty_window_bigger(struct tty *);
+int tty_window_offset(struct tty *, u_int *, u_int *, u_int *, u_int *);
+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 *);
+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_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 *);
+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_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 *);
+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_write(void (*)(struct tty *, const struct tty_ctx *),
+ struct tty_ctx *);
+void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *);
+void tty_cmd_cell(struct tty *, const struct tty_ctx *);
+void tty_cmd_cells(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearendofline(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearendofscreen(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearline(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearscreen(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearstartofline(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearstartofscreen(struct tty *, const struct tty_ctx *);
+void tty_cmd_deletecharacter(struct tty *, const struct tty_ctx *);
+void tty_cmd_clearcharacter(struct tty *, const struct tty_ctx *);
+void tty_cmd_deleteline(struct tty *, const struct tty_ctx *);
+void tty_cmd_erasecharacter(struct tty *, const struct tty_ctx *);
+void tty_cmd_insertcharacter(struct tty *, const struct tty_ctx *);
+void tty_cmd_insertline(struct tty *, const struct tty_ctx *);
+void tty_cmd_linefeed(struct tty *, const struct tty_ctx *);
+void tty_cmd_scrollup(struct tty *, const struct tty_ctx *);
+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 *);
+void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
+void tty_default_colours(struct grid_cell *, struct window_pane *);
+
+/* tty-term.c */
+extern struct tty_terms tty_terms;
+u_int tty_term_ncodes(void);
+void tty_term_apply(struct tty_term *, const char *, int);
+void tty_term_apply_overrides(struct tty_term *);
+struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, int *,
+ char **);
+void tty_term_free(struct tty_term *);
+int tty_term_read_list(const char *, int, char ***, u_int *,
+ char **);
+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,
+ int);
+const char *tty_term_string3(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 *);
+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);
+
+/* tty-features.c */
+void tty_add_features(int *, const char *, const char *);
+const char *tty_get_features(int);
+int tty_apply_features(struct tty_term *, int);
+void tty_default_features(int *, const char *, u_int);
+
+/* tty-acs.c */
+int tty_acs_needed(struct tty *);
+const char *tty_acs_get(struct tty *, u_char);
+int tty_acs_reverse_get(struct tty *, const char *, size_t);
+const struct utf8_data *tty_acs_double_borders(int);
+const struct utf8_data *tty_acs_heavy_borders(int);
+const struct utf8_data *tty_acs_rounded_borders(int);
+
+/* tty-keys.c */
+void tty_keys_build(struct tty *);
+void tty_keys_free(struct tty *);
+int tty_keys_next(struct tty *);
+
+/* arguments.c */
+void args_set(struct args *, u_char, struct args_value *);
+struct args *args_create(void);
+struct args *args_parse(const struct args_parse *, struct args_value *,
+ u_int, char **);
+struct args *args_copy(struct args *, int, char **);
+void args_to_vector(struct args *, int *, char ***);
+struct args_value *args_from_vector(int, char **);
+void args_free_value(struct args_value *);
+void args_free_values(struct args_value *, u_int);
+void args_free(struct args *);
+char *args_print(struct args *);
+char *args_escape(const char *);
+int args_has(struct args *, u_char);
+const char *args_get(struct args *, u_char);
+u_char args_first(struct args *, struct args_entry **);
+u_char args_next(struct args_entry **);
+u_int args_count(struct args *);
+struct args_value *args_values(struct args *);
+struct args_value *args_value(struct args *, u_int);
+const char *args_string(struct args *, u_int);
+struct cmd_list *args_make_commands_now(struct cmd *, struct cmdq_item *,
+ u_int, int);
+struct args_command_state *args_make_commands_prepare(struct cmd *,
+ struct cmdq_item *, u_int, const char *, int, int);
+struct cmd_list *args_make_commands(struct args_command_state *, int, char **,
+ char **);
+void args_make_commands_free(struct args_command_state *);
+char *args_make_commands_get_command(struct args_command_state *);
+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_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 **);
+
+/* cmd-find.c */
+int cmd_find_target(struct cmd_find_state *, struct cmdq_item *,
+ const char *, enum cmd_find_type, int);
+struct client *cmd_find_best_client(struct session *);
+struct client *cmd_find_client(struct cmdq_item *, const char *, int);
+void cmd_find_clear_state(struct cmd_find_state *, int);
+int cmd_find_empty_state(struct cmd_find_state *);
+int cmd_find_valid_state(struct cmd_find_state *);
+void cmd_find_copy_state(struct cmd_find_state *,
+ struct cmd_find_state *);
+void cmd_find_from_session(struct cmd_find_state *,
+ struct session *, int);
+void cmd_find_from_winlink(struct cmd_find_state *,
+ struct winlink *, int);
+int cmd_find_from_session_window(struct cmd_find_state *,
+ struct session *, struct window *, int);
+int cmd_find_from_window(struct cmd_find_state *, struct window *,
+ int);
+void cmd_find_from_winlink_pane(struct cmd_find_state *,
+ struct winlink *, struct window_pane *, int);
+int cmd_find_from_pane(struct cmd_find_state *,
+ struct window_pane *, int);
+int cmd_find_from_client(struct cmd_find_state *, struct client *,
+ int);
+int cmd_find_from_mouse(struct cmd_find_state *,
+ struct mouse_event *, int);
+int cmd_find_from_nothing(struct cmd_find_state *, int);
+
+/* cmd.c */
+extern const struct cmd_entry *cmd_table[];
+void printflike(3, 4) cmd_log_argv(int, char **, const char *, ...);
+void cmd_prepend_argv(int *, char ***, const char *);
+void cmd_append_argv(int *, char ***, const char *);
+int cmd_pack_argv(int, char **, char *, size_t);
+int cmd_unpack_argv(char *, size_t, int, char ***);
+char **cmd_copy_argv(int, char **);
+void cmd_free_argv(int, char **);
+char *cmd_stringify_argv(int, char **);
+char *cmd_get_alias(const char *);
+const struct cmd_entry *cmd_get_entry(struct cmd *);
+struct args *cmd_get_args(struct cmd *);
+u_int cmd_get_group(struct cmd *);
+void cmd_get_source(struct cmd *, const char **, u_int *);
+struct cmd *cmd_parse(struct args_value *, u_int, const char *, u_int,
+ char **);
+struct cmd *cmd_copy(struct cmd *, int, char **);
+void cmd_free(struct cmd *);
+char *cmd_print(struct cmd *);
+struct cmd_list *cmd_list_new(void);
+struct cmd_list *cmd_list_copy(struct cmd_list *, int, char **);
+void cmd_list_append(struct cmd_list *, struct cmd *);
+void cmd_list_append_all(struct cmd_list *, struct cmd_list *);
+void cmd_list_move(struct cmd_list *, struct cmd_list *);
+void cmd_list_free(struct cmd_list *);
+char *cmd_list_print(struct cmd_list *, int);
+struct cmd *cmd_list_first(struct cmd_list *);
+struct cmd *cmd_list_next(struct cmd *);
+int cmd_list_all_have(struct cmd_list *, int);
+int cmd_list_any_have(struct cmd_list *, int);
+int cmd_mouse_at(struct window_pane *, struct mouse_event *,
+ u_int *, u_int *, int);
+struct winlink *cmd_mouse_window(struct mouse_event *, struct session **);
+struct window_pane *cmd_mouse_pane(struct mouse_event *, struct session **,
+ struct winlink **);
+char *cmd_template_replace(const char *, const char *, int);
+
+/* cmd-attach-session.c */
+enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int,
+ int, const char *, int, const char *);
+
+/* cmd-parse.c */
+void cmd_parse_empty(struct cmd_parse_input *);
+struct cmd_parse_result *cmd_parse_from_file(FILE *, struct cmd_parse_input *);
+struct cmd_parse_result *cmd_parse_from_string(const char *,
+ struct cmd_parse_input *);
+enum cmd_parse_status cmd_parse_and_insert(const char *,
+ struct cmd_parse_input *, struct cmdq_item *,
+ struct cmdq_state *, char **);
+enum cmd_parse_status cmd_parse_and_append(const char *,
+ struct cmd_parse_input *, struct client *,
+ struct cmdq_state *, char **);
+struct cmd_parse_result *cmd_parse_from_buffer(const void *, size_t,
+ struct cmd_parse_input *);
+struct cmd_parse_result *cmd_parse_from_arguments(struct args_value *, u_int,
+ struct cmd_parse_input *);
+
+/* cmd-queue.c */
+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 *);
+void cmdq_free_state(struct cmdq_state *);
+void printflike(3, 4) cmdq_add_format(struct cmdq_state *, const char *,
+ const char *, ...);
+void cmdq_add_formats(struct cmdq_state *, struct format_tree *);
+void cmdq_merge_formats(struct cmdq_item *, struct format_tree *);
+struct cmdq_list *cmdq_new(void);
+void cmdq_free(struct cmdq_list *);
+const char *cmdq_get_name(struct cmdq_item *);
+struct client *cmdq_get_client(struct cmdq_item *);
+struct client *cmdq_get_target_client(struct cmdq_item *);
+struct cmdq_state *cmdq_get_state(struct cmdq_item *);
+struct cmd_find_state *cmdq_get_target(struct cmdq_item *);
+struct cmd_find_state *cmdq_get_source(struct cmdq_item *);
+struct key_event *cmdq_get_event(struct cmdq_item *);
+struct cmd_find_state *cmdq_get_current(struct cmdq_item *);
+int cmdq_get_flags(struct cmdq_item *);
+struct cmdq_item *cmdq_get_command(struct cmd_list *, struct cmdq_state *);
+#define cmdq_get_callback(cb, data) cmdq_get_callback1(#cb, cb, data)
+struct cmdq_item *cmdq_get_callback1(const char *, cmdq_cb, void *);
+struct cmdq_item *cmdq_get_error(const char *);
+struct cmdq_item *cmdq_insert_after(struct cmdq_item *, struct cmdq_item *);
+struct cmdq_item *cmdq_append(struct client *, struct cmdq_item *);
+void printflike(4, 5) cmdq_insert_hook(struct session *, struct cmdq_item *,
+ struct cmd_find_state *, const char *, ...);
+void cmdq_continue(struct cmdq_item *);
+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 printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...);
+
+/* cmd-wait-for.c */
+void cmd_wait_for_flush(void);
+
+/* client.c */
+int client_main(struct event_base *, int, char **, uint64_t, int);
+
+/* key-bindings.c */
+struct key_table *key_bindings_get_table(const char *, int);
+struct key_table *key_bindings_first_table(void);
+struct key_table *key_bindings_next_table(struct key_table *);
+void key_bindings_unref_table(struct key_table *);
+struct key_binding *key_bindings_get(struct key_table *, key_code);
+struct key_binding *key_bindings_get_default(struct key_table *, key_code);
+struct key_binding *key_bindings_first(struct key_table *);
+struct key_binding *key_bindings_next(struct key_table *, struct key_binding *);
+void key_bindings_add(const char *, key_code, const char *, int,
+ struct cmd_list *);
+void key_bindings_remove(const char *, key_code);
+void key_bindings_reset(const char *, key_code);
+void key_bindings_remove_table(const char *);
+void key_bindings_reset_table(const char *);
+void key_bindings_init(void);
+struct cmdq_item *key_bindings_dispatch(struct key_binding *,
+ struct cmdq_item *, struct client *, struct key_event *,
+ struct cmd_find_state *);
+
+/* key-string.c */
+key_code key_string_lookup_string(const char *);
+const char *key_string_lookup_key(key_code, int);
+
+/* alerts.c */
+void alerts_reset_all(void);
+void alerts_queue(struct window *, int);
+void alerts_check_session(struct session *);
+
+/* file.c */
+int file_cmp(struct client_file *, struct client_file *);
+RB_PROTOTYPE(client_files, client_file, entry, file_cmp);
+struct client_file *file_create_with_peer(struct tmuxpeer *,
+ struct client_files *, int, client_file_cb, void *);
+struct client_file *file_create_with_client(struct client *, int,
+ client_file_cb, void *);
+void file_free(struct client_file *);
+void file_fire_done(struct client_file *);
+void file_fire_read(struct client_file *);
+int file_can_print(struct client *);
+void printflike(2, 3) file_print(struct client *, const char *, ...);
+void printflike(2, 0) file_vprint(struct client *, const char *, va_list);
+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 *);
+void file_push(struct client_file *);
+int file_write_left(struct client_files *);
+void file_write_open(struct client_files *, struct tmuxpeer *,
+ struct imsg *, int, int, client_file_cb, void *);
+void file_write_data(struct client_files *, struct imsg *);
+void file_write_close(struct client_files *, struct imsg *);
+void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *,
+ int, int, client_file_cb, void *);
+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 *);
+
+/* server.c */
+extern struct tmuxproc *server_proc;
+extern struct clients clients;
+extern struct cmd_find_state marked_pane;
+extern struct message_list message_log;
+void server_set_marked(struct session *, struct winlink *,
+ struct window_pane *);
+void server_clear_marked(void);
+int server_is_marked(struct session *, struct winlink *,
+ struct window_pane *);
+int server_check_marked(void);
+int server_start(struct tmuxproc *, int, struct event_base *, int, char *);
+void server_update_socket(void);
+void server_add_accept(int);
+void printflike(1, 2) server_add_message(const char *, ...);
+int server_create_socket(int, char **);
+
+/* server-client.c */
+RB_PROTOTYPE(client_windows, client_window, entry, server_client_window_cmp);
+u_int server_client_how_many(void);
+void server_client_set_overlay(struct client *, u_int, overlay_check_cb,
+ overlay_mode_cb, overlay_draw_cb, overlay_key_cb,
+ overlay_free_cb, overlay_resize_cb, void *);
+void server_client_clear_overlay(struct client *);
+void server_client_overlay_range(u_int, u_int, u_int, u_int, u_int, u_int,
+ u_int, struct overlay_ranges *);
+void server_client_set_key_table(struct client *, const char *);
+const char *server_client_get_key_table(struct client *);
+int server_client_check_nested(struct client *);
+int server_client_handle_key(struct client *, struct key_event *);
+struct client *server_client_create(int);
+int server_client_open(struct client *, char **);
+void server_client_unref(struct client *);
+void server_client_set_session(struct client *, struct session *);
+void server_client_lost(struct client *);
+void server_client_suspend(struct client *);
+void server_client_detach(struct client *, enum msgtype);
+void server_client_exec(struct client *, const char *);
+void server_client_loop(void);
+void server_client_push_stdout(struct client *);
+void server_client_push_stderr(struct client *);
+const char *server_client_get_cwd(struct client *, struct session *);
+void server_client_set_flags(struct client *, const char *);
+const char *server_client_get_flags(struct client *);
+struct client_window *server_client_get_client_window(struct client *, u_int);
+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 *);
+
+/* server-fn.c */
+void server_redraw_client(struct client *);
+void server_status_client(struct client *);
+void server_redraw_session(struct session *);
+void server_redraw_session_group(struct session *);
+void server_status_session(struct session *);
+void server_status_session_group(struct session *);
+void server_redraw_window(struct window *);
+void server_redraw_window_borders(struct window *);
+void server_status_window(struct window *);
+void server_lock(void);
+void server_lock_session(struct session *);
+void server_lock_client(struct client *);
+void server_kill_pane(struct window_pane *);
+void server_kill_window(struct window *, int);
+void server_renumber_session(struct session *);
+void server_renumber_all(void);
+int server_link_window(struct session *,
+ struct winlink *, struct session *, int, int, int, char **);
+void server_unlink_window(struct session *, struct winlink *);
+void server_destroy_pane(struct window_pane *, int);
+void server_destroy_session(struct session *);
+void server_check_unattached(void);
+void server_unzoom_window(struct window *);
+
+/* status.c */
+extern char **status_prompt_hlist[];
+extern u_int status_prompt_hsize[];
+void status_timer_start(struct client *);
+void status_timer_start_all(void);
+void status_update_cache(struct session *);
+int status_at_line(struct client *);
+u_int status_line_size(struct client *);
+struct style_range *status_get_range(struct client *, u_int, u_int);
+void status_init(struct client *);
+void status_free(struct client *);
+int status_redraw(struct client *);
+void printflike(5, 6) status_message_set(struct client *, int, int, int,
+ const char *, ...);
+void status_message_clear(struct client *);
+int status_message_redraw(struct client *);
+void status_prompt_set(struct client *, struct cmd_find_state *,
+ const char *, const char *, prompt_input_cb, prompt_free_cb,
+ void *, int, enum prompt_type);
+void status_prompt_clear(struct client *);
+int status_prompt_redraw(struct client *);
+int status_prompt_key(struct client *, key_code);
+void status_prompt_update(struct client *, const char *, const char *);
+void status_prompt_load_history(void);
+void status_prompt_save_history(void);
+const char *status_prompt_type_string(u_int);
+enum prompt_type status_prompt_type(const char *type);
+
+/* resize.c */
+void resize_window(struct window *, u_int, u_int, int, int);
+void default_window_size(struct client *, struct session *, struct window *,
+ u_int *, u_int *, u_int *, u_int *, int);
+void recalculate_size(struct window *, int);
+void recalculate_sizes(void);
+void recalculate_sizes_now(int);
+
+/* input.c */
+struct input_ctx *input_init(struct window_pane *, struct bufferevent *,
+ struct colour_palette *);
+void input_free(struct input_ctx *);
+void input_reset(struct input_ctx *, int);
+struct evbuffer *input_pending(struct input_ctx *);
+void input_parse_pane(struct window_pane *);
+void input_parse_buffer(struct window_pane *, u_char *, size_t);
+void input_parse_screen(struct input_ctx *, struct screen *,
+ screen_write_init_ctx_cb, void *, u_char *, size_t);
+void input_reply_clipboard(struct bufferevent *, const char *, size_t,
+ const char *);
+
+/* input-key.c */
+void input_key_build(void);
+int input_key_pane(struct window_pane *, key_code, struct mouse_event *);
+int input_key(struct screen *, struct bufferevent *, key_code);
+int input_key_get_mouse(struct screen *, struct mouse_event *, u_int,
+ u_int, const char **, size_t *);
+
+/* colour.c */
+int colour_find_rgb(u_char, u_char, u_char);
+int colour_join_rgb(u_char, u_char, u_char);
+void colour_split_rgb(int, u_char *, u_char *, u_char *);
+int colour_force_rgb(int);
+const char *colour_tostring(int);
+int colour_fromstring(const char *s);
+int colour_256toRGB(int);
+int colour_256to16(int);
+int colour_byname(const char *);
+void colour_palette_init(struct colour_palette *);
+void colour_palette_clear(struct colour_palette *);
+void colour_palette_free(struct colour_palette *);
+int colour_palette_get(struct colour_palette *, int);
+int colour_palette_set(struct colour_palette *, int, int);
+void colour_palette_from_option(struct colour_palette *, struct options *);
+
+/* attributes.c */
+const char *attributes_tostring(int);
+int attributes_fromstring(const char *);
+
+/* grid.c */
+extern const struct grid_cell grid_default_cell;
+void grid_empty_line(struct grid *, u_int, u_int);
+int grid_cells_equal(const struct grid_cell *, const struct grid_cell *);
+int grid_cells_look_equal(const struct grid_cell *,
+ const struct grid_cell *);
+struct grid *grid_create(u_int, u_int, u_int);
+void grid_destroy(struct grid *);
+int grid_compare(struct grid *, struct grid *);
+void grid_collect_history(struct grid *);
+void grid_remove_history(struct grid *, u_int );
+void grid_scroll_history(struct grid *, u_int);
+void grid_scroll_history_region(struct grid *, u_int, u_int, u_int);
+void grid_clear_history(struct grid *);
+const struct grid_line *grid_peek_line(struct grid *, u_int);
+void grid_get_cell(struct grid *, u_int, u_int, struct grid_cell *);
+void grid_set_cell(struct grid *, u_int, u_int, const struct grid_cell *);
+void grid_set_padding(struct grid *, u_int, u_int);
+void grid_set_cells(struct grid *, u_int, u_int, const struct grid_cell *,
+ const char *, size_t);
+struct grid_line *grid_get_line(struct grid *, u_int);
+void grid_adjust_lines(struct grid *, u_int);
+void grid_clear(struct grid *, u_int, u_int, u_int, u_int, u_int);
+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);
+void grid_duplicate_lines(struct grid *, u_int, struct grid *, u_int,
+ u_int);
+void grid_reflow(struct grid *, u_int);
+void grid_wrap_position(struct grid *, u_int, u_int, u_int *, u_int *);
+void grid_unwrap_position(struct grid *, u_int *, u_int *, u_int, u_int);
+u_int grid_line_length(struct grid *, u_int);
+
+/* grid-reader.c */
+void grid_reader_start(struct grid_reader *, struct grid *, u_int, u_int);
+void grid_reader_get_cursor(struct grid_reader *, u_int *, u_int *);
+u_int grid_reader_line_length(struct grid_reader *);
+int grid_reader_in_set(struct grid_reader *, const char *);
+void grid_reader_cursor_right(struct grid_reader *, int, int);
+void grid_reader_cursor_left(struct grid_reader *, int);
+void grid_reader_cursor_down(struct grid_reader *);
+void grid_reader_cursor_up(struct grid_reader *);
+void grid_reader_cursor_start_of_line(struct grid_reader *, int);
+void grid_reader_cursor_end_of_line(struct grid_reader *, int, int);
+void grid_reader_cursor_next_word(struct grid_reader *, const char *);
+void grid_reader_cursor_next_word_end(struct grid_reader *, const char *);
+void grid_reader_cursor_previous_word(struct grid_reader *, const char *,
+ int, int);
+int grid_reader_cursor_jump(struct grid_reader *,
+ const struct utf8_data *);
+int grid_reader_cursor_jump_back(struct grid_reader *,
+ const struct utf8_data *);
+void grid_reader_cursor_back_to_indentation(struct grid_reader *);
+
+/* grid-view.c */
+void grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *);
+void grid_view_set_cell(struct grid *, u_int, u_int,
+ const struct grid_cell *);
+void grid_view_set_padding(struct grid *, u_int, u_int);
+void grid_view_set_cells(struct grid *, u_int, u_int,
+ const struct grid_cell *, const char *, size_t);
+void grid_view_clear_history(struct grid *, u_int);
+void grid_view_clear(struct grid *, u_int, u_int, u_int, u_int, u_int);
+void grid_view_scroll_region_up(struct grid *, u_int, u_int, u_int);
+void grid_view_scroll_region_down(struct grid *, u_int, u_int, u_int);
+void grid_view_insert_lines(struct grid *, u_int, u_int, u_int);
+void grid_view_insert_lines_region(struct grid *, u_int, u_int, u_int,
+ u_int);
+void grid_view_delete_lines(struct grid *, u_int, u_int, u_int);
+void grid_view_delete_lines_region(struct grid *, u_int, u_int, u_int,
+ u_int);
+void grid_view_insert_cells(struct grid *, u_int, u_int, u_int, u_int);
+void grid_view_delete_cells(struct grid *, u_int, u_int, u_int, u_int);
+char *grid_view_string_cells(struct grid *, u_int, u_int, u_int);
+
+/* screen-write.c */
+void screen_write_make_list(struct screen *);
+void screen_write_free_list(struct screen *);
+void screen_write_start_pane(struct screen_write_ctx *,
+ struct window_pane *, struct screen *);
+void screen_write_start(struct screen_write_ctx *, struct screen *);
+void screen_write_start_callback(struct screen_write_ctx *, struct screen *,
+ screen_write_init_ctx_cb, void *);
+void screen_write_stop(struct screen_write_ctx *);
+void screen_write_reset(struct screen_write_ctx *);
+size_t printflike(1, 2) screen_write_strlen(const char *, ...);
+int printflike(7, 8) screen_write_text(struct screen_write_ctx *, u_int, u_int,
+ u_int, int, const struct grid_cell *, const char *, ...);
+void printflike(3, 4) screen_write_puts(struct screen_write_ctx *,
+ const struct grid_cell *, const char *, ...);
+void printflike(4, 5) screen_write_nputs(struct screen_write_ctx *,
+ ssize_t, const struct grid_cell *, const char *, ...);
+void printflike(4, 0) screen_write_vnputs(struct screen_write_ctx *, ssize_t,
+ const struct grid_cell *, const char *, va_list);
+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_vline(struct screen_write_ctx *, u_int, int, int);
+void screen_write_menu(struct screen_write_ctx *, struct menu *, int,
+ const struct grid_cell *);
+void screen_write_box(struct screen_write_ctx *, u_int, u_int, int,
+ const struct grid_cell *, const char *);
+void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int,
+ u_int);
+void screen_write_backspace(struct screen_write_ctx *);
+void screen_write_mode_set(struct screen_write_ctx *, int);
+void screen_write_mode_clear(struct screen_write_ctx *, int);
+void screen_write_cursorup(struct screen_write_ctx *, u_int);
+void screen_write_cursordown(struct screen_write_ctx *, u_int);
+void screen_write_cursorright(struct screen_write_ctx *, u_int);
+void screen_write_cursorleft(struct screen_write_ctx *, u_int);
+void screen_write_alignmenttest(struct screen_write_ctx *);
+void screen_write_insertcharacter(struct screen_write_ctx *, u_int, u_int);
+void screen_write_deletecharacter(struct screen_write_ctx *, u_int, u_int);
+void screen_write_clearcharacter(struct screen_write_ctx *, u_int, u_int);
+void screen_write_insertline(struct screen_write_ctx *, u_int, u_int);
+void screen_write_deleteline(struct screen_write_ctx *, u_int, u_int);
+void screen_write_clearline(struct screen_write_ctx *, u_int);
+void screen_write_clearendofline(struct screen_write_ctx *, u_int);
+void screen_write_clearstartofline(struct screen_write_ctx *, u_int);
+void screen_write_cursormove(struct screen_write_ctx *, int, int, int);
+void screen_write_reverseindex(struct screen_write_ctx *, u_int);
+void screen_write_scrollregion(struct screen_write_ctx *, u_int, u_int);
+void screen_write_linefeed(struct screen_write_ctx *, int, u_int);
+void screen_write_scrollup(struct screen_write_ctx *, u_int, u_int);
+void screen_write_scrolldown(struct screen_write_ctx *, u_int, u_int);
+void screen_write_carriagereturn(struct screen_write_ctx *);
+void screen_write_clearendofscreen(struct screen_write_ctx *, u_int);
+void screen_write_clearstartofscreen(struct screen_write_ctx *, u_int);
+void screen_write_clearscreen(struct screen_write_ctx *, u_int);
+void screen_write_clearhistory(struct screen_write_ctx *);
+void screen_write_fullredraw(struct screen_write_ctx *);
+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_alternateon(struct screen_write_ctx *,
+ struct grid_cell *, int);
+void screen_write_alternateoff(struct screen_write_ctx *,
+ struct grid_cell *, int);
+
+/* screen-redraw.c */
+void screen_redraw_screen(struct client *);
+void screen_redraw_pane(struct client *, struct window_pane *);
+
+/* screen.c */
+void screen_init(struct screen *, u_int, u_int, u_int);
+void screen_reinit(struct screen *);
+void screen_free(struct screen *);
+void screen_reset_tabs(struct screen *);
+void screen_set_cursor_style(u_int, enum screen_cursor_style *, int *);
+void screen_set_cursor_colour(struct screen *, int);
+int screen_set_title(struct screen *, const char *);
+void screen_set_path(struct screen *, const char *);
+void screen_push_title(struct screen *);
+void screen_pop_title(struct screen *);
+void screen_resize(struct screen *, u_int, u_int, int);
+void screen_resize_cursor(struct screen *, u_int, u_int, int, int, int);
+void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int,
+ u_int, int, struct grid_cell *);
+void screen_clear_selection(struct screen *);
+void screen_hide_selection(struct screen *);
+int screen_check_selection(struct screen *, u_int, u_int);
+void screen_select_cell(struct screen *, struct grid_cell *,
+ const struct grid_cell *);
+void screen_alternate_on(struct screen *, struct grid_cell *, int);
+void screen_alternate_off(struct screen *, struct grid_cell *, int);
+const char *screen_mode_to_string(int);
+
+/* window.c */
+extern struct windows windows;
+extern struct window_pane_tree all_window_panes;
+int window_cmp(struct window *, struct window *);
+RB_PROTOTYPE(windows, window, entry, window_cmp);
+int winlink_cmp(struct winlink *, struct winlink *);
+RB_PROTOTYPE(winlinks, winlink, entry, winlink_cmp);
+int window_pane_cmp(struct window_pane *, struct window_pane *);
+RB_PROTOTYPE(window_pane_tree, window_pane, tree_entry, window_pane_cmp);
+struct winlink *winlink_find_by_index(struct winlinks *, int);
+struct winlink *winlink_find_by_window(struct winlinks *, struct window *);
+struct winlink *winlink_find_by_window_id(struct winlinks *, u_int);
+u_int winlink_count(struct winlinks *);
+struct winlink *winlink_add(struct winlinks *, int);
+void winlink_set_window(struct winlink *, struct window *);
+void winlink_remove(struct winlinks *, struct winlink *);
+struct winlink *winlink_next(struct winlink *);
+struct winlink *winlink_previous(struct winlink *);
+struct winlink *winlink_next_by_number(struct winlink *, struct session *,
+ int);
+struct winlink *winlink_previous_by_number(struct winlink *, struct session *,
+ int);
+void winlink_stack_push(struct winlink_stack *, struct winlink *);
+void winlink_stack_remove(struct winlink_stack *, struct winlink *);
+struct window *window_find_by_id_str(const char *);
+struct window *window_find_by_id(u_int);
+void window_update_activity(struct window *);
+struct window *window_create(u_int, u_int, u_int, u_int);
+void window_pane_set_event(struct window_pane *);
+struct window_pane *window_get_active_at(struct window *, u_int, u_int);
+struct window_pane *window_find_string(struct window *, const char *);
+int window_has_pane(struct window *, struct window_pane *);
+int window_set_active_pane(struct window *, struct window_pane *,
+ int);
+void window_update_focus(struct window *);
+void window_pane_update_focus(struct window_pane *);
+void window_redraw_active_switch(struct window *,
+ struct window_pane *);
+struct window_pane *window_add_pane(struct window *, struct window_pane *,
+ u_int, int);
+void window_resize(struct window *, u_int, u_int, int, int);
+void window_pane_send_resize(struct window_pane *, u_int, u_int);
+int window_zoom(struct window_pane *);
+int window_unzoom(struct window *);
+int window_push_zoom(struct window *, int, int);
+int window_pop_zoom(struct window *);
+void window_lost_pane(struct window *, struct window_pane *);
+void window_remove_pane(struct window *, struct window_pane *);
+struct window_pane *window_pane_at_index(struct window *, u_int);
+struct window_pane *window_pane_next_by_number(struct window *,
+ struct window_pane *, u_int);
+struct window_pane *window_pane_previous_by_number(struct window *,
+ struct window_pane *, u_int);
+int window_pane_index(struct window_pane *, u_int *);
+u_int window_count_panes(struct window *);
+void window_destroy_panes(struct window *);
+struct window_pane *window_pane_find_by_id_str(const char *);
+struct window_pane *window_pane_find_by_id(u_int);
+int window_pane_destroy_ready(struct window_pane *);
+void window_pane_resize(struct window_pane *, u_int, u_int);
+int window_pane_set_mode(struct window_pane *,
+ struct window_pane *, const struct window_mode *,
+ struct cmd_find_state *, struct args *);
+void window_pane_reset_mode(struct window_pane *);
+void window_pane_reset_mode_all(struct window_pane *);
+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 *);
+u_int window_pane_search(struct window_pane *, const char *, int,
+ int);
+const char *window_printable_flags(struct winlink *, int);
+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_set_name(struct window *, const char *);
+void window_add_ref(struct window *, const char *);
+void window_remove_ref(struct window *, const char *);
+void winlink_clear_flags(struct winlink *);
+int winlink_shuffle_up(struct session *, struct winlink *, int);
+int window_pane_start_input(struct window_pane *,
+ struct cmdq_item *, char **);
+void *window_pane_get_new_data(struct window_pane *,
+ struct window_pane_offset *, size_t *);
+void window_pane_update_used_data(struct window_pane *,
+ struct window_pane_offset *, size_t);
+void window_set_fill_character(struct window *);
+
+/* layout.c */
+u_int layout_count_cells(struct layout_cell *);
+struct layout_cell *layout_create_cell(struct layout_cell *);
+void layout_free_cell(struct layout_cell *);
+void layout_print_cell(struct layout_cell *, const char *, u_int);
+void layout_destroy_cell(struct window *, struct layout_cell *,
+ struct layout_cell **);
+void layout_resize_layout(struct window *, struct layout_cell *,
+ enum layout_type, int, int);
+struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int);
+void layout_set_size(struct layout_cell *, u_int, u_int, u_int,
+ u_int);
+void layout_make_leaf(struct layout_cell *, struct window_pane *);
+void layout_make_node(struct layout_cell *, enum layout_type);
+void layout_fix_offsets(struct window *);
+void layout_fix_panes(struct window *, struct window_pane *);
+void layout_resize_adjust(struct window *, struct layout_cell *,
+ enum layout_type, int);
+void layout_init(struct window *, struct window_pane *);
+void layout_free(struct window *);
+void layout_resize(struct window *, u_int, u_int);
+void layout_resize_pane(struct window_pane *, enum layout_type,
+ int, int);
+void layout_resize_pane_to(struct window_pane *, enum layout_type,
+ u_int);
+void layout_assign_pane(struct layout_cell *, struct window_pane *,
+ int);
+struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type,
+ int, int);
+void layout_close_pane(struct window_pane *);
+int layout_spread_cell(struct window *, struct layout_cell *);
+void layout_spread_out(struct window_pane *);
+
+/* layout-custom.c */
+char *layout_dump(struct layout_cell *);
+int layout_parse(struct window *, const char *, char **);
+
+/* layout-set.c */
+int layout_set_lookup(const char *);
+u_int layout_set_select(struct window *, u_int);
+u_int layout_set_next(struct window *);
+u_int layout_set_previous(struct window *);
+
+/* mode-tree.c */
+typedef void (*mode_tree_build_cb)(void *, struct mode_tree_sort_criteria *,
+ uint64_t *, const char *);
+typedef void (*mode_tree_draw_cb)(void *, void *, struct screen_write_ctx *,
+ u_int, u_int);
+typedef int (*mode_tree_search_cb)(void *, void *, const char *);
+typedef void (*mode_tree_menu_cb)(void *, struct client *, key_code);
+typedef u_int (*mode_tree_height_cb)(void *, u_int);
+typedef key_code (*mode_tree_key_cb)(void *, void *, u_int);
+typedef void (*mode_tree_each_cb)(void *, void *, struct client *, key_code);
+u_int mode_tree_count_tagged(struct mode_tree_data *);
+void *mode_tree_get_current(struct mode_tree_data *);
+const char *mode_tree_get_current_name(struct mode_tree_data *);
+void mode_tree_expand_current(struct mode_tree_data *);
+void mode_tree_collapse_current(struct mode_tree_data *);
+void mode_tree_expand(struct mode_tree_data *, uint64_t);
+int mode_tree_set_current(struct mode_tree_data *, uint64_t);
+void mode_tree_each_tagged(struct mode_tree_data *, mode_tree_each_cb,
+ struct client *, key_code, int);
+void mode_tree_up(struct mode_tree_data *, int);
+void mode_tree_down(struct mode_tree_data *, int);
+struct mode_tree_data *mode_tree_start(struct window_pane *, struct args *,
+ mode_tree_build_cb, mode_tree_draw_cb, mode_tree_search_cb,
+ mode_tree_menu_cb, mode_tree_height_cb, mode_tree_key_cb, void *,
+ const struct menu_item *, const char **, u_int, struct screen **);
+void mode_tree_zoom(struct mode_tree_data *, struct args *);
+void mode_tree_build(struct mode_tree_data *);
+void mode_tree_free(struct mode_tree_data *);
+void mode_tree_resize(struct mode_tree_data *, u_int, u_int);
+struct mode_tree_item *mode_tree_add(struct mode_tree_data *,
+ struct mode_tree_item *, void *, uint64_t, const char *,
+ const char *, int);
+void mode_tree_draw_as_parent(struct mode_tree_item *);
+void mode_tree_no_tag(struct mode_tree_item *);
+void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *);
+void mode_tree_draw(struct mode_tree_data *);
+int mode_tree_key(struct mode_tree_data *, struct client *, key_code *,
+ struct mouse_event *, u_int *, u_int *);
+void mode_tree_run_command(struct client *, struct cmd_find_state *,
+ const char *, const char *);
+
+/* window-buffer.c */
+extern const struct window_mode window_buffer_mode;
+
+/* window-tree.c */
+extern const struct window_mode window_tree_mode;
+
+/* window-clock.c */
+extern const struct window_mode window_clock_mode;
+extern const char window_clock_table[14][5][5];
+
+/* window-client.c */
+extern const struct window_mode window_client_mode;
+
+/* window-copy.c */
+extern const struct window_mode window_copy_mode;
+extern const struct window_mode window_view_mode;
+void printflike(3, 4) window_copy_add(struct window_pane *, int, const char *,
+ ...);
+void printflike(3, 0) window_copy_vadd(struct window_pane *, int, const char *,
+ va_list);
+void window_copy_pageup(struct window_pane *, int);
+void window_copy_start_drag(struct client *, struct mouse_event *);
+char *window_copy_get_word(struct window_pane *, u_int, u_int);
+char *window_copy_get_line(struct window_pane *, u_int);
+
+/* window-option.c */
+extern const struct window_mode window_customize_mode;
+
+/* names.c */
+void check_window_name(struct window *);
+char *default_window_name(struct window *);
+char *parse_window_name(const char *);
+
+/* control.c */
+void control_discard(struct client *);
+void control_start(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 *);
+void control_continue_pane(struct client *, struct window_pane *);
+void control_pause_pane(struct client *, struct window_pane *);
+struct window_pane_offset *control_pane_offset(struct client *,
+ struct window_pane *, int *);
+void control_reset_offsets(struct client *);
+void printflike(2, 3) control_write(struct client *, const char *, ...);
+void control_write_output(struct client *, struct window_pane *);
+int control_all_done(struct client *);
+void control_add_sub(struct client *, const char *, enum control_sub_type,
+ int, const char *);
+void control_remove_sub(struct client *, const char *);
+
+/* control-notify.c */
+void control_notify_input(struct client *, struct window_pane *,
+ const u_char *, size_t);
+void control_notify_pane_mode_changed(int);
+void control_notify_window_layout_changed(struct window *);
+void control_notify_window_pane_changed(struct window *);
+void control_notify_window_unlinked(struct session *, struct window *);
+void control_notify_window_linked(struct session *, struct window *);
+void control_notify_window_renamed(struct window *);
+void control_notify_client_session_changed(struct client *);
+void control_notify_client_detached(struct client *);
+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 *);
+
+/* session.c */
+extern struct sessions sessions;
+extern u_int next_session_id;
+int session_cmp(struct session *, struct session *);
+RB_PROTOTYPE(sessions, session, entry, session_cmp);
+int session_alive(struct session *);
+struct session *session_find(const char *);
+struct session *session_find_by_id_str(const char *);
+struct session *session_find_by_id(u_int);
+struct session *session_create(const char *, const char *, const char *,
+ struct environ *, struct options *, struct termios *);
+void session_destroy(struct session *, int, const char *);
+void session_add_ref(struct session *, const char *);
+void session_remove_ref(struct session *, const char *);
+char *session_check_name(const char *);
+void session_update_activity(struct session *, struct timeval *);
+struct session *session_next_session(struct session *);
+struct session *session_previous_session(struct session *);
+struct winlink *session_new(struct session *, const char *, int, char **,
+ const char *, const char *, int, char **);
+struct winlink *session_attach(struct session *, struct window *, int,
+ char **);
+int session_detach(struct session *, struct winlink *);
+int session_has(struct session *, struct window *);
+int session_is_linked(struct session *, struct window *);
+int session_next(struct session *, int);
+int session_previous(struct session *, int);
+int session_select(struct session *, int);
+int session_last(struct session *);
+int session_set_current(struct session *, struct winlink *);
+struct session_group *session_group_contains(struct session *);
+struct session_group *session_group_find(const char *);
+struct session_group *session_group_new(const char *);
+void session_group_add(struct session_group *, struct session *);
+void session_group_synchronize_to(struct session *);
+void session_group_synchronize_from(struct session *);
+u_int session_group_count(struct session_group *);
+u_int session_group_attached_count(struct session_group *);
+void session_renumber_windows(struct session *);
+
+/* utf8.c */
+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 *);
+void utf8_set(struct utf8_data *, u_char);
+void utf8_copy(struct utf8_data *, const struct utf8_data *);
+enum utf8_state utf8_open(struct utf8_data *, u_char);
+enum utf8_state utf8_append(struct utf8_data *, u_char);
+int utf8_isvalid(const char *);
+int utf8_strvis(char *, const char *, size_t, int);
+int utf8_stravis(char **, const char *, int);
+int utf8_stravisx(char **, const char *, size_t, int);
+char *utf8_sanitize(const char *);
+size_t utf8_strlen(const struct utf8_data *);
+u_int utf8_strwidth(const struct utf8_data *, ssize_t);
+struct utf8_data *utf8_fromcstr(const char *);
+char *utf8_tocstr(struct utf8_data *);
+u_int utf8_cstrwidth(const char *);
+char *utf8_padcstr(const char *, u_int);
+char *utf8_rpadcstr(const char *, u_int);
+int utf8_cstrhas(const char *, const struct utf8_data *);
+
+/* osdep-*.c */
+char *osdep_get_name(int, char *);
+char *osdep_get_cwd(int);
+struct event_base *osdep_event_init(void);
+
+/* log.c */
+void log_add_level(void);
+int log_get_level(void);
+void log_open(const char *);
+void log_toggle(const char *);
+void log_close(void);
+void printflike(1, 2) log_debug(const char *, ...);
+__dead void printflike(1, 2) fatal(const char *, ...);
+__dead void printflike(1, 2) fatalx(const char *, ...);
+
+/* menu.c */
+#define MENU_NOMOUSE 0x1
+#define MENU_TAB 0x2
+#define MENU_STAYOPEN 0x4
+struct menu *menu_create(const char *);
+void menu_add_items(struct menu *, const struct menu_item *,
+ struct cmdq_item *, struct client *,
+ struct cmd_find_state *);
+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 *,
+ menu_choice_cb, void *);
+int menu_display(struct menu *, int, struct cmdq_item *, u_int,
+ u_int, struct client *, struct cmd_find_state *,
+ menu_choice_cb, void *);
+struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *);
+void menu_check_cb(struct client *, void *, u_int, u_int, u_int,
+ struct overlay_ranges *);
+void menu_draw_cb(struct client *, void *,
+ struct screen_redraw_ctx *);
+void menu_free_cb(struct client *, void *);
+int menu_key_cb(struct client *, void *, struct key_event *);
+
+/* popup.c */
+#define POPUP_CLOSEEXIT 0x1
+#define POPUP_CLOSEEXITZERO 0x2
+#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_editor(struct client *, const char *, size_t,
+ popup_finish_edit_cb, void *);
+
+/* style.c */
+int style_parse(struct style *,const struct grid_cell *,
+ const char *);
+const char *style_tostring(struct style *);
+void style_add(struct grid_cell *, struct options *,
+ const char *, struct format_tree *);
+void style_apply(struct grid_cell *, struct options *,
+ const char *, struct format_tree *);
+void style_set(struct style *, const struct grid_cell *);
+void style_copy(struct style *, struct style *);
+
+/* spawn.c */
+struct winlink *spawn_window(struct spawn_context *, char **);
+struct window_pane *spawn_pane(struct spawn_context *, char **);
+
+/* regsub.c */
+char *regsub(const char *, const char *, const char *, int);
+
+/* server-acl.c */
+void server_acl_init(void);
+struct server_acl_user *server_acl_user_find(uid_t);
+void server_acl_display(struct cmdq_item *);
+void server_acl_user_allow(uid_t);
+void server_acl_user_deny(uid_t);
+void server_acl_user_allow_write(uid_t);
+void server_acl_user_deny_write(uid_t);
+int server_acl_join(struct client *);
+uid_t server_acl_get_uid(struct server_acl_user *);
+
+#endif /* TMUX_H */
diff --git a/tty-acs.c b/tty-acs.c
new file mode 100644
index 0000000..64ba367
--- /dev/null
+++ b/tty-acs.c
@@ -0,0 +1,269 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2010 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"
+
+/* Table mapping ACS entries to UTF-8. */
+struct tty_acs_entry {
+ u_char key;
+ const char *string;
+};
+static const struct tty_acs_entry tty_acs_table[] = {
+ { '+', "\342\206\222" }, /* arrow pointing right */
+ { ',', "\342\206\220" }, /* arrow pointing left */
+ { '-', "\342\206\221" }, /* arrow pointing up */
+ { '.', "\342\206\223" }, /* arrow pointing down */
+ { '0', "\342\226\256" }, /* solid square block */
+ { '`', "\342\227\206" }, /* diamond */
+ { 'a', "\342\226\222" }, /* checker board (stipple) */
+ { 'b', "\342\220\211" },
+ { 'c', "\342\220\214" },
+ { 'd', "\342\220\215" },
+ { 'e', "\342\220\212" },
+ { 'f', "\302\260" }, /* degree symbol */
+ { 'g', "\302\261" }, /* plus/minus */
+ { 'h', "\342\220\244" },
+ { 'i', "\342\220\213" },
+ { 'j', "\342\224\230" }, /* lower right corner */
+ { 'k', "\342\224\220" }, /* upper right corner */
+ { 'l', "\342\224\214" }, /* upper left corner */
+ { 'm', "\342\224\224" }, /* lower left corner */
+ { 'n', "\342\224\274" }, /* large plus or crossover */
+ { 'o', "\342\216\272" }, /* scan line 1 */
+ { 'p', "\342\216\273" }, /* scan line 3 */
+ { 'q', "\342\224\200" }, /* horizontal line */
+ { 'r', "\342\216\274" }, /* scan line 7 */
+ { 's', "\342\216\275" }, /* scan line 9 */
+ { 't', "\342\224\234" }, /* tee pointing right */
+ { 'u', "\342\224\244" }, /* tee pointing left */
+ { 'v', "\342\224\264" }, /* tee pointing up */
+ { 'w', "\342\224\254" }, /* tee pointing down */
+ { 'x', "\342\224\202" }, /* vertical line */
+ { 'y', "\342\211\244" }, /* less-than-or-equal-to */
+ { 'z', "\342\211\245" }, /* greater-than-or-equal-to */
+ { '{', "\317\200" }, /* greek pi */
+ { '|', "\342\211\240" }, /* not-equal */
+ { '}', "\302\243" }, /* UK pound sign */
+ { '~', "\302\267" } /* bullet */
+};
+
+/* Table mapping UTF-8 to ACS entries. */
+struct tty_acs_reverse_entry {
+ const char *string;
+ u_char key;
+};
+static const struct tty_acs_reverse_entry tty_acs_reverse2[] = {
+ { "\302\267", '~' }
+};
+static const struct tty_acs_reverse_entry tty_acs_reverse3[] = {
+ { "\342\224\200", 'q' },
+ { "\342\224\201", 'q' },
+ { "\342\224\202", 'x' },
+ { "\342\224\203", 'x' },
+ { "\342\224\214", 'l' },
+ { "\342\224\217", 'k' },
+ { "\342\224\220", 'k' },
+ { "\342\224\223", 'l' },
+ { "\342\224\224", 'm' },
+ { "\342\224\227", 'm' },
+ { "\342\224\230", 'j' },
+ { "\342\224\233", 'j' },
+ { "\342\224\234", 't' },
+ { "\342\224\243", 't' },
+ { "\342\224\244", 'u' },
+ { "\342\224\253", 'u' },
+ { "\342\224\263", 'w' },
+ { "\342\224\264", 'v' },
+ { "\342\224\273", 'v' },
+ { "\342\224\274", 'n' },
+ { "\342\225\213", 'n' },
+ { "\342\225\220", 'q' },
+ { "\342\225\221", 'x' },
+ { "\342\225\224", 'l' },
+ { "\342\225\227", 'k' },
+ { "\342\225\232", 'm' },
+ { "\342\225\235", 'j' },
+ { "\342\225\240", 't' },
+ { "\342\225\243", 'u' },
+ { "\342\225\246", 'w' },
+ { "\342\225\251", 'v' },
+ { "\342\225\254", 'n' },
+};
+
+/* UTF-8 double borders. */
+static const struct utf8_data tty_acs_double_borders_list[] = {
+ { "", 0, 0, 0 },
+ { "\342\225\221", 0, 3, 1 }, /* U+2551 */
+ { "\342\225\220", 0, 3, 1 }, /* U+2550 */
+ { "\342\225\224", 0, 3, 1 }, /* U+2554 */
+ { "\342\225\227", 0, 3, 1 }, /* U+2557 */
+ { "\342\225\232", 0, 3, 1 }, /* U+255A */
+ { "\342\225\235", 0, 3, 1 }, /* U+255D */
+ { "\342\225\246", 0, 3, 1 }, /* U+2566 */
+ { "\342\225\251", 0, 3, 1 }, /* U+2569 */
+ { "\342\225\240", 0, 3, 1 }, /* U+2560 */
+ { "\342\225\243", 0, 3, 1 }, /* U+2563 */
+ { "\342\225\254", 0, 3, 1 }, /* U+256C */
+ { "\302\267", 0, 2, 1 } /* U+00B7 */
+};
+
+/* UTF-8 heavy borders. */
+static const struct utf8_data tty_acs_heavy_borders_list[] = {
+ { "", 0, 0, 0 },
+ { "\342\224\203", 0, 3, 1 }, /* U+2503 */
+ { "\342\224\201", 0, 3, 1 }, /* U+2501 */
+ { "\342\224\217", 0, 3, 1 }, /* U+250F */
+ { "\342\224\223", 0, 3, 1 }, /* U+2513 */
+ { "\342\224\227", 0, 3, 1 }, /* U+2517 */
+ { "\342\224\233", 0, 3, 1 }, /* U+251B */
+ { "\342\224\263", 0, 3, 1 }, /* U+2533 */
+ { "\342\224\273", 0, 3, 1 }, /* U+253B */
+ { "\342\224\243", 0, 3, 1 }, /* U+2523 */
+ { "\342\224\253", 0, 3, 1 }, /* U+252B */
+ { "\342\225\213", 0, 3, 1 }, /* U+254B */
+ { "\302\267", 0, 2, 1 } /* U+00B7 */
+};
+
+/* UTF-8 rounded borders. */
+static const struct utf8_data tty_acs_rounded_borders_list[] = {
+ { "", 0, 0, 0 },
+ { "\342\224\202", 0, 3, 1 }, /* U+2502 */
+ { "\342\224\200", 0, 3, 1 }, /* U+2500 */
+ { "\342\225\255", 0, 3, 1 }, /* U+256D */
+ { "\342\225\256", 0, 3, 1 }, /* U+256E */
+ { "\342\225\260", 0, 3, 1 }, /* U+2570 */
+ { "\342\225\257", 0, 3, 1 }, /* U+256F */
+ { "\342\224\263", 0, 3, 1 }, /* U+2533 */
+ { "\342\224\273", 0, 3, 1 }, /* U+253B */
+ { "\342\224\243", 0, 3, 1 }, /* U+2523 */
+ { "\342\224\253", 0, 3, 1 }, /* U+252B */
+ { "\342\225\213", 0, 3, 1 }, /* U+254B */
+ { "\302\267", 0, 2, 1 } /* U+00B7 */
+};
+
+/* Get cell border character for double style. */
+const struct utf8_data *
+tty_acs_double_borders(int cell_type)
+{
+ return (&tty_acs_double_borders_list[cell_type]);
+}
+
+/* Get cell border character for heavy style. */
+const struct utf8_data *
+tty_acs_heavy_borders(int cell_type)
+{
+ return (&tty_acs_heavy_borders_list[cell_type]);
+}
+
+/* Get cell border character for rounded style. */
+const struct utf8_data *
+tty_acs_rounded_borders(int cell_type)
+{
+ return (&tty_acs_rounded_borders_list[cell_type]);
+}
+
+static int
+tty_acs_cmp(const void *key, const void *value)
+{
+ const struct tty_acs_entry *entry = value;
+ int test = *(u_char *)key;
+
+ return (test - entry->key);
+}
+
+static int
+tty_acs_reverse_cmp(const void *key, const void *value)
+{
+ const struct tty_acs_reverse_entry *entry = value;
+ const char *test = key;
+
+ return (strcmp(test, entry->string));
+}
+
+/* Should this terminal use ACS instead of UTF-8 line drawing? */
+int
+tty_acs_needed(struct tty *tty)
+{
+ if (tty == NULL)
+ return (0);
+
+ /*
+ * If the U8 flag is present, it marks whether a terminal supports
+ * UTF-8 and ACS together.
+ *
+ * If it is present and zero, we force ACS - this gives users a way to
+ * turn off UTF-8 line drawing.
+ *
+ * If it is nonzero, we can fall through to the default and use UTF-8
+ * line drawing on UTF-8 terminals.
+ */
+ if (tty_term_has(tty->term, TTYC_U8) &&
+ tty_term_number(tty->term, TTYC_U8) == 0)
+ return (1);
+
+ if (tty->client->flags & CLIENT_UTF8)
+ return (0);
+ return (1);
+}
+
+/* Retrieve ACS to output as UTF-8. */
+const char *
+tty_acs_get(struct tty *tty, u_char ch)
+{
+ const struct tty_acs_entry *entry;
+
+ /* Use the ACS set instead of UTF-8 if needed. */
+ if (tty_acs_needed(tty)) {
+ if (tty->term->acs[ch][0] == '\0')
+ return (NULL);
+ return (&tty->term->acs[ch][0]);
+ }
+
+ /* Otherwise look up the UTF-8 translation. */
+ entry = bsearch(&ch, tty_acs_table, nitems(tty_acs_table),
+ sizeof tty_acs_table[0], tty_acs_cmp);
+ if (entry == NULL)
+ return (NULL);
+ return (entry->string);
+}
+
+/* Reverse UTF-8 into ACS. */
+int
+tty_acs_reverse_get(__unused struct tty *tty, const char *s, size_t slen)
+{
+ const struct tty_acs_reverse_entry *table, *entry;
+ u_int items;
+
+ if (slen == 2) {
+ table = tty_acs_reverse2;
+ items = nitems(tty_acs_reverse2);
+ } else if (slen == 3) {
+ table = tty_acs_reverse3;
+ items = nitems(tty_acs_reverse3);
+ } else
+ return (-1);
+ entry = bsearch(s, table, items, sizeof table[0], tty_acs_reverse_cmp);
+ if (entry == NULL)
+ return (-1);
+ return (entry->key);
+}
diff --git a/tty-features.c b/tty-features.c
new file mode 100644
index 0000000..2848b4d
--- /dev/null
+++ b/tty-features.c
@@ -0,0 +1,400 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 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"
+
+/*
+ * Still hardcoded:
+ * - default colours (under AX or op capabilities);
+ * - AIX colours (under colors >= 16);
+ * - alternate escape (if terminal is VT100-like).
+ *
+ * Also:
+ * - DECFRA uses a flag instead of capabilities;
+ * - UTF-8 is a separate flag on the client; needed for unattached clients.
+ */
+
+/* A named terminal feature. */
+struct tty_feature {
+ const char *name;
+ const char **capabilities;
+ int flags;
+};
+
+/* Terminal has xterm(1) title setting. */
+static const char *tty_feature_title_capabilities[] = {
+ "tsl=\\E]0;", /* should be using TS really */
+ "fsl=\\a",
+ NULL
+};
+static const struct tty_feature tty_feature_title = {
+ "title",
+ tty_feature_title_capabilities,
+ 0
+};
+
+/* Terminal has OSC 7 working directory. */
+static const char *tty_feature_osc7_capabilities[] = {
+ "Swd=\\E]7;",
+ "fsl=\\a",
+ NULL
+};
+static const struct tty_feature tty_feature_osc7 = {
+ "osc7",
+ tty_feature_osc7_capabilities,
+ 0
+};
+
+/* Terminal has mouse support. */
+static const char *tty_feature_mouse_capabilities[] = {
+ "kmous=\\E[M",
+ NULL
+};
+static const struct tty_feature tty_feature_mouse = {
+ "mouse",
+ tty_feature_mouse_capabilities,
+ 0
+};
+
+/* Terminal can set the clipboard with OSC 52. */
+static const char *tty_feature_clipboard_capabilities[] = {
+ "Ms=\\E]52;%p1%s;%p2%s\\a",
+ NULL
+};
+static const struct tty_feature tty_feature_clipboard = {
+ "clipboard",
+ tty_feature_clipboard_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[] = {
+ "AX",
+ "setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm",
+ "setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm",
+ "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",
+ NULL
+};
+static const struct tty_feature tty_feature_rgb = {
+ "RGB",
+ tty_feature_rgb_capabilities,
+ TERM_256COLOURS|TERM_RGBCOLOURS
+};
+
+/* Terminal supports 256 colours. */
+static const char *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",
+ NULL
+};
+static const struct tty_feature tty_feature_256 = {
+ "256",
+ tty_feature_256_capabilities,
+ TERM_256COLOURS
+};
+
+/* Terminal supports overline. */
+static const char *tty_feature_overline_capabilities[] = {
+ "Smol=\\E[53m",
+ NULL
+};
+static const struct tty_feature tty_feature_overline = {
+ "overline",
+ tty_feature_overline_capabilities,
+ 0
+};
+
+/* Terminal supports underscore styles. */
+static const char *tty_feature_usstyle_capabilities[] = {
+ "Smulx=\\E[4::%p1%dm",
+ "Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m",
+ "ol=\\E[59m",
+ NULL
+};
+static const struct tty_feature tty_feature_usstyle = {
+ "usstyle",
+ tty_feature_usstyle_capabilities,
+ 0
+};
+
+/* Terminal supports bracketed paste. */
+static const char *tty_feature_bpaste_capabilities[] = {
+ "Enbp=\\E[?2004h",
+ "Dsbp=\\E[?2004l",
+ NULL
+};
+static const struct tty_feature tty_feature_bpaste = {
+ "bpaste",
+ tty_feature_bpaste_capabilities,
+ 0
+};
+
+/* Terminal supports focus reporting. */
+static const char *tty_feature_focus_capabilities[] = {
+ "Enfcs=\\E[?1004h",
+ "Dsfcs=\\E[?1004l",
+ NULL
+};
+static const struct tty_feature tty_feature_focus = {
+ "focus",
+ tty_feature_focus_capabilities,
+ 0
+};
+
+/* Terminal supports cursor styles. */
+static const char *tty_feature_cstyle_capabilities[] = {
+ "Ss=\\E[%p1%d q",
+ "Se=\\E[2 q",
+ NULL
+};
+static const struct tty_feature tty_feature_cstyle = {
+ "cstyle",
+ tty_feature_cstyle_capabilities,
+ 0
+};
+
+/* Terminal supports cursor colours. */
+static const char *tty_feature_ccolour_capabilities[] = {
+ "Cs=\\E]12;%p1%s\\a",
+ "Cr=\\E]112\\a",
+ NULL
+};
+static const struct tty_feature tty_feature_ccolour = {
+ "ccolour",
+ tty_feature_ccolour_capabilities,
+ 0
+};
+
+/* Terminal supports strikethrough. */
+static const char *tty_feature_strikethrough_capabilities[] = {
+ "smxx=\\E[9m",
+ NULL
+};
+static const struct tty_feature tty_feature_strikethrough = {
+ "strikethrough",
+ tty_feature_strikethrough_capabilities,
+ 0
+};
+
+/* Terminal supports synchronized updates. */
+static const char *tty_feature_sync_capabilities[] = {
+ "Sync=\\EP=%p1%ds\\E\\\\",
+ NULL
+};
+static const struct tty_feature tty_feature_sync = {
+ "sync",
+ tty_feature_sync_capabilities,
+ 0
+};
+
+/* Terminal supports extended keys. */
+static const char *tty_feature_extkeys_capabilities[] = {
+ "Eneks=\\E[>4;1m",
+ "Dseks=\\E[>4m",
+ NULL
+};
+static const struct tty_feature tty_feature_extkeys = {
+ "extkeys",
+ tty_feature_extkeys_capabilities,
+ 0
+};
+
+/* Terminal supports DECSLRM margins. */
+static const char *tty_feature_margins_capabilities[] = {
+ "Enmg=\\E[?69h",
+ "Dsmg=\\E[?69l",
+ "Clmg=\\E[s",
+ "Cmg=\\E[%i%p1%d;%p2%ds",
+ NULL
+};
+static const struct tty_feature tty_feature_margins = {
+ "margins",
+ tty_feature_margins_capabilities,
+ TERM_DECSLRM
+};
+
+/* Terminal supports DECFRA rectangle fill. */
+static const char *tty_feature_rectfill_capabilities[] = {
+ "Rect",
+ NULL
+};
+static const struct tty_feature tty_feature_rectfill = {
+ "rectfill",
+ tty_feature_rectfill_capabilities,
+ TERM_DECFRA
+};
+
+/* Available terminal features. */
+static const struct tty_feature *tty_features[] = {
+ &tty_feature_256,
+ &tty_feature_bpaste,
+ &tty_feature_ccolour,
+ &tty_feature_clipboard,
+ &tty_feature_cstyle,
+ &tty_feature_extkeys,
+ &tty_feature_focus,
+ &tty_feature_margins,
+ &tty_feature_mouse,
+ &tty_feature_osc7,
+ &tty_feature_overline,
+ &tty_feature_rectfill,
+ &tty_feature_rgb,
+ &tty_feature_strikethrough,
+ &tty_feature_sync,
+ &tty_feature_title,
+ &tty_feature_usstyle
+};
+
+void
+tty_add_features(int *feat, const char *s, const char *separators)
+{
+ const struct tty_feature *tf;
+ char *next, *loop, *copy;
+ u_int i;
+
+ log_debug("adding terminal features %s", s);
+
+ loop = copy = xstrdup(s);
+ while ((next = strsep(&loop, separators)) != NULL) {
+ for (i = 0; i < nitems(tty_features); i++) {
+ tf = tty_features[i];
+ if (strcasecmp(tf->name, next) == 0)
+ break;
+ }
+ if (i == nitems(tty_features)) {
+ log_debug("unknown terminal feature: %s", next);
+ break;
+ }
+ if (~(*feat) & (1 << i)) {
+ log_debug("adding terminal feature: %s", tf->name);
+ (*feat) |= (1 << i);
+ }
+ }
+ free(copy);
+}
+
+const char *
+tty_get_features(int feat)
+{
+ const struct tty_feature *tf;
+ static char s[512];
+ u_int i;
+
+ *s = '\0';
+ for (i = 0; i < nitems(tty_features); i++) {
+ if (~feat & (1 << i))
+ continue;
+ tf = tty_features[i];
+
+ strlcat(s, tf->name, sizeof s);
+ strlcat(s, ",", sizeof s);
+ }
+ if (*s != '\0')
+ s[strlen(s) - 1] = '\0';
+ return (s);
+}
+
+int
+tty_apply_features(struct tty_term *term, int feat)
+{
+ const struct tty_feature *tf;
+ const char **capability;
+ u_int i;
+
+ if (feat == 0)
+ return (0);
+ log_debug("applying terminal features: %s", tty_get_features(feat));
+
+ for (i = 0; i < nitems(tty_features); i++) {
+ if ((term->features & (1 << i)) || (~feat & (1 << i)))
+ continue;
+ tf = tty_features[i];
+
+ log_debug("applying terminal feature: %s", tf->name);
+ if (tf->capabilities != NULL) {
+ capability = tf->capabilities;
+ while (*capability != NULL) {
+ log_debug("adding capability: %s", *capability);
+ tty_term_apply(term, *capability, 1);
+ capability++;
+ }
+ }
+ term->flags |= tf->flags;
+ }
+ if ((term->features | feat) == term->features)
+ return (0);
+ term->features |= feat;
+ return (1);
+}
+
+void
+tty_default_features(int *feat, const char *name, u_int version)
+{
+ static struct {
+ const char *name;
+ u_int version;
+ const char *features;
+ } table[] = {
+#define TTY_FEATURES_BASE_MODERN_XTERM \
+ "256,RGB,bpaste,clipboard,mouse,strikethrough,title"
+ { .name = "mintty",
+ .features = TTY_FEATURES_BASE_MODERN_XTERM
+ ",ccolour,cstyle,extkeys,margins,overline,usstyle"
+ },
+ { .name = "tmux",
+ .features = TTY_FEATURES_BASE_MODERN_XTERM
+ ",ccolour,cstyle,focus,overline,usstyle"
+ },
+ { .name = "rxvt-unicode",
+ .features = "256,bpaste,ccolour,cstyle,mouse,title"
+ },
+ { .name = "iTerm2",
+ .features = TTY_FEATURES_BASE_MODERN_XTERM
+ ",cstyle,extkeys,margins,usstyle,sync,osc7"
+ },
+ { .name = "XTerm",
+ /*
+ * xterm also supports DECSLRM and DECFRA, but they can be
+ * disabled so not set it here - they will be added if
+ * secondary DA shows VT420.
+ */
+ .features = TTY_FEATURES_BASE_MODERN_XTERM
+ ",ccolour,cstyle,extkeys,focus"
+ }
+ };
+ u_int i;
+
+ for (i = 0; i < nitems(table); i++) {
+ if (strcmp(table[i].name, name) != 0)
+ continue;
+ if (version != 0 && version < table[i].version)
+ continue;
+ tty_add_features(feat, table[i].features, ",");
+ }
+}
diff --git a/tty-keys.c b/tty-keys.c
new file mode 100644
index 0000000..64dd91b
--- /dev/null
+++ b/tty-keys.c
@@ -0,0 +1,1404 @@
+/* $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 <sys/time.h>
+
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Handle keys input from the outside terminal. tty_default_*_keys[] are a base
+ * table of supported keys which are looked up in terminfo(5) and translated
+ * into a ternary tree.
+ */
+
+static void tty_keys_add1(struct tty_key **, const char *, key_code);
+static void tty_keys_add(struct tty *, const char *, key_code);
+static void tty_keys_free1(struct tty_key *);
+static struct tty_key *tty_keys_find1(struct tty_key *, const char *, size_t,
+ size_t *);
+static struct tty_key *tty_keys_find(struct tty *, const char *, size_t,
+ size_t *);
+static int tty_keys_next1(struct tty *, const char *, size_t, key_code *,
+ size_t *, int);
+static void tty_keys_callback(int, short, void *);
+static int tty_keys_extended_key(struct tty *, const char *, size_t,
+ size_t *, key_code *);
+static int tty_keys_mouse(struct tty *, const char *, size_t, size_t *,
+ struct mouse_event *);
+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_extended_device_attributes(struct tty *, const char *,
+ size_t, size_t *);
+
+/* A key tree entry. */
+struct tty_key {
+ char ch;
+ key_code key;
+
+ struct tty_key *left;
+ struct tty_key *right;
+
+ struct tty_key *next;
+};
+
+/* Default raw keys. */
+struct tty_default_key_raw {
+ const char *string;
+ key_code key;
+};
+static const struct tty_default_key_raw tty_default_raw_keys[] = {
+ /* Application escape. */
+ { "\033O[", '\033' },
+
+ /*
+ * Numeric keypad. Just use the vt100 escape sequences here and always
+ * put the terminal into keypad_xmit mode. Translation of numbers
+ * mode/applications mode is done in input-keys.c.
+ */
+ { "\033Oo", KEYC_KP_SLASH|KEYC_KEYPAD },
+ { "\033Oj", KEYC_KP_STAR|KEYC_KEYPAD },
+ { "\033Om", KEYC_KP_MINUS|KEYC_KEYPAD },
+ { "\033Ow", KEYC_KP_SEVEN|KEYC_KEYPAD },
+ { "\033Ox", KEYC_KP_EIGHT|KEYC_KEYPAD },
+ { "\033Oy", KEYC_KP_NINE|KEYC_KEYPAD },
+ { "\033Ok", KEYC_KP_PLUS|KEYC_KEYPAD },
+ { "\033Ot", KEYC_KP_FOUR|KEYC_KEYPAD },
+ { "\033Ou", KEYC_KP_FIVE|KEYC_KEYPAD },
+ { "\033Ov", KEYC_KP_SIX|KEYC_KEYPAD },
+ { "\033Oq", KEYC_KP_ONE|KEYC_KEYPAD },
+ { "\033Or", KEYC_KP_TWO|KEYC_KEYPAD },
+ { "\033Os", KEYC_KP_THREE|KEYC_KEYPAD },
+ { "\033OM", KEYC_KP_ENTER|KEYC_KEYPAD },
+ { "\033Op", KEYC_KP_ZERO|KEYC_KEYPAD },
+ { "\033On", KEYC_KP_PERIOD|KEYC_KEYPAD },
+
+ /* Arrow keys. */
+ { "\033OA", KEYC_UP|KEYC_CURSOR },
+ { "\033OB", KEYC_DOWN|KEYC_CURSOR },
+ { "\033OC", KEYC_RIGHT|KEYC_CURSOR },
+ { "\033OD", KEYC_LEFT|KEYC_CURSOR },
+
+ { "\033[A", KEYC_UP|KEYC_CURSOR },
+ { "\033[B", KEYC_DOWN|KEYC_CURSOR },
+ { "\033[C", KEYC_RIGHT|KEYC_CURSOR },
+ { "\033[D", KEYC_LEFT|KEYC_CURSOR },
+
+ /*
+ * Meta arrow keys. These do not get the IMPLIED_META flag so they
+ * don't match the xterm-style meta keys in the output tree - Escape+Up
+ * should stay as Escape+Up and not become M-Up.
+ */
+ { "\033\033OA", KEYC_UP|KEYC_CURSOR|KEYC_META },
+ { "\033\033OB", KEYC_DOWN|KEYC_CURSOR|KEYC_META },
+ { "\033\033OC", KEYC_RIGHT|KEYC_CURSOR|KEYC_META },
+ { "\033\033OD", KEYC_LEFT|KEYC_CURSOR|KEYC_META },
+
+ { "\033\033[A", KEYC_UP|KEYC_CURSOR|KEYC_META },
+ { "\033\033[B", KEYC_DOWN|KEYC_CURSOR|KEYC_META },
+ { "\033\033[C", KEYC_RIGHT|KEYC_CURSOR|KEYC_META },
+ { "\033\033[D", KEYC_LEFT|KEYC_CURSOR|KEYC_META },
+
+ /* Other (xterm) "cursor" keys. */
+ { "\033OH", KEYC_HOME },
+ { "\033OF", KEYC_END },
+
+ { "\033\033OH", KEYC_HOME|KEYC_META|KEYC_IMPLIED_META },
+ { "\033\033OF", KEYC_END|KEYC_META|KEYC_IMPLIED_META },
+
+ { "\033[H", KEYC_HOME },
+ { "\033[F", KEYC_END },
+
+ { "\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. */
+ { "\033Oa", KEYC_UP|KEYC_CTRL },
+ { "\033Ob", KEYC_DOWN|KEYC_CTRL },
+ { "\033Oc", KEYC_RIGHT|KEYC_CTRL },
+ { "\033Od", KEYC_LEFT|KEYC_CTRL },
+
+ { "\033[a", KEYC_UP|KEYC_SHIFT },
+ { "\033[b", KEYC_DOWN|KEYC_SHIFT },
+ { "\033[c", KEYC_RIGHT|KEYC_SHIFT },
+ { "\033[d", KEYC_LEFT|KEYC_SHIFT },
+
+ /* rxvt-style function + modifier keys (C = ^, S = $, C-S = @). */
+ { "\033[11^", KEYC_F1|KEYC_CTRL },
+ { "\033[12^", KEYC_F2|KEYC_CTRL },
+ { "\033[13^", KEYC_F3|KEYC_CTRL },
+ { "\033[14^", KEYC_F4|KEYC_CTRL },
+ { "\033[15^", KEYC_F5|KEYC_CTRL },
+ { "\033[17^", KEYC_F6|KEYC_CTRL },
+ { "\033[18^", KEYC_F7|KEYC_CTRL },
+ { "\033[19^", KEYC_F8|KEYC_CTRL },
+ { "\033[20^", KEYC_F9|KEYC_CTRL },
+ { "\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 },
+ { "\033[13@", KEYC_F3|KEYC_CTRL|KEYC_SHIFT },
+ { "\033[14@", KEYC_F4|KEYC_CTRL|KEYC_SHIFT },
+ { "\033[15@", KEYC_F5|KEYC_CTRL|KEYC_SHIFT },
+ { "\033[17@", KEYC_F6|KEYC_CTRL|KEYC_SHIFT },
+ { "\033[18@", KEYC_F7|KEYC_CTRL|KEYC_SHIFT },
+ { "\033[19@", KEYC_F8|KEYC_CTRL|KEYC_SHIFT },
+ { "\033[20@", KEYC_F9|KEYC_CTRL|KEYC_SHIFT },
+ { "\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 },
+ { "\033[O", KEYC_FOCUS_OUT },
+
+ /* Paste keys. */
+ { "\033[200~", KEYC_PASTE_START },
+ { "\033[201~", KEYC_PASTE_END },
+};
+
+/* Default xterm keys. */
+struct tty_default_key_xterm {
+ const char *template;
+ key_code key;
+};
+static const struct tty_default_key_xterm tty_default_xterm_keys[] = {
+ { "\033[1;_P", KEYC_F1 },
+ { "\033O1;_P", KEYC_F1 },
+ { "\033O_P", KEYC_F1 },
+ { "\033[1;_Q", KEYC_F2 },
+ { "\033O1;_Q", KEYC_F2 },
+ { "\033O_Q", KEYC_F2 },
+ { "\033[1;_R", KEYC_F3 },
+ { "\033O1;_R", KEYC_F3 },
+ { "\033O_R", KEYC_F3 },
+ { "\033[1;_S", KEYC_F4 },
+ { "\033O1;_S", KEYC_F4 },
+ { "\033O_S", 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_F11 },
+ { "\033[24;_~", KEYC_F12 },
+ { "\033[1;_A", KEYC_UP },
+ { "\033[1;_B", KEYC_DOWN },
+ { "\033[1;_C", KEYC_RIGHT },
+ { "\033[1;_D", KEYC_LEFT },
+ { "\033[1;_H", KEYC_HOME },
+ { "\033[1;_F", KEYC_END },
+ { "\033[5;_~", KEYC_PPAGE },
+ { "\033[6;_~", KEYC_NPAGE },
+ { "\033[2;_~", KEYC_IC },
+ { "\033[3;_~", KEYC_DC },
+};
+static const key_code tty_default_xterm_modifiers[] = {
+ 0,
+ 0,
+ KEYC_SHIFT,
+ KEYC_META|KEYC_IMPLIED_META,
+ KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META,
+ KEYC_CTRL,
+ KEYC_SHIFT|KEYC_CTRL,
+ KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL,
+ KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL,
+ KEYC_META|KEYC_IMPLIED_META
+};
+
+/*
+ * Default terminfo(5) keys. Any keys that have builtin modifiers (that is,
+ * where the key itself contains the modifiers) has the KEYC_XTERM flag set so
+ * a leading escape is not treated as meta (and probably removed).
+ */
+struct tty_default_key_code {
+ enum tty_code_code code;
+ key_code key;
+};
+static const struct tty_default_key_code tty_default_code_keys[] = {
+ /* Function keys. */
+ { TTYC_KF1, KEYC_F1 },
+ { TTYC_KF2, KEYC_F2 },
+ { TTYC_KF3, KEYC_F3 },
+ { TTYC_KF4, KEYC_F4 },
+ { TTYC_KF5, KEYC_F5 },
+ { TTYC_KF6, KEYC_F6 },
+ { TTYC_KF7, KEYC_F7 },
+ { TTYC_KF8, KEYC_F8 },
+ { TTYC_KF9, KEYC_F9 },
+ { TTYC_KF10, KEYC_F10 },
+ { TTYC_KF11, KEYC_F11 },
+ { TTYC_KF12, KEYC_F12 },
+
+ { TTYC_KF13, KEYC_F1|KEYC_SHIFT },
+ { TTYC_KF14, KEYC_F2|KEYC_SHIFT },
+ { TTYC_KF15, KEYC_F3|KEYC_SHIFT },
+ { TTYC_KF16, KEYC_F4|KEYC_SHIFT },
+ { TTYC_KF17, KEYC_F5|KEYC_SHIFT },
+ { TTYC_KF18, KEYC_F6|KEYC_SHIFT },
+ { TTYC_KF19, KEYC_F7|KEYC_SHIFT },
+ { TTYC_KF20, KEYC_F8|KEYC_SHIFT },
+ { TTYC_KF21, KEYC_F9|KEYC_SHIFT },
+ { TTYC_KF22, KEYC_F10|KEYC_SHIFT },
+ { TTYC_KF23, KEYC_F11|KEYC_SHIFT },
+ { TTYC_KF24, KEYC_F12|KEYC_SHIFT },
+
+ { TTYC_KF25, KEYC_F1|KEYC_CTRL },
+ { TTYC_KF26, KEYC_F2|KEYC_CTRL },
+ { TTYC_KF27, KEYC_F3|KEYC_CTRL },
+ { TTYC_KF28, KEYC_F4|KEYC_CTRL },
+ { TTYC_KF29, KEYC_F5|KEYC_CTRL },
+ { TTYC_KF30, KEYC_F6|KEYC_CTRL },
+ { TTYC_KF31, KEYC_F7|KEYC_CTRL },
+ { TTYC_KF32, KEYC_F8|KEYC_CTRL },
+ { TTYC_KF33, KEYC_F9|KEYC_CTRL },
+ { TTYC_KF34, KEYC_F10|KEYC_CTRL },
+ { TTYC_KF35, KEYC_F11|KEYC_CTRL },
+ { TTYC_KF36, KEYC_F12|KEYC_CTRL },
+
+ { TTYC_KF37, KEYC_F1|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF38, KEYC_F2|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF39, KEYC_F3|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF40, KEYC_F4|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF41, KEYC_F5|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF42, KEYC_F6|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF43, KEYC_F7|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF44, KEYC_F8|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF45, KEYC_F9|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF46, KEYC_F10|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF47, KEYC_F11|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KF48, KEYC_F12|KEYC_SHIFT|KEYC_CTRL },
+
+ { TTYC_KF49, KEYC_F1|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF50, KEYC_F2|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF51, KEYC_F3|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF52, KEYC_F4|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF53, KEYC_F5|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF54, KEYC_F6|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF55, KEYC_F7|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF56, KEYC_F8|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF57, KEYC_F9|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF58, KEYC_F10|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF59, KEYC_F11|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KF60, KEYC_F12|KEYC_META|KEYC_IMPLIED_META },
+
+ { TTYC_KF61, KEYC_F1|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT },
+ { TTYC_KF62, KEYC_F2|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT },
+ { TTYC_KF63, KEYC_F3|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT },
+
+ { TTYC_KICH1, KEYC_IC },
+ { TTYC_KDCH1, KEYC_DC },
+ { TTYC_KHOME, KEYC_HOME },
+ { TTYC_KEND, KEYC_END },
+ { TTYC_KNP, KEYC_NPAGE },
+ { TTYC_KPP, KEYC_PPAGE },
+ { TTYC_KCBT, KEYC_BTAB },
+
+ /* Arrow keys from terminfo. */
+ { TTYC_KCUU1, KEYC_UP|KEYC_CURSOR },
+ { TTYC_KCUD1, KEYC_DOWN|KEYC_CURSOR },
+ { TTYC_KCUB1, KEYC_LEFT|KEYC_CURSOR },
+ { TTYC_KCUF1, KEYC_RIGHT|KEYC_CURSOR },
+
+ /* Key and modifier capabilities. */
+ { TTYC_KDC2, KEYC_DC|KEYC_SHIFT },
+ { TTYC_KDC3, KEYC_DC|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KDC5, KEYC_DC|KEYC_CTRL },
+ { TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KDC7, KEYC_DC|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KIND, KEYC_DOWN|KEYC_SHIFT },
+ { TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT },
+ { TTYC_KDN3, KEYC_DOWN|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KDN5, KEYC_DOWN|KEYC_CTRL },
+ { TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KDN7, KEYC_DOWN|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KEND2, KEYC_END|KEYC_SHIFT },
+ { TTYC_KEND3, KEYC_END|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KEND5, KEYC_END|KEYC_CTRL },
+ { TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KEND7, KEYC_END|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT },
+ { TTYC_KHOM3, KEYC_HOME|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KHOM5, KEYC_HOME|KEYC_CTRL },
+ { TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KHOM7, KEYC_HOME|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KIC2, KEYC_IC|KEYC_SHIFT },
+ { TTYC_KIC3, KEYC_IC|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KIC5, KEYC_IC|KEYC_CTRL },
+ { TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KIC7, KEYC_IC|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT },
+ { TTYC_KLFT3, KEYC_LEFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL },
+ { TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KLFT7, KEYC_LEFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT },
+ { TTYC_KNXT3, KEYC_NPAGE|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL },
+ { TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KNXT7, KEYC_NPAGE|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT },
+ { TTYC_KPRV3, KEYC_PPAGE|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL },
+ { TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KPRV7, KEYC_PPAGE|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT },
+ { TTYC_KRIT3, KEYC_RIGHT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL },
+ { TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KRIT7, KEYC_RIGHT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+ { TTYC_KRI, KEYC_UP|KEYC_SHIFT },
+ { TTYC_KUP2, KEYC_UP|KEYC_SHIFT },
+ { TTYC_KUP3, KEYC_UP|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META },
+ { TTYC_KUP5, KEYC_UP|KEYC_CTRL },
+ { TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL },
+ { TTYC_KUP7, KEYC_UP|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL },
+};
+
+/* Add key to tree. */
+static void
+tty_keys_add(struct tty *tty, const char *s, key_code key)
+{
+ struct tty_key *tk;
+ size_t size;
+ const char *keystr;
+
+ keystr = key_string_lookup_key(key, 1);
+ if ((tk = tty_keys_find(tty, s, strlen(s), &size)) == NULL) {
+ log_debug("new key %s: 0x%llx (%s)", s, key, keystr);
+ tty_keys_add1(&tty->key_tree, s, key);
+ } else {
+ log_debug("replacing key %s: 0x%llx (%s)", s, key, keystr);
+ tk->key = key;
+ }
+}
+
+/* Add next node to the tree. */
+static void
+tty_keys_add1(struct tty_key **tkp, const char *s, key_code key)
+{
+ struct tty_key *tk;
+
+ /* Allocate a tree entry if there isn't one already. */
+ tk = *tkp;
+ if (tk == NULL) {
+ tk = *tkp = xcalloc(1, sizeof *tk);
+ tk->ch = *s;
+ tk->key = KEYC_UNKNOWN;
+ }
+
+ /* Find the next entry. */
+ if (*s == tk->ch) {
+ /* Move forward in string. */
+ s++;
+
+ /* If this is the end of the string, no more is necessary. */
+ if (*s == '\0') {
+ tk->key = key;
+ return;
+ }
+
+ /* Use the child tree for the next character. */
+ tkp = &tk->next;
+ } else {
+ if (*s < tk->ch)
+ tkp = &tk->left;
+ else if (*s > tk->ch)
+ tkp = &tk->right;
+ }
+
+ /* And recurse to add it. */
+ tty_keys_add1(tkp, s, key);
+}
+
+/* Initialise a key tree from the table. */
+void
+tty_keys_build(struct tty *tty)
+{
+ const struct tty_default_key_raw *tdkr;
+ const struct tty_default_key_xterm *tdkx;
+ const struct tty_default_key_code *tdkc;
+ u_int i, j;
+ const char *s;
+ struct options_entry *o;
+ struct options_array_item *a;
+ union options_value *ov;
+ char copy[16];
+ key_code key;
+
+ if (tty->key_tree != NULL)
+ tty_keys_free(tty);
+ tty->key_tree = NULL;
+
+ for (i = 0; i < nitems(tty_default_xterm_keys); i++) {
+ tdkx = &tty_default_xterm_keys[i];
+ for (j = 2; j < nitems(tty_default_xterm_modifiers); j++) {
+ strlcpy(copy, tdkx->template, sizeof copy);
+ copy[strcspn(copy, "_")] = '0' + j;
+
+ key = tdkx->key|tty_default_xterm_modifiers[j];
+ tty_keys_add(tty, copy, key);
+ }
+ }
+ for (i = 0; i < nitems(tty_default_raw_keys); i++) {
+ tdkr = &tty_default_raw_keys[i];
+
+ s = tdkr->string;
+ if (*s != '\0')
+ tty_keys_add(tty, s, tdkr->key);
+ }
+ for (i = 0; i < nitems(tty_default_code_keys); i++) {
+ tdkc = &tty_default_code_keys[i];
+
+ s = tty_term_string(tty->term, tdkc->code);
+ if (*s != '\0')
+ tty_keys_add(tty, s, tdkc->key);
+
+ }
+
+ o = options_get(global_options, "user-keys");
+ if (o != NULL) {
+ a = options_array_first(o);
+ while (a != NULL) {
+ i = options_array_item_index(a);
+ ov = options_array_item_value(a);
+ tty_keys_add(tty, ov->string, KEYC_USER + i);
+ a = options_array_next(a);
+ }
+ }
+}
+
+/* Free the entire key tree. */
+void
+tty_keys_free(struct tty *tty)
+{
+ tty_keys_free1(tty->key_tree);
+}
+
+/* Free a single key. */
+static void
+tty_keys_free1(struct tty_key *tk)
+{
+ if (tk->next != NULL)
+ tty_keys_free1(tk->next);
+ if (tk->left != NULL)
+ tty_keys_free1(tk->left);
+ if (tk->right != NULL)
+ tty_keys_free1(tk->right);
+ free(tk);
+}
+
+/* Lookup a key in the tree. */
+static struct tty_key *
+tty_keys_find(struct tty *tty, const char *buf, size_t len, size_t *size)
+{
+ *size = 0;
+ return (tty_keys_find1(tty->key_tree, buf, len, size));
+}
+
+/* Find the next node. */
+static struct tty_key *
+tty_keys_find1(struct tty_key *tk, const char *buf, size_t len, size_t *size)
+{
+ /* If no data, no match. */
+ if (len == 0)
+ return (NULL);
+
+ /* If the node is NULL, this is the end of the tree. No match. */
+ if (tk == NULL)
+ return (NULL);
+
+ /* Pick the next in the sequence. */
+ if (tk->ch == *buf) {
+ /* Move forward in the string. */
+ buf++; len--;
+ (*size)++;
+
+ /* At the end of the string, return the current node. */
+ if (len == 0 || (tk->next == NULL && tk->key != KEYC_UNKNOWN))
+ return (tk);
+
+ /* Move into the next tree for the following character. */
+ tk = tk->next;
+ } else {
+ if (*buf < tk->ch)
+ tk = tk->left;
+ else if (*buf > tk->ch)
+ tk = tk->right;
+ }
+
+ /* Move to the next in the tree. */
+ return (tty_keys_find1(tk, buf, len, size));
+}
+
+/* Look up part of the next key. */
+static int
+tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key,
+ size_t *size, int expired)
+{
+ struct client *c = tty->client;
+ struct tty_key *tk, *tk1;
+ struct utf8_data ud;
+ enum utf8_state more;
+ utf8_char uc;
+ u_int i;
+
+ log_debug("%s: next key is %zu (%.*s) (expired=%d)", c->name, len,
+ (int)len, buf, expired);
+
+ /* Is this a known key? */
+ tk = tty_keys_find(tty, buf, len, size);
+ if (tk != NULL && tk->key != KEYC_UNKNOWN) {
+ tk1 = tk;
+ do
+ log_debug("%s: keys in list: %#llx", c->name, tk1->key);
+ while ((tk1 = tk1->next) != NULL);
+ if (tk->next != NULL && !expired)
+ return (1);
+ *key = tk->key;
+ return (0);
+ }
+
+ /* Is this valid UTF-8? */
+ more = utf8_open(&ud, (u_char)*buf);
+ if (more == UTF8_MORE) {
+ *size = ud.size;
+ if (len < ud.size) {
+ if (!expired)
+ return (1);
+ return (-1);
+ }
+ for (i = 1; i < ud.size; i++)
+ more = utf8_append(&ud, (u_char)buf[i]);
+ if (more != UTF8_DONE)
+ return (-1);
+
+ if (utf8_from_data(&ud, &uc) != UTF8_DONE)
+ return (-1);
+ *key = uc;
+
+ log_debug("%s: UTF-8 key %.*s %#llx", c->name, (int)ud.size,
+ ud.data, *key);
+ return (0);
+ }
+
+ return (-1);
+}
+
+/* Process at least one key in the buffer. Return 0 if no keys present. */
+int
+tty_keys_next(struct tty *tty)
+{
+ struct client *c = tty->client;
+ struct timeval tv;
+ const char *buf;
+ size_t len, size;
+ cc_t bspace;
+ int delay, expired = 0, n;
+ key_code key;
+ struct mouse_event m = { 0 };
+ struct key_event *event;
+
+ /* Get key buffer. */
+ buf = EVBUFFER_DATA(tty->in);
+ len = EVBUFFER_LENGTH(tty->in);
+ if (len == 0)
+ return (0);
+ log_debug("%s: keys are %zu (%.*s)", c->name, len, (int)len, buf);
+
+ /* Is this a clipboard response? */
+ switch (tty_keys_clipboard(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 device attributes response? */
+ switch (tty_keys_device_attributes(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 */
+ 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 */
+ key = KEYC_MOUSE;
+ goto complete_key;
+ case -1: /* no, or not valid */
+ break;
+ case -2: /* yes, but we don't care. */
+ key = KEYC_MOUSE;
+ goto discard_key;
+ case 1: /* partial */
+ goto partial_key;
+ }
+
+ /* Is this an extended key press? */
+ switch (tty_keys_extended_key(tty, buf, len, &size, &key)) {
+ case 0: /* yes */
+ goto complete_key;
+ case -1: /* no, or not valid */
+ break;
+ case 1: /* partial */
+ goto partial_key;
+ }
+
+first_key:
+ /* Try to lookup complete key. */
+ n = tty_keys_next1(tty, buf, len, &key, &size, expired);
+ if (n == 0) /* found */
+ goto complete_key;
+ if (n == 1)
+ goto partial_key;
+
+ /*
+ * If not a complete key, look for key with an escape prefix (meta
+ * modifier).
+ */
+ if (*buf == '\033' && len > 1) {
+ /* Look for a key without the escape. */
+ n = tty_keys_next1(tty, buf + 1, len - 1, &key, &size, expired);
+ if (n == 0) { /* found */
+ if (key & KEYC_IMPLIED_META) {
+ /*
+ * We want the escape key as well as the xterm
+ * key, because the xterm sequence implicitly
+ * includes the escape (so if we see
+ * \033\033[1;3D we know it is an Escape
+ * followed by M-Left, not just M-Left).
+ */
+ key = '\033';
+ size = 1;
+ goto complete_key;
+ }
+ key |= KEYC_META;
+ size++;
+ goto complete_key;
+ }
+ if (n == 1) /* partial */
+ goto partial_key;
+ }
+
+ /*
+ * At this point, we know the key is not partial (with or without
+ * escape). So pass it through even if the timer has not expired.
+ */
+ if (*buf == '\033' && len >= 2) {
+ key = (u_char)buf[1] | KEYC_META;
+ size = 2;
+ } else {
+ key = (u_char)buf[0];
+ size = 1;
+ }
+ goto complete_key;
+
+partial_key:
+ log_debug("%s: partial key %.*s", c->name, (int)len, buf);
+
+ /* If timer is going, check for expiration. */
+ if (tty->flags & TTY_TIMER) {
+ if (evtimer_initialized(&tty->key_timer) &&
+ !evtimer_pending(&tty->key_timer, NULL)) {
+ expired = 1;
+ goto first_key;
+ }
+ return (0);
+ }
+
+ /* Get the time period. */
+ delay = options_get_number(global_options, "escape-time");
+ tv.tv_sec = delay / 1000;
+ tv.tv_usec = (delay % 1000) * 1000L;
+
+ /* Start the timer. */
+ if (event_initialized(&tty->key_timer))
+ evtimer_del(&tty->key_timer);
+ evtimer_set(&tty->key_timer, tty_keys_callback, tty);
+ evtimer_add(&tty->key_timer, &tv);
+
+ tty->flags |= TTY_TIMER;
+ return (0);
+
+complete_key:
+ log_debug("%s: complete key %.*s %#llx", c->name, (int)size, buf, key);
+
+ /*
+ * Check for backspace key using termios VERASE - the terminfo
+ * kbs entry is extremely unreliable, so cannot be safely
+ * used. termios should have a better idea.
+ */
+ bspace = tty->tio.c_cc[VERASE];
+ if (bspace != _POSIX_VDISABLE && (key & KEYC_MASK_KEY) == bspace)
+ key = (key & KEYC_MASK_MODIFIERS)|KEYC_BSPACE;
+
+ /* Remove data from buffer. */
+ evbuffer_drain(tty->in, size);
+
+ /* Remove key timer. */
+ if (event_initialized(&tty->key_timer))
+ evtimer_del(&tty->key_timer);
+ tty->flags &= ~TTY_TIMER;
+
+ /* Check for focus events. */
+ if (key == KEYC_FOCUS_OUT) {
+ c->flags &= ~CLIENT_FOCUSED;
+ window_update_focus(c->session->curw->window);
+ notify_client("client-focus-out", c);
+ } else if (key == KEYC_FOCUS_IN) {
+ c->flags |= CLIENT_FOCUSED;
+ notify_client("client-focus-in", c);
+ window_update_focus(c->session->curw->window);
+ }
+
+ /* Fire the key. */
+ if (key != KEYC_UNKNOWN) {
+ event = xmalloc(sizeof *event);
+ event->key = key;
+ memcpy(&event->m, &m, sizeof event->m);
+ if (!server_client_handle_key(c, event))
+ free(event);
+ }
+
+ return (1);
+
+discard_key:
+ log_debug("%s: discard key %.*s %#llx", c->name, (int)size, buf, key);
+
+ /* Remove data from buffer. */
+ evbuffer_drain(tty->in, size);
+
+ return (1);
+}
+
+/* Key timer callback. */
+static void
+tty_keys_callback(__unused int fd, __unused short events, void *data)
+{
+ struct tty *tty = data;
+
+ if (tty->flags & TTY_TIMER) {
+ while (tty_keys_next(tty))
+ ;
+ }
+}
+
+/*
+ * Handle extended key input. This has two forms: \033[27;m;k~ and \033[k;mu,
+ * where k is key as a number and m is a modifier. Returns 0 for success, -1
+ * for failure, 1 for partial;
+ */
+static int
+tty_keys_extended_key(struct tty *tty, const char *buf, size_t len,
+ size_t *size, key_code *key)
+{
+ struct client *c = tty->client;
+ size_t end;
+ u_int number, modifiers;
+ char tmp[64];
+ cc_t bspace;
+ key_code nkey;
+ key_code onlykey;
+
+ *size = 0;
+
+ /* First two 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);
+
+ /*
+ * Look for a terminator. Stop at either '~' or anything that isn't a
+ * number or ';'.
+ */
+ for (end = 2; end < len && end != sizeof tmp; end++) {
+ if (buf[end] == '~')
+ break;
+ if (!isdigit((u_char)buf[end]) && buf[end] != ';')
+ break;
+ }
+ if (end == len)
+ return (1);
+ if (end == sizeof tmp || (buf[end] != '~' && buf[end] != 'u'))
+ return (-1);
+
+ /* Copy to the buffer. */
+ memcpy(tmp, buf + 2, end);
+ tmp[end] = '\0';
+
+ /* Try to parse either form of key. */
+ if (buf[end] == '~') {
+ if (sscanf(tmp, "27;%u;%u", &modifiers, &number) != 2)
+ return (-1);
+ } else {
+ if (sscanf(tmp ,"%u;%u", &number, &modifiers) != 2)
+ return (-1);
+ }
+ *size = end + 1;
+
+ /* Store the key. */
+ bspace = tty->tio.c_cc[VERASE];
+ if (bspace != _POSIX_VDISABLE && number == bspace)
+ nkey = KEYC_BSPACE;
+ else
+ 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;
+ }
+
+ /*
+ * Don't allow both KEYC_CTRL and as an implied modifier. Also convert
+ * C-X into C-x and so on.
+ */
+ if (nkey & KEYC_CTRL) {
+ onlykey = (nkey & KEYC_MASK_KEY);
+ if (onlykey < 32 &&
+ onlykey != 9 &&
+ onlykey != 13 &&
+ onlykey != 27)
+ /* nothing */;
+ else if (onlykey >= 97 && onlykey <= 122)
+ onlykey -= 96;
+ else if (onlykey >= 64 && onlykey <= 95)
+ onlykey -= 64;
+ else if (onlykey == 32)
+ onlykey = 0;
+ else if (onlykey == 63)
+ onlykey = 127;
+ else
+ onlykey |= KEYC_CTRL;
+ nkey = onlykey|((nkey & KEYC_MASK_MODIFIERS) & ~KEYC_CTRL);
+ }
+
+ if (log_get_level() != 0) {
+ log_debug("%s: extended key %.*s is %llx (%s)", c->name,
+ (int)*size, buf, nkey, key_string_lookup_key(nkey, 1));
+ }
+ *key = nkey;
+ return (0);
+}
+
+/*
+ * Handle mouse key input. Returns 0 for success, -1 for failure, 1 for partial
+ * (probably a mouse sequence but need more data).
+ */
+static int
+tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size,
+ struct mouse_event *m)
+{
+ struct client *c = tty->client;
+ u_int i, x, y, b, sgr_b;
+ u_char sgr_type, ch;
+
+ /*
+ * Standard mouse sequences are \033[M followed by three characters
+ * indicating button, X and Y, all based at 32 with 1,1 top-left.
+ *
+ * UTF-8 mouse sequences are similar but the three are expressed as
+ * UTF-8 characters.
+ *
+ * SGR extended mouse sequences are \033[< followed by three numbers in
+ * decimal and separated by semicolons indicating button, X and Y. A
+ * trailing 'M' is click or scroll and trailing 'm' release. All are
+ * based at 0 with 1,1 top-left.
+ */
+
+ *size = 0;
+ x = y = b = sgr_b = 0;
+ sgr_type = ' ';
+
+ /* First two 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);
+
+ /*
+ * Third byte is M in old standard (and UTF-8 extension which we do not
+ * support), < in SGR extension.
+ */
+ if (buf[2] == 'M') {
+ /* Read the three inputs. */
+ *size = 3;
+ for (i = 0; i < 3; i++) {
+ if (len <= *size)
+ return (1);
+ ch = (u_char)buf[(*size)++];
+ if (i == 0)
+ b = ch;
+ else if (i == 1)
+ x = ch;
+ else
+ y = ch;
+ }
+ log_debug("%s: mouse input: %.*s", c->name, (int)*size, buf);
+
+ /* Check and return the mouse input. */
+ if (b < MOUSE_PARAM_BTN_OFF ||
+ x < MOUSE_PARAM_POS_OFF ||
+ y < MOUSE_PARAM_POS_OFF)
+ return (-1);
+ b -= MOUSE_PARAM_BTN_OFF;
+ x -= MOUSE_PARAM_POS_OFF;
+ y -= MOUSE_PARAM_POS_OFF;
+ } else if (buf[2] == '<') {
+ /* Read the three inputs. */
+ *size = 3;
+ while (1) {
+ if (len <= *size)
+ return (1);
+ ch = (u_char)buf[(*size)++];
+ if (ch == ';')
+ break;
+ if (ch < '0' || ch > '9')
+ return (-1);
+ sgr_b = 10 * sgr_b + (ch - '0');
+ }
+ while (1) {
+ if (len <= *size)
+ return (1);
+ ch = (u_char)buf[(*size)++];
+ if (ch == ';')
+ break;
+ if (ch < '0' || ch > '9')
+ return (-1);
+ x = 10 * x + (ch - '0');
+ }
+ while (1) {
+ if (len <= *size)
+ return (1);
+ ch = (u_char)buf[(*size)++];
+ if (ch == 'M' || ch == 'm')
+ break;
+ if (ch < '0' || ch > '9')
+ return (-1);
+ y = 10 * y + (ch - '0');
+ }
+ log_debug("%s: mouse input (SGR): %.*s", c->name, (int)*size,
+ buf);
+
+ /* Check and return the mouse input. */
+ if (x < 1 || y < 1)
+ return (-1);
+ x--;
+ y--;
+ b = sgr_b;
+
+ /* Type is M for press, m for release. */
+ sgr_type = ch;
+ if (sgr_type == 'm')
+ b = 3;
+
+ /*
+ * Some terminals (like PuTTY 0.63) mistakenly send
+ * button-release events for scroll-wheel button-press event.
+ * Discard it before it reaches any program running inside
+ * tmux.
+ */
+ if (sgr_type == 'm' && MOUSE_WHEEL(sgr_b))
+ return (-2);
+ } else
+ return (-1);
+
+ /* Fill mouse event. */
+ m->lx = tty->mouse_last_x;
+ m->x = x;
+ m->ly = tty->mouse_last_y;
+ m->y = y;
+ m->lb = tty->mouse_last_b;
+ m->b = b;
+ m->sgr_type = sgr_type;
+ m->sgr_b = sgr_b;
+
+ /* Update last mouse state. */
+ tty->mouse_last_x = x;
+ tty->mouse_last_y = y;
+ tty->mouse_last_b = b;
+
+ return (0);
+}
+
+/*
+ * Handle OSC 52 clipboard input. Returns 0 for success, -1 for failure, 1 for
+ * partial.
+ */
+static int
+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;
+ char *copy, *out;
+ int outlen;
+ u_int i;
+
+ *size = 0;
+
+ /* First five bytes are always \033]52;. */
+ if (buf[0] != '\033')
+ return (-1);
+ if (len == 1)
+ return (1);
+ if (buf[1] != ']')
+ return (-1);
+ if (len == 2)
+ return (1);
+ if (buf[2] != '5')
+ return (-1);
+ if (len == 3)
+ return (1);
+ if (buf[3] != '2')
+ return (-1);
+ if (len == 4)
+ return (1);
+ if (buf[4] != ';')
+ return (-1);
+ if (len == 5)
+ return (1);
+
+ /* Find the terminator if any. */
+ for (end = 5; end < len; end++) {
+ if (buf[end] == '\007') {
+ terminator = 1;
+ break;
+ }
+ if (end > 5 && buf[end - 1] == '\033' && buf[end] == '\\') {
+ terminator = 2;
+ break;
+ }
+ }
+ if (end == len)
+ return (1);
+ *size = end + terminator;
+
+ /* Skip the initial part. */
+ buf += 5;
+ end -= 5;
+
+ /* Adjust end so that it points to the start of the terminator. */
+ end -= terminator - 1;
+
+ /* Get the second argument. */
+ while (end != 0 && *buf != ';') {
+ buf++;
+ end--;
+ }
+ if (end == 0 || end == 1)
+ return (0);
+ buf++;
+ end--;
+
+ /* If we did not request this, ignore it. */
+ if (~tty->flags & TTY_OSC52QUERY)
+ return (0);
+ tty->flags &= ~TTY_OSC52QUERY;
+ evtimer_del(&tty->clipboard_timer);
+
+ /* It has to be a string so copy it. */
+ copy = xmalloc(end + 1);
+ memcpy(copy, buf, end);
+ copy[end] = '\0';
+
+ /* Convert from base64. */
+ needed = (end / 4) * 3;
+ out = xmalloc(needed);
+ if ((outlen = b64_pton(copy, out, len)) == -1) {
+ free(out);
+ free(copy);
+ return (0);
+ }
+ free(copy);
+
+ /* Create a new paste buffer and forward to panes. */
+ log_debug("%s: %.*s", __func__, outlen, out);
+ if (c->flags & CLIENT_CLIPBOARDBUFFER) {
+ paste_add(NULL, out, outlen);
+ c->flags &= ~CLIENT_CLIPBOARDBUFFER;
+ }
+ for (i = 0; i < c->clipboard_npanes; i++) {
+ wp = window_pane_find_by_id(c->clipboard_panes[i]);
+ if (wp != NULL)
+ input_reply_clipboard(wp->event, out, outlen, "\033\\");
+ }
+ free(c->clipboard_panes);
+ c->clipboard_panes = NULL;
+ c->clipboard_npanes = 0;
+
+ return (0);
+}
+
+/*
+ * Handle secondary device attributes input. Returns 0 for success, -1 for
+ * failure, 1 for partial.
+ */
+static int
+tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len,
+ size_t *size)
+{
+ struct client *c = tty->client;
+ u_int i, n = 0;
+ char tmp[64], *endptr, p[32] = { 0 }, *cp, *next;
+
+ *size = 0;
+ if (tty->flags & TTY_HAVEDA)
+ return (-1);
+
+ /*
+ * First three bytes are always \033[>. Some older Terminal.app
+ * versions respond as for DA (\033[?) so accept and ignore that.
+ */
+ if (buf[0] != '\033')
+ return (-1);
+ if (len == 1)
+ return (1);
+ if (buf[1] != '[')
+ return (-1);
+ if (len == 2)
+ return (1);
+ if (buf[2] != '>' && buf[2] != '?')
+ return (-1);
+ if (len == 3)
+ return (1);
+
+ /* Copy the rest up to a 'c'. */
+ for (i = 0; i < (sizeof tmp) - 1; i++) {
+ if (3 + i == len)
+ return (1);
+ if (buf[3 + i] == 'c')
+ break;
+ tmp[i] = buf[3 + i];
+ }
+ if (i == (sizeof tmp) - 1)
+ 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++;
+ }
+
+ /* Add terminal features. */
+ switch (p[0]) {
+ case 41: /* VT420 */
+ tty_add_features(&c->term_features, "margins,rectfill", ",");
+ break;
+ case 'M': /* mintty */
+ tty_default_features(&c->term_features, "mintty", 0);
+ break;
+ case 'T': /* tmux */
+ tty_default_features(&c->term_features, "tmux", 0);
+ break;
+ case 'U': /* rxvt-unicode */
+ tty_default_features(&c->term_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;
+
+ return (0);
+}
+
+/*
+ * Handle extended device attributes input. Returns 0 for success, -1 for
+ * failure, 1 for partial.
+ */
+static int
+tty_keys_extended_device_attributes(struct tty *tty, const char *buf,
+ size_t len, size_t *size)
+{
+ struct client *c = tty->client;
+ u_int i;
+ char tmp[128];
+
+ *size = 0;
+ if (tty->flags & TTY_HAVEXDA)
+ return (-1);
+
+ /* First four bytes are always \033P>|. */
+ if (buf[0] != '\033')
+ return (-1);
+ if (len == 1)
+ return (1);
+ if (buf[1] != 'P')
+ return (-1);
+ if (len == 2)
+ return (1);
+ if (buf[2] != '>')
+ return (-1);
+ if (len == 3)
+ return (1);
+ if (buf[3] != '|')
+ return (-1);
+ if (len == 4)
+ return (1);
+
+ /* Copy the rest up to a '\033\\'. */
+ for (i = 0; i < (sizeof tmp) - 1; i++) {
+ if (4 + i == len)
+ return (1);
+ if (buf[4 + i - 1] == '\033' && buf[4 + i] == '\\')
+ break;
+ tmp[i] = buf[4 + i];
+ }
+ if (i == (sizeof tmp) - 1)
+ return (-1);
+ tmp[i - 1] = '\0';
+ *size = 5 + i;
+
+ /* Add terminal features. */
+ if (strncmp(tmp, "iTerm2 ", 7) == 0)
+ tty_default_features(&c->term_features, "iTerm2", 0);
+ else if (strncmp(tmp, "tmux ", 5) == 0)
+ tty_default_features(&c->term_features, "tmux", 0);
+ else if (strncmp(tmp, "XTerm(", 6) == 0)
+ tty_default_features(&c->term_features, "XTerm", 0);
+ else if (strncmp(tmp, "mintty ", 7) == 0)
+ tty_default_features(&c->term_features, "mintty", 0);
+ log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf);
+
+ free(c->term_type);
+ c->term_type = xstrdup(tmp);
+
+ tty_update_features(tty);
+ tty->flags |= TTY_HAVEXDA;
+
+ return (0);
+}
diff --git a/tty-term.c b/tty-term.c
new file mode 100644
index 0000000..fdf0c4f
--- /dev/null
+++ b/tty-term.c
@@ -0,0 +1,844 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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>
+
+#if defined(HAVE_CURSES_H)
+#include <curses.h>
+#elif defined(HAVE_NCURSES_H)
+#include <ncurses.h>
+#endif
+#include <fnmatch.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+
+#include "tmux.h"
+
+static char *tty_term_strip(const char *);
+
+struct tty_terms tty_terms = LIST_HEAD_INITIALIZER(tty_terms);
+
+enum tty_code_type {
+ TTYCODE_NONE = 0,
+ TTYCODE_STRING,
+ TTYCODE_NUMBER,
+ TTYCODE_FLAG,
+};
+
+struct tty_code {
+ enum tty_code_type type;
+ union {
+ char *string;
+ int number;
+ int flag;
+ } value;
+};
+
+struct tty_term_code_entry {
+ enum tty_code_type type;
+ const char *name;
+};
+
+static const struct tty_term_code_entry tty_term_codes[] = {
+ [TTYC_ACSC] = { TTYCODE_STRING, "acsc" },
+ [TTYC_AM] = { TTYCODE_FLAG, "am" },
+ [TTYC_AX] = { TTYCODE_FLAG, "AX" },
+ [TTYC_BCE] = { TTYCODE_FLAG, "bce" },
+ [TTYC_BEL] = { TTYCODE_STRING, "bel" },
+ [TTYC_BIDI] = { TTYCODE_STRING, "Bidi" },
+ [TTYC_BLINK] = { TTYCODE_STRING, "blink" },
+ [TTYC_BOLD] = { TTYCODE_STRING, "bold" },
+ [TTYC_CIVIS] = { TTYCODE_STRING, "civis" },
+ [TTYC_CLEAR] = { TTYCODE_STRING, "clear" },
+ [TTYC_CLMG] = { TTYCODE_STRING, "Clmg" },
+ [TTYC_CMG] = { TTYCODE_STRING, "Cmg" },
+ [TTYC_CNORM] = { TTYCODE_STRING, "cnorm" },
+ [TTYC_COLORS] = { TTYCODE_NUMBER, "colors" },
+ [TTYC_CR] = { TTYCODE_STRING, "Cr" },
+ [TTYC_CSR] = { TTYCODE_STRING, "csr" },
+ [TTYC_CS] = { TTYCODE_STRING, "Cs" },
+ [TTYC_CUB1] = { TTYCODE_STRING, "cub1" },
+ [TTYC_CUB] = { TTYCODE_STRING, "cub" },
+ [TTYC_CUD1] = { TTYCODE_STRING, "cud1" },
+ [TTYC_CUD] = { TTYCODE_STRING, "cud" },
+ [TTYC_CUF1] = { TTYCODE_STRING, "cuf1" },
+ [TTYC_CUF] = { TTYCODE_STRING, "cuf" },
+ [TTYC_CUP] = { TTYCODE_STRING, "cup" },
+ [TTYC_CUU1] = { TTYCODE_STRING, "cuu1" },
+ [TTYC_CUU] = { TTYCODE_STRING, "cuu" },
+ [TTYC_CVVIS] = { TTYCODE_STRING, "cvvis" },
+ [TTYC_DCH1] = { TTYCODE_STRING, "dch1" },
+ [TTYC_DCH] = { TTYCODE_STRING, "dch" },
+ [TTYC_DIM] = { TTYCODE_STRING, "dim" },
+ [TTYC_DL1] = { TTYCODE_STRING, "dl1" },
+ [TTYC_DL] = { TTYCODE_STRING, "dl" },
+ [TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" },
+ [TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" },
+ [TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" },
+ [TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" },
+ [TTYC_E3] = { TTYCODE_STRING, "E3" },
+ [TTYC_ECH] = { TTYCODE_STRING, "ech" },
+ [TTYC_ED] = { TTYCODE_STRING, "ed" },
+ [TTYC_EL1] = { TTYCODE_STRING, "el1" },
+ [TTYC_EL] = { TTYCODE_STRING, "el" },
+ [TTYC_ENACS] = { TTYCODE_STRING, "enacs" },
+ [TTYC_ENBP] = { TTYCODE_STRING, "Enbp" },
+ [TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" },
+ [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" },
+ [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" },
+ [TTYC_FSL] = { TTYCODE_STRING, "fsl" },
+ [TTYC_HOME] = { TTYCODE_STRING, "home" },
+ [TTYC_HPA] = { TTYCODE_STRING, "hpa" },
+ [TTYC_ICH1] = { TTYCODE_STRING, "ich1" },
+ [TTYC_ICH] = { TTYCODE_STRING, "ich" },
+ [TTYC_IL1] = { TTYCODE_STRING, "il1" },
+ [TTYC_IL] = { TTYCODE_STRING, "il" },
+ [TTYC_INDN] = { TTYCODE_STRING, "indn" },
+ [TTYC_INVIS] = { TTYCODE_STRING, "invis" },
+ [TTYC_KCBT] = { TTYCODE_STRING, "kcbt" },
+ [TTYC_KCUB1] = { TTYCODE_STRING, "kcub1" },
+ [TTYC_KCUD1] = { TTYCODE_STRING, "kcud1" },
+ [TTYC_KCUF1] = { TTYCODE_STRING, "kcuf1" },
+ [TTYC_KCUU1] = { TTYCODE_STRING, "kcuu1" },
+ [TTYC_KDC2] = { TTYCODE_STRING, "kDC" },
+ [TTYC_KDC3] = { TTYCODE_STRING, "kDC3" },
+ [TTYC_KDC4] = { TTYCODE_STRING, "kDC4" },
+ [TTYC_KDC5] = { TTYCODE_STRING, "kDC5" },
+ [TTYC_KDC6] = { TTYCODE_STRING, "kDC6" },
+ [TTYC_KDC7] = { TTYCODE_STRING, "kDC7" },
+ [TTYC_KDCH1] = { TTYCODE_STRING, "kdch1" },
+ [TTYC_KDN2] = { TTYCODE_STRING, "kDN" }, /* not kDN2 */
+ [TTYC_KDN3] = { TTYCODE_STRING, "kDN3" },
+ [TTYC_KDN4] = { TTYCODE_STRING, "kDN4" },
+ [TTYC_KDN5] = { TTYCODE_STRING, "kDN5" },
+ [TTYC_KDN6] = { TTYCODE_STRING, "kDN6" },
+ [TTYC_KDN7] = { TTYCODE_STRING, "kDN7" },
+ [TTYC_KEND2] = { TTYCODE_STRING, "kEND" },
+ [TTYC_KEND3] = { TTYCODE_STRING, "kEND3" },
+ [TTYC_KEND4] = { TTYCODE_STRING, "kEND4" },
+ [TTYC_KEND5] = { TTYCODE_STRING, "kEND5" },
+ [TTYC_KEND6] = { TTYCODE_STRING, "kEND6" },
+ [TTYC_KEND7] = { TTYCODE_STRING, "kEND7" },
+ [TTYC_KEND] = { TTYCODE_STRING, "kend" },
+ [TTYC_KF10] = { TTYCODE_STRING, "kf10" },
+ [TTYC_KF11] = { TTYCODE_STRING, "kf11" },
+ [TTYC_KF12] = { TTYCODE_STRING, "kf12" },
+ [TTYC_KF13] = { TTYCODE_STRING, "kf13" },
+ [TTYC_KF14] = { TTYCODE_STRING, "kf14" },
+ [TTYC_KF15] = { TTYCODE_STRING, "kf15" },
+ [TTYC_KF16] = { TTYCODE_STRING, "kf16" },
+ [TTYC_KF17] = { TTYCODE_STRING, "kf17" },
+ [TTYC_KF18] = { TTYCODE_STRING, "kf18" },
+ [TTYC_KF19] = { TTYCODE_STRING, "kf19" },
+ [TTYC_KF1] = { TTYCODE_STRING, "kf1" },
+ [TTYC_KF20] = { TTYCODE_STRING, "kf20" },
+ [TTYC_KF21] = { TTYCODE_STRING, "kf21" },
+ [TTYC_KF22] = { TTYCODE_STRING, "kf22" },
+ [TTYC_KF23] = { TTYCODE_STRING, "kf23" },
+ [TTYC_KF24] = { TTYCODE_STRING, "kf24" },
+ [TTYC_KF25] = { TTYCODE_STRING, "kf25" },
+ [TTYC_KF26] = { TTYCODE_STRING, "kf26" },
+ [TTYC_KF27] = { TTYCODE_STRING, "kf27" },
+ [TTYC_KF28] = { TTYCODE_STRING, "kf28" },
+ [TTYC_KF29] = { TTYCODE_STRING, "kf29" },
+ [TTYC_KF2] = { TTYCODE_STRING, "kf2" },
+ [TTYC_KF30] = { TTYCODE_STRING, "kf30" },
+ [TTYC_KF31] = { TTYCODE_STRING, "kf31" },
+ [TTYC_KF32] = { TTYCODE_STRING, "kf32" },
+ [TTYC_KF33] = { TTYCODE_STRING, "kf33" },
+ [TTYC_KF34] = { TTYCODE_STRING, "kf34" },
+ [TTYC_KF35] = { TTYCODE_STRING, "kf35" },
+ [TTYC_KF36] = { TTYCODE_STRING, "kf36" },
+ [TTYC_KF37] = { TTYCODE_STRING, "kf37" },
+ [TTYC_KF38] = { TTYCODE_STRING, "kf38" },
+ [TTYC_KF39] = { TTYCODE_STRING, "kf39" },
+ [TTYC_KF3] = { TTYCODE_STRING, "kf3" },
+ [TTYC_KF40] = { TTYCODE_STRING, "kf40" },
+ [TTYC_KF41] = { TTYCODE_STRING, "kf41" },
+ [TTYC_KF42] = { TTYCODE_STRING, "kf42" },
+ [TTYC_KF43] = { TTYCODE_STRING, "kf43" },
+ [TTYC_KF44] = { TTYCODE_STRING, "kf44" },
+ [TTYC_KF45] = { TTYCODE_STRING, "kf45" },
+ [TTYC_KF46] = { TTYCODE_STRING, "kf46" },
+ [TTYC_KF47] = { TTYCODE_STRING, "kf47" },
+ [TTYC_KF48] = { TTYCODE_STRING, "kf48" },
+ [TTYC_KF49] = { TTYCODE_STRING, "kf49" },
+ [TTYC_KF4] = { TTYCODE_STRING, "kf4" },
+ [TTYC_KF50] = { TTYCODE_STRING, "kf50" },
+ [TTYC_KF51] = { TTYCODE_STRING, "kf51" },
+ [TTYC_KF52] = { TTYCODE_STRING, "kf52" },
+ [TTYC_KF53] = { TTYCODE_STRING, "kf53" },
+ [TTYC_KF54] = { TTYCODE_STRING, "kf54" },
+ [TTYC_KF55] = { TTYCODE_STRING, "kf55" },
+ [TTYC_KF56] = { TTYCODE_STRING, "kf56" },
+ [TTYC_KF57] = { TTYCODE_STRING, "kf57" },
+ [TTYC_KF58] = { TTYCODE_STRING, "kf58" },
+ [TTYC_KF59] = { TTYCODE_STRING, "kf59" },
+ [TTYC_KF5] = { TTYCODE_STRING, "kf5" },
+ [TTYC_KF60] = { TTYCODE_STRING, "kf60" },
+ [TTYC_KF61] = { TTYCODE_STRING, "kf61" },
+ [TTYC_KF62] = { TTYCODE_STRING, "kf62" },
+ [TTYC_KF63] = { TTYCODE_STRING, "kf63" },
+ [TTYC_KF6] = { TTYCODE_STRING, "kf6" },
+ [TTYC_KF7] = { TTYCODE_STRING, "kf7" },
+ [TTYC_KF8] = { TTYCODE_STRING, "kf8" },
+ [TTYC_KF9] = { TTYCODE_STRING, "kf9" },
+ [TTYC_KHOM2] = { TTYCODE_STRING, "kHOM" },
+ [TTYC_KHOM3] = { TTYCODE_STRING, "kHOM3" },
+ [TTYC_KHOM4] = { TTYCODE_STRING, "kHOM4" },
+ [TTYC_KHOM5] = { TTYCODE_STRING, "kHOM5" },
+ [TTYC_KHOM6] = { TTYCODE_STRING, "kHOM6" },
+ [TTYC_KHOM7] = { TTYCODE_STRING, "kHOM7" },
+ [TTYC_KHOME] = { TTYCODE_STRING, "khome" },
+ [TTYC_KIC2] = { TTYCODE_STRING, "kIC" },
+ [TTYC_KIC3] = { TTYCODE_STRING, "kIC3" },
+ [TTYC_KIC4] = { TTYCODE_STRING, "kIC4" },
+ [TTYC_KIC5] = { TTYCODE_STRING, "kIC5" },
+ [TTYC_KIC6] = { TTYCODE_STRING, "kIC6" },
+ [TTYC_KIC7] = { TTYCODE_STRING, "kIC7" },
+ [TTYC_KICH1] = { TTYCODE_STRING, "kich1" },
+ [TTYC_KIND] = { TTYCODE_STRING, "kind" },
+ [TTYC_KLFT2] = { TTYCODE_STRING, "kLFT" },
+ [TTYC_KLFT3] = { TTYCODE_STRING, "kLFT3" },
+ [TTYC_KLFT4] = { TTYCODE_STRING, "kLFT4" },
+ [TTYC_KLFT5] = { TTYCODE_STRING, "kLFT5" },
+ [TTYC_KLFT6] = { TTYCODE_STRING, "kLFT6" },
+ [TTYC_KLFT7] = { TTYCODE_STRING, "kLFT7" },
+ [TTYC_KMOUS] = { TTYCODE_STRING, "kmous" },
+ [TTYC_KNP] = { TTYCODE_STRING, "knp" },
+ [TTYC_KNXT2] = { TTYCODE_STRING, "kNXT" },
+ [TTYC_KNXT3] = { TTYCODE_STRING, "kNXT3" },
+ [TTYC_KNXT4] = { TTYCODE_STRING, "kNXT4" },
+ [TTYC_KNXT5] = { TTYCODE_STRING, "kNXT5" },
+ [TTYC_KNXT6] = { TTYCODE_STRING, "kNXT6" },
+ [TTYC_KNXT7] = { TTYCODE_STRING, "kNXT7" },
+ [TTYC_KPP] = { TTYCODE_STRING, "kpp" },
+ [TTYC_KPRV2] = { TTYCODE_STRING, "kPRV" },
+ [TTYC_KPRV3] = { TTYCODE_STRING, "kPRV3" },
+ [TTYC_KPRV4] = { TTYCODE_STRING, "kPRV4" },
+ [TTYC_KPRV5] = { TTYCODE_STRING, "kPRV5" },
+ [TTYC_KPRV6] = { TTYCODE_STRING, "kPRV6" },
+ [TTYC_KPRV7] = { TTYCODE_STRING, "kPRV7" },
+ [TTYC_KRIT2] = { TTYCODE_STRING, "kRIT" },
+ [TTYC_KRIT3] = { TTYCODE_STRING, "kRIT3" },
+ [TTYC_KRIT4] = { TTYCODE_STRING, "kRIT4" },
+ [TTYC_KRIT5] = { TTYCODE_STRING, "kRIT5" },
+ [TTYC_KRIT6] = { TTYCODE_STRING, "kRIT6" },
+ [TTYC_KRIT7] = { TTYCODE_STRING, "kRIT7" },
+ [TTYC_KRI] = { TTYCODE_STRING, "kri" },
+ [TTYC_KUP2] = { TTYCODE_STRING, "kUP" }, /* not kUP2 */
+ [TTYC_KUP3] = { TTYCODE_STRING, "kUP3" },
+ [TTYC_KUP4] = { TTYCODE_STRING, "kUP4" },
+ [TTYC_KUP5] = { TTYCODE_STRING, "kUP5" },
+ [TTYC_KUP6] = { TTYCODE_STRING, "kUP6" },
+ [TTYC_KUP7] = { TTYCODE_STRING, "kUP7" },
+ [TTYC_MS] = { TTYCODE_STRING, "Ms" },
+ [TTYC_OL] = { TTYCODE_STRING, "ol" },
+ [TTYC_OP] = { TTYCODE_STRING, "op" },
+ [TTYC_RECT] = { TTYCODE_STRING, "Rect" },
+ [TTYC_REV] = { TTYCODE_STRING, "rev" },
+ [TTYC_RGB] = { TTYCODE_FLAG, "RGB" },
+ [TTYC_RIN] = { TTYCODE_STRING, "rin" },
+ [TTYC_RI] = { TTYCODE_STRING, "ri" },
+ [TTYC_RMACS] = { TTYCODE_STRING, "rmacs" },
+ [TTYC_RMCUP] = { TTYCODE_STRING, "rmcup" },
+ [TTYC_RMKX] = { TTYCODE_STRING, "rmkx" },
+ [TTYC_SETAB] = { TTYCODE_STRING, "setab" },
+ [TTYC_SETAF] = { TTYCODE_STRING, "setaf" },
+ [TTYC_SETAL] = { TTYCODE_STRING, "setal" },
+ [TTYC_SETRGBB] = { TTYCODE_STRING, "setrgbb" },
+ [TTYC_SETRGBF] = { TTYCODE_STRING, "setrgbf" },
+ [TTYC_SETULC] = { TTYCODE_STRING, "Setulc" },
+ [TTYC_SE] = { TTYCODE_STRING, "Se" },
+ [TTYC_SGR0] = { TTYCODE_STRING, "sgr0" },
+ [TTYC_SITM] = { TTYCODE_STRING, "sitm" },
+ [TTYC_SMACS] = { TTYCODE_STRING, "smacs" },
+ [TTYC_SMCUP] = { TTYCODE_STRING, "smcup" },
+ [TTYC_SMKX] = { TTYCODE_STRING, "smkx" },
+ [TTYC_SMOL] = { TTYCODE_STRING, "Smol" },
+ [TTYC_SMSO] = { TTYCODE_STRING, "smso" },
+ [TTYC_SMULX] = { TTYCODE_STRING, "Smulx" },
+ [TTYC_SMUL] = { TTYCODE_STRING, "smul" },
+ [TTYC_SMXX] = { TTYCODE_STRING, "smxx" },
+ [TTYC_SS] = { TTYCODE_STRING, "Ss" },
+ [TTYC_SWD] = { TTYCODE_STRING, "Swd" },
+ [TTYC_SYNC] = { TTYCODE_STRING, "Sync" },
+ [TTYC_TC] = { TTYCODE_FLAG, "Tc" },
+ [TTYC_TSL] = { TTYCODE_STRING, "tsl" },
+ [TTYC_U8] = { TTYCODE_NUMBER, "U8" },
+ [TTYC_VPA] = { TTYCODE_STRING, "vpa" },
+ [TTYC_XT] = { TTYCODE_FLAG, "XT" }
+};
+
+u_int
+tty_term_ncodes(void)
+{
+ return (nitems(tty_term_codes));
+}
+
+static char *
+tty_term_strip(const char *s)
+{
+ const char *ptr;
+ static char buf[8192];
+ size_t len;
+
+ /* Ignore strings with no padding. */
+ if (strchr(s, '$') == NULL)
+ return (xstrdup(s));
+
+ len = 0;
+ for (ptr = s; *ptr != '\0'; ptr++) {
+ if (*ptr == '$' && *(ptr + 1) == '<') {
+ while (*ptr != '\0' && *ptr != '>')
+ ptr++;
+ if (*ptr == '>')
+ ptr++;
+ if (*ptr == '\0')
+ break;
+ }
+
+ buf[len++] = *ptr;
+ if (len == (sizeof buf) - 1)
+ break;
+ }
+ buf[len] = '\0';
+
+ return (xstrdup(buf));
+}
+
+static char *
+tty_term_override_next(const char *s, size_t *offset)
+{
+ static char value[8192];
+ size_t n = 0, at = *offset;
+
+ if (s[at] == '\0')
+ return (NULL);
+
+ while (s[at] != '\0') {
+ if (s[at] == ':') {
+ if (s[at + 1] == ':') {
+ value[n++] = ':';
+ at += 2;
+ } else
+ break;
+ } else {
+ value[n++] = s[at];
+ at++;
+ }
+ if (n == (sizeof value) - 1)
+ return (NULL);
+ }
+ if (s[at] != '\0')
+ *offset = at + 1;
+ else
+ *offset = at;
+ value[n] = '\0';
+ return (value);
+}
+
+void
+tty_term_apply(struct tty_term *term, const char *capabilities, int quiet)
+{
+ const struct tty_term_code_entry *ent;
+ struct tty_code *code;
+ size_t offset = 0;
+ char *cp, *value, *s;
+ const char *errstr, *name = term->name;
+ u_int i;
+ int n, remove;
+
+ while ((s = tty_term_override_next(capabilities, &offset)) != NULL) {
+ if (*s == '\0')
+ continue;
+ value = NULL;
+
+ remove = 0;
+ if ((cp = strchr(s, '=')) != NULL) {
+ *cp++ = '\0';
+ value = xstrdup(cp);
+ if (strunvis(value, cp) == -1) {
+ free(value);
+ value = xstrdup(cp);
+ }
+ } else if (s[strlen(s) - 1] == '@') {
+ s[strlen(s) - 1] = '\0';
+ remove = 1;
+ } else
+ value = xstrdup("");
+
+ if (!quiet) {
+ if (remove)
+ log_debug("%s override: %s@", name, s);
+ else if (*value == '\0')
+ log_debug("%s override: %s", name, s);
+ else
+ log_debug("%s override: %s=%s", name, s, value);
+ }
+
+ for (i = 0; i < tty_term_ncodes(); i++) {
+ ent = &tty_term_codes[i];
+ if (strcmp(s, ent->name) != 0)
+ continue;
+ code = &term->codes[i];
+
+ if (remove) {
+ code->type = TTYCODE_NONE;
+ continue;
+ }
+ switch (ent->type) {
+ case TTYCODE_NONE:
+ break;
+ case TTYCODE_STRING:
+ if (code->type == TTYCODE_STRING)
+ free(code->value.string);
+ code->value.string = xstrdup(value);
+ code->type = ent->type;
+ break;
+ case TTYCODE_NUMBER:
+ n = strtonum(value, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ break;
+ code->value.number = n;
+ code->type = ent->type;
+ break;
+ case TTYCODE_FLAG:
+ code->value.flag = 1;
+ code->type = ent->type;
+ break;
+ }
+ }
+
+ free(value);
+ }
+}
+
+void
+tty_term_apply_overrides(struct tty_term *term)
+{
+ struct options_entry *o;
+ struct options_array_item *a;
+ union options_value *ov;
+ const char *s, *acs;
+ size_t offset;
+ char *first;
+
+ /* Update capabilities from the option. */
+ o = options_get_only(global_options, "terminal-overrides");
+ a = options_array_first(o);
+ while (a != NULL) {
+ ov = options_array_item_value(a);
+ s = ov->string;
+
+ offset = 0;
+ first = tty_term_override_next(s, &offset);
+ if (first != NULL && fnmatch(first, term->name, 0) == 0)
+ tty_term_apply(term, s + offset, 0);
+ a = options_array_next(a);
+ }
+
+ /* Update the RGB flag if the terminal has RGB colours. */
+ if (tty_term_has(term, TTYC_SETRGBF) &&
+ tty_term_has(term, TTYC_SETRGBB))
+ term->flags |= TERM_RGBCOLOURS;
+ else
+ term->flags &= ~TERM_RGBCOLOURS;
+ log_debug("RGBCOLOURS flag is %d", !!(term->flags & TERM_RGBCOLOURS));
+
+ /*
+ * Set or clear the DECSLRM flag if the terminal has the margin
+ * capabilities.
+ */
+ if (tty_term_has(term, TTYC_CMG) && tty_term_has(term, TTYC_CLMG))
+ term->flags |= TERM_DECSLRM;
+ else
+ term->flags &= ~TERM_DECSLRM;
+ log_debug("DECSLRM flag is %d", !!(term->flags & TERM_DECSLRM));
+
+ /*
+ * Set or clear the DECFRA flag if the terminal has the rectangle
+ * capability.
+ */
+ if (tty_term_has(term, TTYC_RECT))
+ term->flags |= TERM_DECFRA;
+ else
+ term->flags &= ~TERM_DECFRA;
+ log_debug("DECFRA flag is %d", !!(term->flags & TERM_DECFRA));
+
+ /*
+ * Terminals without am (auto right margin) wrap at at $COLUMNS - 1
+ * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1).
+ *
+ * Terminals without xenl (eat newline glitch) ignore a newline beyond
+ * the right edge of the terminal, but tmux doesn't care about this -
+ * it always uses absolute only moves the cursor with a newline when
+ * also sending a linefeed.
+ *
+ * This is irritating, most notably because it is painful to write to
+ * the very bottom-right of the screen without scrolling.
+ *
+ * Flag the terminal here and apply some workarounds in other places to
+ * do the best possible.
+ */
+ if (!tty_term_flag(term, TTYC_AM))
+ term->flags |= TERM_NOAM;
+ else
+ term->flags &= ~TERM_NOAM;
+ log_debug("NOAM flag is %d", !!(term->flags & TERM_NOAM));
+
+ /* Generate ACS table. If none is present, use nearest ASCII. */
+ memset(term->acs, 0, sizeof term->acs);
+ if (tty_term_has(term, TTYC_ACSC))
+ acs = tty_term_string(term, TTYC_ACSC);
+ else
+ acs = "a#j+k+l+m+n+o-p-q-r-s-t+u+v+w+x|y<z>~.";
+ for (; acs[0] != '\0' && acs[1] != '\0'; acs += 2)
+ term->acs[(u_char) acs[0]][0] = acs[1];
+}
+
+struct tty_term *
+tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps,
+ int *feat, char **cause)
+{
+ struct tty_term *term;
+ const struct tty_term_code_entry *ent;
+ struct tty_code *code;
+ struct options_entry *o;
+ struct options_array_item *a;
+ union options_value *ov;
+ u_int i, j;
+ const char *s, *value;
+ size_t offset, namelen;
+ char *first;
+
+ log_debug("adding term %s", name);
+
+ term = xcalloc(1, sizeof *term);
+ term->tty = tty;
+ term->name = xstrdup(name);
+ term->codes = xcalloc(tty_term_ncodes(), sizeof *term->codes);
+ LIST_INSERT_HEAD(&tty_terms, term, entry);
+
+ /* Fill in codes. */
+ for (i = 0; i < ncaps; i++) {
+ namelen = strcspn(caps[i], "=");
+ if (namelen == 0)
+ continue;
+ value = caps[i] + namelen + 1;
+
+ for (j = 0; j < tty_term_ncodes(); j++) {
+ ent = &tty_term_codes[j];
+ if (strncmp(ent->name, caps[i], namelen) != 0)
+ continue;
+ if (ent->name[namelen] != '\0')
+ continue;
+
+ code = &term->codes[j];
+ code->type = TTYCODE_NONE;
+ switch (ent->type) {
+ case TTYCODE_NONE:
+ break;
+ case TTYCODE_STRING:
+ code->type = TTYCODE_STRING;
+ code->value.string = tty_term_strip(value);
+ break;
+ case TTYCODE_NUMBER:
+ code->type = TTYCODE_NUMBER;
+ code->value.number = atoi(value);
+ break;
+ case TTYCODE_FLAG:
+ code->type = TTYCODE_FLAG;
+ code->value.flag = (*value == '1');
+ break;
+ }
+ }
+ }
+
+ /* Apply terminal features. */
+ o = options_get_only(global_options, "terminal-features");
+ a = options_array_first(o);
+ while (a != NULL) {
+ ov = options_array_item_value(a);
+ s = ov->string;
+
+ offset = 0;
+ first = tty_term_override_next(s, &offset);
+ if (first != NULL && fnmatch(first, term->name, 0) == 0)
+ tty_add_features(feat, s + offset, ":");
+ a = options_array_next(a);
+ }
+
+ /* Delete curses data. */
+#if !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR > 5 || \
+ (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 6)
+ del_curterm(cur_term);
+#endif
+
+ /* Apply overrides so any capabilities used for features are changed. */
+ tty_term_apply_overrides(term);
+
+ /* These are always required. */
+ if (!tty_term_has(term, TTYC_CLEAR)) {
+ xasprintf(cause, "terminal does not support clear");
+ goto error;
+ }
+ if (!tty_term_has(term, TTYC_CUP)) {
+ xasprintf(cause, "terminal does not support cup");
+ goto error;
+ }
+
+ /*
+ * If TERM has XT or clear starts with CSI then it is safe to assume
+ * the terminal is derived from the VT100. This controls whether device
+ * attributes requests are sent to get more information.
+ *
+ * This is a bit of a hack but there aren't that many alternatives.
+ * Worst case tmux will just fall back to using whatever terminfo(5)
+ * says without trying to correct anything that is missing.
+ *
+ * Also add few features that VT100-like terminals should either
+ * support or safely ignore.
+ */
+ s = tty_term_string(term, TTYC_CLEAR);
+ if (tty_term_flag(term, TTYC_XT) || strncmp(s, "\033[", 2) == 0) {
+ term->flags |= TERM_VT100LIKE;
+ tty_add_features(feat, "bpaste,focus,title", ",");
+ }
+
+ /* Add RGB feature if terminal has RGB colours. */
+ if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) &&
+ (!tty_term_has(term, TTYC_SETRGBF) ||
+ !tty_term_has(term, TTYC_SETRGBB)))
+ tty_add_features(feat, "RGB", ",");
+
+ /* Apply the features and overrides again. */
+ if (tty_apply_features(term, *feat))
+ tty_term_apply_overrides(term);
+
+ /* Log the capabilities. */
+ for (i = 0; i < tty_term_ncodes(); i++)
+ log_debug("%s%s", name, tty_term_describe(term, i));
+
+ return (term);
+
+error:
+ tty_term_free(term);
+ return (NULL);
+}
+
+void
+tty_term_free(struct tty_term *term)
+{
+ u_int i;
+
+ log_debug("removing term %s", term->name);
+
+ for (i = 0; i < tty_term_ncodes(); i++) {
+ if (term->codes[i].type == TTYCODE_STRING)
+ free(term->codes[i].value.string);
+ }
+ free(term->codes);
+
+ LIST_REMOVE(term, entry);
+ free(term->name);
+ free(term);
+}
+
+int
+tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps,
+ char **cause)
+{
+ const struct tty_term_code_entry *ent;
+ int error, n;
+ u_int i;
+ const char *s;
+ char tmp[11];
+
+ if (setupterm((char *)name, fd, &error) != OK) {
+ switch (error) {
+ case 1:
+ xasprintf(cause, "can't use hardcopy terminal: %s",
+ name);
+ break;
+ case 0:
+ xasprintf(cause, "missing or unsuitable terminal: %s",
+ name);
+ break;
+ case -1:
+ xasprintf(cause, "can't find terminfo database");
+ break;
+ default:
+ xasprintf(cause, "unknown error");
+ break;
+ }
+ return (-1);
+ }
+
+ *ncaps = 0;
+ *caps = NULL;
+
+ for (i = 0; i < tty_term_ncodes(); i++) {
+ ent = &tty_term_codes[i];
+ switch (ent->type) {
+ case TTYCODE_NONE:
+ continue;
+ case TTYCODE_STRING:
+ s = tigetstr((char *)ent->name);
+ if (s == NULL || s == (char *)-1)
+ continue;
+ break;
+ case TTYCODE_NUMBER:
+ n = tigetnum((char *)ent->name);
+ if (n == -1 || n == -2)
+ continue;
+ xsnprintf(tmp, sizeof tmp, "%d", n);
+ s = tmp;
+ break;
+ case TTYCODE_FLAG:
+ n = tigetflag((char *) ent->name);
+ if (n == -1)
+ continue;
+ if (n)
+ s = "1";
+ else
+ s = "0";
+ break;
+ }
+ *caps = xreallocarray(*caps, (*ncaps) + 1, sizeof **caps);
+ xasprintf(&(*caps)[*ncaps], "%s=%s", ent->name, s);
+ (*ncaps)++;
+ }
+
+#if !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR > 5 || \
+ (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 6)
+ del_curterm(cur_term);
+#endif
+ return (0);
+}
+
+void
+tty_term_free_list(char **caps, u_int ncaps)
+{
+ u_int i;
+
+ for (i = 0; i < ncaps; i++)
+ free(caps[i]);
+ free(caps);
+}
+
+int
+tty_term_has(struct tty_term *term, enum tty_code_code code)
+{
+ return (term->codes[code].type != TTYCODE_NONE);
+}
+
+const char *
+tty_term_string(struct tty_term *term, enum tty_code_code code)
+{
+ if (!tty_term_has(term, code))
+ return ("");
+ if (term->codes[code].type != TTYCODE_STRING)
+ fatalx("not a string: %d", code);
+ return (term->codes[code].value.string);
+}
+
+const char *
+tty_term_string1(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 *
+tty_term_string2(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 *
+tty_term_string3(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 *
+tty_term_ptr1(struct tty_term *term, enum tty_code_code code, const void *a)
+{
+ return (tparm((char *) tty_term_string(term, code), (long)a, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+const char *
+tty_term_ptr2(struct tty_term *term, enum tty_code_code code, const void *a,
+ const void *b)
+{
+ return (tparm((char *) tty_term_string(term, code), (long)a, (long)b, 0, 0, 0, 0, 0, 0, 0));
+}
+
+int
+tty_term_number(struct tty_term *term, enum tty_code_code code)
+{
+ if (!tty_term_has(term, code))
+ return (0);
+ if (term->codes[code].type != TTYCODE_NUMBER)
+ fatalx("not a number: %d", code);
+ return (term->codes[code].value.number);
+}
+
+int
+tty_term_flag(struct tty_term *term, enum tty_code_code code)
+{
+ if (!tty_term_has(term, code))
+ return (0);
+ if (term->codes[code].type != TTYCODE_FLAG)
+ fatalx("not a flag: %d", code);
+ return (term->codes[code].value.flag);
+}
+
+const char *
+tty_term_describe(struct tty_term *term, enum tty_code_code code)
+{
+ static char s[256];
+ char out[128];
+
+ switch (term->codes[code].type) {
+ case TTYCODE_NONE:
+ xsnprintf(s, sizeof s, "%4u: %s: [missing]",
+ code, tty_term_codes[code].name);
+ break;
+ case TTYCODE_STRING:
+ strnvis(out, term->codes[code].value.string, sizeof out,
+ VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
+ xsnprintf(s, sizeof s, "%4u: %s: (string) %s",
+ code, tty_term_codes[code].name,
+ out);
+ break;
+ case TTYCODE_NUMBER:
+ xsnprintf(s, sizeof s, "%4u: %s: (number) %d",
+ code, tty_term_codes[code].name,
+ term->codes[code].value.number);
+ break;
+ case TTYCODE_FLAG:
+ xsnprintf(s, sizeof s, "%4u: %s: (flag) %s",
+ code, tty_term_codes[code].name,
+ term->codes[code].value.flag ? "true" : "false");
+ break;
+ }
+ return (s);
+}
diff --git a/tty.c b/tty.c
new file mode 100644
index 0000000..49cf979
--- /dev/null
+++ b/tty.c
@@ -0,0 +1,2961 @@
+/* $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 <sys/ioctl.h>
+
+#include <netinet/in.h>
+
+#include <curses.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+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);
+static void tty_cursor_pane(struct tty *, const struct tty_ctx *, u_int,
+ u_int);
+static void tty_cursor_pane_unless_wrap(struct tty *,
+ const struct tty_ctx *, u_int, u_int);
+static void tty_invalidate(struct tty *);
+static void tty_colours(struct tty *, const struct grid_cell *);
+static void tty_check_fg(struct tty *, struct colour_palette *,
+ struct grid_cell *);
+static void tty_check_bg(struct tty *, struct colour_palette *,
+ struct grid_cell *);
+static void tty_check_us(struct tty *, struct colour_palette *,
+ struct grid_cell *);
+static void tty_colours_fg(struct tty *, const struct grid_cell *);
+static void tty_colours_bg(struct tty *, const struct grid_cell *);
+static void tty_colours_us(struct tty *, const struct grid_cell *);
+
+static void tty_region_pane(struct tty *, const struct tty_ctx *, u_int,
+ u_int);
+static void tty_region(struct tty *, u_int, u_int);
+static void tty_margin_pane(struct tty *, const struct tty_ctx *);
+static void tty_margin(struct tty *, u_int, u_int);
+static int tty_large_region(struct tty *, const struct tty_ctx *);
+static int tty_fake_bce(const struct tty *, const struct grid_cell *,
+ u_int);
+static void tty_redraw_region(struct tty *, const struct tty_ctx *);
+static void tty_emulate_repeat(struct tty *, enum tty_code_code,
+ enum tty_code_code, u_int);
+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);
+static int tty_check_overlay(struct tty *, u_int, u_int);
+static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int,
+ struct overlay_ranges *);
+
+#define tty_use_margin(tty) \
+ (tty->term->flags & TERM_DECSLRM)
+#define tty_full_width(tty, ctx) \
+ ((ctx)->xoff == 0 && (ctx)->sx >= (tty)->sx)
+
+#define TTY_BLOCK_INTERVAL (100000 /* 100 milliseconds */)
+#define TTY_BLOCK_START(tty) (1 + ((tty)->sx * (tty)->sy) * 8)
+#define TTY_BLOCK_STOP(tty) (1 + ((tty)->sx * (tty)->sy) / 8)
+
+#define TTY_QUERY_TIMEOUT 5
+
+void
+tty_create_log(void)
+{
+ char name[64];
+
+ xsnprintf(name, sizeof name, "tmux-out-%ld.log", (long)getpid());
+
+ tty_log_fd = open(name, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (tty_log_fd != -1 && fcntl(tty_log_fd, F_SETFD, FD_CLOEXEC) == -1)
+ fatal("fcntl failed");
+}
+
+int
+tty_init(struct tty *tty, struct client *c)
+{
+ if (!isatty(c->fd))
+ return (-1);
+
+ memset(tty, 0, sizeof *tty);
+ tty->client = c;
+
+ tty->cstyle = SCREEN_CURSOR_DEFAULT;
+ tty->ccolour = -1;
+
+ if (tcgetattr(c->fd, &tty->tio) != 0)
+ return (-1);
+ return (0);
+}
+
+void
+tty_resize(struct tty *tty)
+{
+ struct client *c = tty->client;
+ struct winsize ws;
+ u_int sx, sy, xpixel, ypixel;
+
+ if (ioctl(c->fd, TIOCGWINSZ, &ws) != -1) {
+ sx = ws.ws_col;
+ if (sx == 0) {
+ sx = 80;
+ xpixel = 0;
+ } else
+ xpixel = ws.ws_xpixel / sx;
+ sy = ws.ws_row;
+ if (sy == 0) {
+ sy = 24;
+ ypixel = 0;
+ } else
+ ypixel = ws.ws_ypixel / sy;
+ } else {
+ sx = 80;
+ sy = 24;
+ xpixel = 0;
+ ypixel = 0;
+ }
+ log_debug("%s: %s now %ux%u (%ux%u)", __func__, c->name, sx, sy,
+ xpixel, ypixel);
+ tty_set_size(tty, sx, sy, xpixel, ypixel);
+ tty_invalidate(tty);
+}
+
+void
+tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel)
+{
+ tty->sx = sx;
+ tty->sy = sy;
+ tty->xpixel = xpixel;
+ tty->ypixel = ypixel;
+}
+
+static void
+tty_read_callback(__unused int fd, __unused short events, void *data)
+{
+ struct tty *tty = data;
+ struct client *c = tty->client;
+ const char *name = c->name;
+ size_t size = EVBUFFER_LENGTH(tty->in);
+ int nread;
+
+ nread = evbuffer_read(tty->in, c->fd, -1);
+ if (nread == 0 || nread == -1) {
+ if (nread == 0)
+ log_debug("%s: read closed", name);
+ else
+ log_debug("%s: read error: %s", name, strerror(errno));
+ event_del(&tty->event_in);
+ server_client_lost(tty->client);
+ return;
+ }
+ log_debug("%s: read %d bytes (already %zu)", name, nread, size);
+
+ while (tty_keys_next(tty))
+ ;
+}
+
+static void
+tty_timer_callback(__unused int fd, __unused short events, void *data)
+{
+ struct tty *tty = data;
+ struct client *c = tty->client;
+ struct timeval tv = { .tv_usec = TTY_BLOCK_INTERVAL };
+
+ log_debug("%s: %zu discarded", c->name, tty->discarded);
+
+ c->flags |= CLIENT_ALLREDRAWFLAGS;
+ c->discarded += tty->discarded;
+
+ if (tty->discarded < TTY_BLOCK_STOP(tty)) {
+ tty->flags &= ~TTY_BLOCK;
+ tty_invalidate(tty);
+ return;
+ }
+ tty->discarded = 0;
+ evtimer_add(&tty->timer, &tv);
+}
+
+static int
+tty_block_maybe(struct tty *tty)
+{
+ struct client *c = tty->client;
+ size_t size = EVBUFFER_LENGTH(tty->out);
+ struct timeval tv = { .tv_usec = TTY_BLOCK_INTERVAL };
+
+ if (size == 0)
+ tty->flags &= ~TTY_NOBLOCK;
+ else if (tty->flags & TTY_NOBLOCK)
+ return (0);
+
+ if (size < TTY_BLOCK_START(tty))
+ return (0);
+
+ if (tty->flags & TTY_BLOCK)
+ return (1);
+ tty->flags |= TTY_BLOCK;
+
+ log_debug("%s: can't keep up, %zu discarded", c->name, size);
+
+ evbuffer_drain(tty->out, size);
+ c->discarded += size;
+
+ tty->discarded = 0;
+ evtimer_add(&tty->timer, &tv);
+ return (1);
+}
+
+static void
+tty_write_callback(__unused int fd, __unused short events, void *data)
+{
+ struct tty *tty = data;
+ struct client *c = tty->client;
+ size_t size = EVBUFFER_LENGTH(tty->out);
+ int nwrite;
+
+ nwrite = evbuffer_write(tty->out, c->fd);
+ if (nwrite == -1)
+ return;
+ log_debug("%s: wrote %d bytes (of %zu)", c->name, nwrite, size);
+
+ if (c->redraw > 0) {
+ if ((size_t)nwrite >= c->redraw)
+ c->redraw = 0;
+ else
+ c->redraw -= nwrite;
+ log_debug("%s: waiting for redraw, %zu bytes left", c->name,
+ c->redraw);
+ } else if (tty_block_maybe(tty))
+ return;
+
+ if (EVBUFFER_LENGTH(tty->out) != 0)
+ event_add(&tty->event_out, NULL);
+}
+
+int
+tty_open(struct tty *tty, char **cause)
+{
+ struct client *c = tty->client;
+
+ tty->term = tty_term_create(tty, c->term_name, c->term_caps,
+ c->term_ncaps, &c->term_features, cause);
+ if (tty->term == NULL) {
+ tty_close(tty);
+ return (-1);
+ }
+ tty->flags |= TTY_OPENED;
+
+ tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_BLOCK|TTY_TIMER);
+
+ event_set(&tty->event_in, c->fd, EV_PERSIST|EV_READ,
+ tty_read_callback, tty);
+ tty->in = evbuffer_new();
+ if (tty->in == NULL)
+ fatal("out of memory");
+
+ event_set(&tty->event_out, c->fd, EV_WRITE, tty_write_callback, tty);
+ tty->out = evbuffer_new();
+ if (tty->out == NULL)
+ fatal("out of memory");
+
+ evtimer_set(&tty->timer, tty_timer_callback, tty);
+
+ tty_start_tty(tty);
+
+ tty_keys_build(tty);
+
+ return (0);
+}
+
+static void
+tty_start_timer_callback(__unused int fd, __unused short events, void *data)
+{
+ struct tty *tty = data;
+ struct client *c = tty->client;
+
+ log_debug("%s: start timer fired", c->name);
+ if ((tty->flags & (TTY_HAVEDA|TTY_HAVEXDA)) == 0)
+ tty_update_features(tty);
+ tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA);
+}
+
+void
+tty_start_tty(struct tty *tty)
+{
+ struct client *c = tty->client;
+ struct termios tio;
+ struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT };
+
+ setblocking(c->fd, 0);
+ event_add(&tty->event_in, NULL);
+
+ memcpy(&tio, &tty->tio, sizeof tio);
+ tio.c_iflag &= ~(IXON|IXOFF|ICRNL|INLCR|IGNCR|IMAXBEL|ISTRIP);
+ tio.c_iflag |= IGNBRK;
+ tio.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET);
+ tio.c_lflag &= ~(IEXTEN|ICANON|ECHO|ECHOE|ECHONL|ECHOCTL|ECHOPRT|
+ ECHOKE|ISIG);
+ tio.c_cc[VMIN] = 1;
+ tio.c_cc[VTIME] = 0;
+ if (tcsetattr(c->fd, TCSANOW, &tio) == 0)
+ tcflush(c->fd, TCOFLUSH);
+
+ tty_putcode(tty, TTYC_SMCUP);
+
+ tty_putcode(tty, TTYC_SMKX);
+ tty_putcode(tty, TTYC_CLEAR);
+
+ if (tty_acs_needed(tty)) {
+ log_debug("%s: using capabilities for ACS", c->name);
+ tty_putcode(tty, TTYC_ENACS);
+ } else
+ log_debug("%s: using UTF-8 for ACS", c->name);
+
+ tty_putcode(tty, TTYC_CNORM);
+ if (tty_term_has(tty->term, TTYC_KMOUS)) {
+ tty_puts(tty, "\033[?1000l\033[?1002l\033[?1003l");
+ tty_puts(tty, "\033[?1006l\033[?1005l");
+ }
+
+ evtimer_set(&tty->start_timer, tty_start_timer_callback, tty);
+ evtimer_add(&tty->start_timer, &tv);
+
+ tty->flags |= TTY_STARTED;
+ tty_invalidate(tty);
+
+ if (tty->ccolour != -1)
+ tty_force_cursor_colour(tty, -1);
+
+ tty->mouse_drag_flag = 0;
+ tty->mouse_drag_update = NULL;
+ tty->mouse_drag_release = NULL;
+}
+
+void
+tty_send_requests(struct tty *tty)
+{
+ if (~tty->flags & TTY_STARTED)
+ return;
+
+ if (tty->term->flags & TERM_VT100LIKE) {
+ if (~tty->flags & TTY_HAVEDA)
+ tty_puts(tty, "\033[>c");
+ if (~tty->flags & TTY_HAVEXDA)
+ tty_puts(tty, "\033[>q");
+ } else
+ tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA);
+}
+
+void
+tty_stop_tty(struct tty *tty)
+{
+ struct client *c = tty->client;
+ struct winsize ws;
+
+ if (!(tty->flags & TTY_STARTED))
+ return;
+ tty->flags &= ~TTY_STARTED;
+
+ evtimer_del(&tty->start_timer);
+
+ event_del(&tty->timer);
+ tty->flags &= ~TTY_BLOCK;
+
+ event_del(&tty->event_in);
+ event_del(&tty->event_out);
+
+ /*
+ * Be flexible about error handling and try not kill the server just
+ * because the fd is invalid. Things like ssh -t can easily leave us
+ * with a dead tty.
+ */
+ if (ioctl(c->fd, TIOCGWINSZ, &ws) == -1)
+ return;
+ if (tcsetattr(c->fd, TCSANOW, &tty->tio) == -1)
+ return;
+
+ tty_raw(tty, tty_term_string2(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));
+ tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX));
+ tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR));
+ if (tty->cstyle != SCREEN_CURSOR_DEFAULT) {
+ 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));
+ }
+ 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));
+
+ tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM));
+ if (tty_term_has(tty->term, TTYC_KMOUS)) {
+ tty_raw(tty, "\033[?1000l\033[?1002l\033[?1003l");
+ tty_raw(tty, "\033[?1006l\033[?1005l");
+ }
+
+ if (tty->term->flags & TERM_VT100LIKE)
+ tty_raw(tty, "\033[?7727l");
+ tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS));
+ tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS));
+
+ if (tty_use_margin(tty))
+ tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG));
+ tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP));
+
+ setblocking(c->fd, 1);
+}
+
+void
+tty_close(struct tty *tty)
+{
+ if (event_initialized(&tty->key_timer))
+ evtimer_del(&tty->key_timer);
+ tty_stop_tty(tty);
+
+ if (tty->flags & TTY_OPENED) {
+ evbuffer_free(tty->in);
+ event_del(&tty->event_in);
+ evbuffer_free(tty->out);
+ event_del(&tty->event_out);
+
+ tty_term_free(tty->term);
+ tty_keys_free(tty);
+
+ tty->flags &= ~TTY_OPENED;
+ }
+}
+
+void
+tty_free(struct tty *tty)
+{
+ tty_close(tty);
+}
+
+void
+tty_update_features(struct tty *tty)
+{
+ struct client *c = tty->client;
+
+ if (tty_apply_features(tty->term, c->term_features))
+ tty_term_apply_overrides(tty->term);
+
+ if (tty_use_margin(tty))
+ tty_putcode(tty, TTYC_ENMG);
+ if (options_get_number(global_options, "extended-keys"))
+ tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS));
+ if (options_get_number(global_options, "focus-events"))
+ tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS));
+ if (tty->term->flags & TERM_VT100LIKE)
+ tty_puts(tty, "\033[?7727h");
+}
+
+void
+tty_raw(struct tty *tty, const char *s)
+{
+ struct client *c = tty->client;
+ ssize_t n, slen;
+ u_int i;
+
+ slen = strlen(s);
+ for (i = 0; i < 5; i++) {
+ n = write(c->fd, s, slen);
+ if (n >= 0) {
+ s += n;
+ slen -= n;
+ if (slen == 0)
+ break;
+ } else if (n == -1 && errno != EAGAIN)
+ break;
+ usleep(100);
+ }
+}
+
+void
+tty_putcode(struct tty *tty, enum tty_code_code code)
+{
+ tty_puts(tty, tty_term_string(tty->term, code));
+}
+
+void
+tty_putcode1(struct tty *tty, enum tty_code_code code, int a)
+{
+ if (a < 0)
+ return;
+ tty_puts(tty, tty_term_string1(tty->term, code, a));
+}
+
+void
+tty_putcode2(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));
+}
+
+void
+tty_putcode3(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));
+}
+
+void
+tty_putcode_ptr1(struct tty *tty, enum tty_code_code code, const void *a)
+{
+ if (a != NULL)
+ tty_puts(tty, tty_term_ptr1(tty->term, code, a));
+}
+
+void
+tty_putcode_ptr2(struct tty *tty, enum tty_code_code code, const void *a,
+ const void *b)
+{
+ if (a != NULL && b != NULL)
+ tty_puts(tty, tty_term_ptr2(tty->term, code, a, b));
+}
+
+static void
+tty_add(struct tty *tty, const char *buf, size_t len)
+{
+ struct client *c = tty->client;
+
+ if (tty->flags & TTY_BLOCK) {
+ tty->discarded += len;
+ return;
+ }
+
+ evbuffer_add(tty->out, buf, len);
+ log_debug("%s: %.*s", c->name, (int)len, buf);
+ c->written += len;
+
+ if (tty_log_fd != -1)
+ write(tty_log_fd, buf, len);
+ if (tty->flags & TTY_STARTED)
+ event_add(&tty->event_out, NULL);
+}
+
+void
+tty_puts(struct tty *tty, const char *s)
+{
+ if (*s != '\0')
+ tty_add(tty, s, strlen(s));
+}
+
+void
+tty_putc(struct tty *tty, u_char ch)
+{
+ const char *acs;
+
+ if ((tty->term->flags & TERM_NOAM) &&
+ ch >= 0x20 && ch != 0x7f &&
+ tty->cy == tty->sy - 1 &&
+ tty->cx + 1 >= tty->sx)
+ return;
+
+ if (tty->cell.attr & GRID_ATTR_CHARSET) {
+ acs = tty_acs_get(tty, ch);
+ if (acs != NULL)
+ tty_add(tty, acs, strlen(acs));
+ else
+ tty_add(tty, &ch, 1);
+ } else
+ tty_add(tty, &ch, 1);
+
+ if (ch >= 0x20 && ch != 0x7f) {
+ if (tty->cx >= tty->sx) {
+ tty->cx = 1;
+ if (tty->cy != tty->rlower)
+ tty->cy++;
+
+ /*
+ * On !am terminals, force the cursor position to where
+ * we think it should be after a line wrap - this means
+ * it works on sensible terminals as well.
+ */
+ if (tty->term->flags & TERM_NOAM)
+ tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx);
+ } else
+ tty->cx++;
+ }
+}
+
+void
+tty_putn(struct tty *tty, const void *buf, size_t len, u_int width)
+{
+ if ((tty->term->flags & TERM_NOAM) &&
+ tty->cy == tty->sy - 1 &&
+ tty->cx + len >= tty->sx)
+ len = tty->sx - tty->cx - 1;
+
+ tty_add(tty, buf, len);
+ if (tty->cx + width > tty->sx) {
+ tty->cx = (tty->cx + width) - tty->sx;
+ if (tty->cx <= tty->sx)
+ tty->cy++;
+ else
+ tty->cx = tty->cy = UINT_MAX;
+ } else
+ tty->cx += width;
+}
+
+static void
+tty_set_italics(struct tty *tty)
+{
+ const char *s;
+
+ if (tty_term_has(tty->term, TTYC_SITM)) {
+ s = options_get_string(global_options, "default-terminal");
+ if (strcmp(s, "screen") != 0 && strncmp(s, "screen-", 7) != 0) {
+ tty_putcode(tty, TTYC_SITM);
+ return;
+ }
+ }
+ tty_putcode(tty, TTYC_SMSO);
+}
+
+void
+tty_set_title(struct tty *tty, const char *title)
+{
+ if (!tty_term_has(tty->term, TTYC_TSL) ||
+ !tty_term_has(tty->term, TTYC_FSL))
+ return;
+
+ tty_putcode(tty, TTYC_TSL);
+ tty_puts(tty, title);
+ tty_putcode(tty, TTYC_FSL);
+}
+
+void
+tty_set_path(struct tty *tty, const char *title)
+{
+ if (!tty_term_has(tty->term, TTYC_SWD) ||
+ !tty_term_has(tty->term, TTYC_FSL))
+ return;
+
+ tty_putcode(tty, TTYC_SWD);
+ tty_puts(tty, title);
+ tty_putcode(tty, TTYC_FSL);
+}
+
+static void
+tty_force_cursor_colour(struct tty *tty, int c)
+{
+ u_char r, g, b;
+ char s[13] = "";
+
+ if (c != -1)
+ c = colour_force_rgb(c);
+ if (c == tty->ccolour)
+ return;
+ if (c == -1)
+ tty_putcode(tty, TTYC_CR);
+ else {
+ colour_split_rgb(c, &r, &g, &b);
+ xsnprintf(s, sizeof s, "rgb:%02hhx/%02hhx/%02hhx", r, g, b);
+ tty_putcode_ptr1(tty, TTYC_CS, s);
+ }
+ tty->ccolour = c;
+}
+
+static int
+tty_update_cursor(struct tty *tty, int mode, struct screen *s)
+{
+ enum screen_cursor_style cstyle;
+ int ccolour, changed, cmode = mode;
+
+ /* Set cursor colour if changed. */
+ if (s != NULL) {
+ ccolour = s->ccolour;
+ if (s->ccolour == -1)
+ ccolour = s->default_ccolour;
+ tty_force_cursor_colour(tty, ccolour);
+ }
+
+ /* If cursor is off, set as invisible. */
+ if (~cmode & MODE_CURSOR) {
+ if (tty->mode & MODE_CURSOR)
+ tty_putcode(tty, TTYC_CIVIS);
+ return (cmode);
+ }
+
+ /* Check if blinking or very visible flag changed or style changed. */
+ if (s == NULL)
+ cstyle = tty->cstyle;
+ else {
+ cstyle = s->cstyle;
+ if (cstyle == SCREEN_CURSOR_DEFAULT) {
+ if (~cmode & MODE_CURSOR_BLINKING_SET) {
+ if (s->default_mode & MODE_CURSOR_BLINKING)
+ cmode |= MODE_CURSOR_BLINKING;
+ else
+ cmode &= ~MODE_CURSOR_BLINKING;
+ }
+ cstyle = s->default_cstyle;
+ }
+ }
+
+ /* If nothing changed, do nothing. */
+ changed = cmode ^ tty->mode;
+ if ((changed & CURSOR_MODES) == 0 && cstyle == tty->cstyle)
+ return (cmode);
+
+ /*
+ * Set cursor style. If an explicit style has been set with DECSCUSR,
+ * set it if supported, otherwise send cvvis for blinking styles.
+ *
+ * If no style, has been set (SCREEN_CURSOR_DEFAULT), then send cvvis
+ * if either the blinking or very visible flags are set.
+ */
+ tty_putcode(tty, TTYC_CNORM);
+ switch (cstyle) {
+ case SCREEN_CURSOR_DEFAULT:
+ if (tty->cstyle != SCREEN_CURSOR_DEFAULT) {
+ if (tty_term_has(tty->term, TTYC_SE))
+ tty_putcode(tty, TTYC_SE);
+ else
+ tty_putcode1(tty, TTYC_SS, 0);
+ }
+ if (cmode & (MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE))
+ tty_putcode(tty, TTYC_CVVIS);
+ break;
+ case SCREEN_CURSOR_BLOCK:
+ if (tty_term_has(tty->term, TTYC_SS)) {
+ if (cmode & MODE_CURSOR_BLINKING)
+ tty_putcode1(tty, TTYC_SS, 1);
+ else
+ tty_putcode1(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);
+ else
+ tty_putcode1(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);
+ else
+ tty_putcode1(tty, TTYC_SS, 6);
+ } else if (cmode & MODE_CURSOR_BLINKING)
+ tty_putcode(tty, TTYC_CVVIS);
+ break;
+ }
+ tty->cstyle = cstyle;
+ return (cmode);
+ }
+
+void
+tty_update_mode(struct tty *tty, int mode, struct screen *s)
+{
+ struct tty_term *term = tty->term;
+ struct client *c = tty->client;
+ int changed;
+
+ if (tty->flags & TTY_NOCURSOR)
+ mode &= ~MODE_CURSOR;
+
+ if (tty_update_cursor(tty, mode, s) & MODE_CURSOR_BLINKING)
+ mode |= MODE_CURSOR_BLINKING;
+ else
+ mode &= ~MODE_CURSOR_BLINKING;
+
+ changed = mode ^ tty->mode;
+ if (log_get_level() != 0 && changed != 0) {
+ log_debug("%s: current mode %s", c->name,
+ screen_mode_to_string(tty->mode));
+ log_debug("%s: setting mode %s", c->name,
+ screen_mode_to_string(mode));
+ }
+
+ if ((changed & ALL_MOUSE_MODES) && tty_term_has(term, TTYC_KMOUS)) {
+ /*
+ * If the mouse modes have changed, clear then all and apply
+ * again. There are differences in how terminals track the
+ * various bits.
+ */
+ tty_puts(tty, "\033[?1006l\033[?1000l\033[?1002l\033[?1003l");
+ if (mode & ALL_MOUSE_MODES)
+ tty_puts(tty, "\033[?1006h");
+ if (mode & MODE_MOUSE_ALL)
+ tty_puts(tty, "\033[?1000h\033[?1002h\033[?1003h");
+ else if (mode & MODE_MOUSE_BUTTON)
+ tty_puts(tty, "\033[?1000h\033[?1002h");
+ else if (mode & MODE_MOUSE_STANDARD)
+ tty_puts(tty, "\033[?1000h");
+ }
+ if (changed & MODE_BRACKETPASTE) {
+ if (mode & MODE_BRACKETPASTE)
+ tty_putcode(tty, TTYC_ENBP);
+ else
+ tty_putcode(tty, TTYC_DSBP);
+ }
+ tty->mode = mode;
+}
+
+static void
+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);
+ else {
+ while (n-- > 0)
+ tty_putcode(tty, code1);
+ }
+}
+
+static void
+tty_repeat_space(struct tty *tty, u_int n)
+{
+ static char s[500];
+
+ if (*s != ' ')
+ memset(s, ' ', sizeof s);
+
+ while (n > sizeof s) {
+ tty_putn(tty, s, sizeof s, sizeof s);
+ n -= sizeof s;
+ }
+ if (n != 0)
+ tty_putn(tty, s, n, n);
+}
+
+/* Is this window bigger than the terminal? */
+int
+tty_window_bigger(struct tty *tty)
+{
+ struct client *c = tty->client;
+ struct window *w = c->session->curw->window;
+
+ return (tty->sx < w->sx || tty->sy - status_line_size(c) < w->sy);
+}
+
+/* What offset should this window be drawn at? */
+int
+tty_window_offset(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy)
+{
+ *ox = tty->oox;
+ *oy = tty->ooy;
+ *sx = tty->osx;
+ *sy = tty->osy;
+
+ return (tty->oflag);
+}
+
+/* What offset should this window be drawn at? */
+static int
+tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy)
+{
+ struct client *c = tty->client;
+ struct window *w = c->session->curw->window;
+ struct window_pane *wp = server_client_get_pane(c);
+ u_int cx, cy, lines;
+
+ lines = status_line_size(c);
+
+ if (tty->sx >= w->sx && tty->sy - lines >= w->sy) {
+ *ox = 0;
+ *oy = 0;
+ *sx = w->sx;
+ *sy = w->sy;
+
+ c->pan_window = NULL;
+ return (0);
+ }
+
+ *sx = tty->sx;
+ *sy = tty->sy - lines;
+
+ if (c->pan_window == w) {
+ if (*sx >= w->sx)
+ c->pan_ox = 0;
+ else if (c->pan_ox + *sx > w->sx)
+ c->pan_ox = w->sx - *sx;
+ *ox = c->pan_ox;
+ if (*sy >= w->sy)
+ c->pan_oy = 0;
+ else if (c->pan_oy + *sy > w->sy)
+ c->pan_oy = w->sy - *sy;
+ *oy = c->pan_oy;
+ return (1);
+ }
+
+ if (~wp->screen->mode & MODE_CURSOR) {
+ *ox = 0;
+ *oy = 0;
+ } else {
+ cx = wp->xoff + wp->screen->cx;
+ cy = wp->yoff + wp->screen->cy;
+
+ if (cx < *sx)
+ *ox = 0;
+ else if (cx > w->sx - *sx)
+ *ox = w->sx - *sx;
+ else
+ *ox = cx - *sx / 2;
+
+ if (cy < *sy)
+ *oy = 0;
+ else if (cy > w->sy - *sy)
+ *oy = w->sy - *sy;
+ else
+ *oy = cy - *sy / 2;
+ }
+
+ c->pan_window = NULL;
+ return (1);
+}
+
+/* Update stored offsets for a window and redraw if necessary. */
+void
+tty_update_window_offset(struct window *w)
+{
+ struct client *c;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL &&
+ c->session->curw != NULL &&
+ c->session->curw->window == w)
+ tty_update_client_offset(c);
+ }
+}
+
+/* Update stored offsets for a client and redraw if necessary. */
+void
+tty_update_client_offset(struct client *c)
+{
+ u_int ox, oy, sx, sy;
+
+ if (~c->flags & CLIENT_TERMINAL)
+ return;
+
+ c->tty.oflag = tty_window_offset1(&c->tty, &ox, &oy, &sx, &sy);
+ if (ox == c->tty.oox &&
+ oy == c->tty.ooy &&
+ sx == c->tty.osx &&
+ sy == c->tty.osy)
+ return;
+
+ log_debug ("%s: %s offset has changed (%u,%u %ux%u -> %u,%u %ux%u)",
+ __func__, c->name, c->tty.oox, c->tty.ooy, c->tty.osx, c->tty.osy,
+ ox, oy, sx, sy);
+
+ c->tty.oox = ox;
+ c->tty.ooy = oy;
+ c->tty.osx = sx;
+ c->tty.osy = sy;
+
+ c->flags |= (CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS);
+}
+
+/*
+ * Is the region large enough to be worth redrawing once later rather than
+ * probably several times now? Currently yes if it is more than 50% of the
+ * pane.
+ */
+static int
+tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx)
+{
+ return (ctx->orlower - ctx->orupper >= ctx->sy / 2);
+}
+
+/*
+ * Return if BCE is needed but the terminal doesn't have it - it'll need to be
+ * emulated.
+ */
+static int
+tty_fake_bce(const struct tty *tty, const struct grid_cell *gc, u_int bg)
+{
+ if (tty_term_flag(tty->term, TTYC_BCE))
+ return (0);
+ if (!COLOUR_DEFAULT(bg) || !COLOUR_DEFAULT(gc->bg))
+ return (1);
+ return (0);
+}
+
+/*
+ * Redraw scroll region using data from screen (already updated). Used when
+ * CSR not supported, or window is a pane that doesn't take up the full
+ * width of the terminal.
+ */
+static void
+tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+ u_int i;
+
+ /*
+ * If region is large, schedule a redraw. In most cases this is likely
+ * to be followed by some more scrolling.
+ */
+ if (tty_large_region(tty, ctx)) {
+ log_debug("%s: %s large redraw", __func__, c->name);
+ ctx->redraw_cb(ctx);
+ return;
+ }
+
+ for (i = ctx->orupper; i <= ctx->orlower; i++)
+ tty_draw_pane(tty, ctx, i);
+}
+
+/* Is this position visible in the pane? */
+static int
+tty_is_visible(__unused struct tty *tty, const struct tty_ctx *ctx, u_int px,
+ u_int py, u_int nx, u_int ny)
+{
+ u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py;
+
+ if (!ctx->bigger)
+ return (1);
+
+ if (xoff + nx <= ctx->wox || xoff >= ctx->wox + ctx->wsx ||
+ yoff + ny <= ctx->woy || yoff >= ctx->woy + ctx->wsy)
+ return (0);
+ return (1);
+}
+
+/* Clamp line position to visible part of pane. */
+static int
+tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py,
+ u_int nx, u_int *i, u_int *x, u_int *rx, u_int *ry)
+{
+ u_int xoff = ctx->rxoff + px;
+
+ if (!tty_is_visible(tty, ctx, px, py, nx, 1))
+ return (0);
+ *ry = ctx->yoff + py - ctx->woy;
+
+ if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) {
+ /* All visible. */
+ *i = 0;
+ *x = ctx->xoff + px - ctx->wox;
+ *rx = nx;
+ } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) {
+ /* Both left and right not visible. */
+ *i = ctx->wox;
+ *x = 0;
+ *rx = ctx->wsx;
+ } else if (xoff < ctx->wox) {
+ /* Left not visible. */
+ *i = ctx->wox - (ctx->xoff + px);
+ *x = 0;
+ *rx = nx - *i;
+ } else {
+ /* Right not visible. */
+ *i = 0;
+ *x = (ctx->xoff + px) - ctx->wox;
+ *rx = ctx->wsx - *x;
+ }
+ if (*rx > nx)
+ fatalx("%s: x too big, %u > %u", __func__, *rx, nx);
+
+ return (1);
+}
+
+/* Clear a line. */
+static void
+tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py,
+ u_int px, u_int nx, u_int bg)
+{
+ struct client *c = tty->client;
+ struct overlay_ranges r;
+ u_int i;
+
+ log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py);
+
+ /* Nothing to clear. */
+ if (nx == 0)
+ return;
+
+ /* If genuine BCE is available, can try escape sequences. */
+ if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) {
+ /* Off the end of the line, use EL if available. */
+ if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) {
+ tty_cursor(tty, px, py);
+ tty_putcode(tty, TTYC_EL);
+ return;
+ }
+
+ /* At the start of the line. Use EL1. */
+ if (px == 0 && tty_term_has(tty->term, TTYC_EL1)) {
+ tty_cursor(tty, px + nx - 1, py);
+ tty_putcode(tty, TTYC_EL1);
+ return;
+ }
+
+ /* 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);
+ return;
+ }
+ }
+
+ /*
+ * Couldn't use an escape sequence, use spaces. Clear only the visible
+ * bit if there is an overlay.
+ */
+ tty_check_overlay_range(tty, px, py, nx, &r);
+ for (i = 0; i < OVERLAY_MAX_RANGES; i++) {
+ if (r.nx[i] == 0)
+ continue;
+ tty_cursor(tty, r.px[i], py);
+ tty_repeat_space(tty, r.nx[i]);
+ }
+}
+
+/* Clear a line, adjusting to visible part of pane. */
+static void
+tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py,
+ u_int px, u_int nx, u_int bg)
+{
+ struct client *c = tty->client;
+ u_int i, x, rx, ry;
+
+ log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py);
+
+ if (tty_clamp_line(tty, ctx, px, py, nx, &i, &x, &rx, &ry))
+ tty_clear_line(tty, &ctx->defaults, ry, x, rx, bg);
+}
+
+/* Clamp area position to visible part of pane. */
+static int
+tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py,
+ u_int nx, u_int ny, u_int *i, u_int *j, u_int *x, u_int *y, u_int *rx,
+ u_int *ry)
+{
+ u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py;
+
+ if (!tty_is_visible(tty, ctx, px, py, nx, ny))
+ return (0);
+
+ if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) {
+ /* All visible. */
+ *i = 0;
+ *x = ctx->xoff + px - ctx->wox;
+ *rx = nx;
+ } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) {
+ /* Both left and right not visible. */
+ *i = ctx->wox;
+ *x = 0;
+ *rx = ctx->wsx;
+ } else if (xoff < ctx->wox) {
+ /* Left not visible. */
+ *i = ctx->wox - (ctx->xoff + px);
+ *x = 0;
+ *rx = nx - *i;
+ } else {
+ /* Right not visible. */
+ *i = 0;
+ *x = (ctx->xoff + px) - ctx->wox;
+ *rx = ctx->wsx - *x;
+ }
+ if (*rx > nx)
+ fatalx("%s: x too big, %u > %u", __func__, *rx, nx);
+
+ if (yoff >= ctx->woy && yoff + ny <= ctx->woy + ctx->wsy) {
+ /* All visible. */
+ *j = 0;
+ *y = ctx->yoff + py - ctx->woy;
+ *ry = ny;
+ } else if (yoff < ctx->woy && yoff + ny > ctx->woy + ctx->wsy) {
+ /* Both top and bottom not visible. */
+ *j = ctx->woy;
+ *y = 0;
+ *ry = ctx->wsy;
+ } else if (yoff < ctx->woy) {
+ /* Top not visible. */
+ *j = ctx->woy - (ctx->yoff + py);
+ *y = 0;
+ *ry = ny - *j;
+ } else {
+ /* Bottom not visible. */
+ *j = 0;
+ *y = (ctx->yoff + py) - ctx->woy;
+ *ry = ctx->wsy - *y;
+ }
+ if (*ry > ny)
+ fatalx("%s: y too big, %u > %u", __func__, *ry, ny);
+
+ return (1);
+}
+
+/* Clear an area, adjusting to visible part of pane. */
+static void
+tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py,
+ u_int ny, u_int px, u_int nx, u_int bg)
+{
+ struct client *c = tty->client;
+ u_int yy;
+ char tmp[64];
+
+ log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py);
+
+ /* Nothing to clear. */
+ if (nx == 0 || ny == 0)
+ return;
+
+ /* If genuine BCE is available, can try escape sequences. */
+ if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) {
+ /* Use ED if clearing off the bottom of the terminal. */
+ if (px == 0 &&
+ px + nx >= tty->sx &&
+ py + ny >= tty->sy &&
+ tty_term_has(tty->term, TTYC_ED)) {
+ tty_cursor(tty, 0, py);
+ tty_putcode(tty, TTYC_ED);
+ return;
+ }
+
+ /*
+ * On VT420 compatible terminals we can use DECFRA if the
+ * background colour isn't default (because it doesn't work
+ * after SGR 0).
+ */
+ if ((tty->term->flags & TERM_DECFRA) && !COLOUR_DEFAULT(bg)) {
+ xsnprintf(tmp, sizeof tmp, "\033[32;%u;%u;%u;%u$x",
+ py + 1, px + 1, py + ny, px + nx);
+ tty_puts(tty, tmp);
+ return;
+ }
+
+ /* Full lines can be scrolled away to clear them. */
+ if (px == 0 &&
+ px + nx >= tty->sx &&
+ ny > 2 &&
+ tty_term_has(tty->term, TTYC_CSR) &&
+ tty_term_has(tty->term, TTYC_INDN)) {
+ tty_region(tty, py, py + ny - 1);
+ tty_margin_off(tty);
+ tty_putcode1(tty, TTYC_INDN, ny);
+ return;
+ }
+
+ /*
+ * If margins are supported, can just scroll the area off to
+ * clear it.
+ */
+ if (nx > 2 &&
+ ny > 2 &&
+ tty_term_has(tty->term, TTYC_CSR) &&
+ tty_use_margin(tty) &&
+ 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);
+ return;
+ }
+ }
+
+ /* Couldn't use an escape sequence, loop over the lines. */
+ for (yy = py; yy < py + ny; yy++)
+ tty_clear_line(tty, defaults, yy, px, nx, bg);
+}
+
+/* Clear an area in a pane. */
+static void
+tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py,
+ u_int ny, u_int px, u_int nx, u_int bg)
+{
+ u_int i, j, x, y, rx, ry;
+
+ if (tty_clamp_area(tty, ctx, px, py, nx, ny, &i, &j, &x, &y, &rx, &ry))
+ tty_clear_area(tty, &ctx->defaults, y, ry, x, rx, bg);
+}
+
+static void
+tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py)
+{
+ struct screen *s = ctx->s;
+ u_int nx = ctx->sx, i, x, rx, ry;
+
+ log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger);
+
+ if (!ctx->bigger) {
+ tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py,
+ &ctx->defaults, ctx->palette);
+ return;
+ }
+ if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) {
+ tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults,
+ ctx->palette);
+ }
+}
+
+static const struct grid_cell *
+tty_check_codeset(struct tty *tty, const struct grid_cell *gc)
+{
+ static struct grid_cell new;
+ int c;
+
+ /* Characters less than 0x7f are always fine, no matter what. */
+ if (gc->data.size == 1 && *gc->data.data < 0x7f)
+ return (gc);
+
+ /* UTF-8 terminal and a UTF-8 character - fine. */
+ if (tty->client->flags & CLIENT_UTF8)
+ return (gc);
+ memcpy(&new, gc, sizeof new);
+
+ /* See if this can be mapped to an ACS character. */
+ c = tty_acs_reverse_get(tty, gc->data.data, gc->data.size);
+ if (c != -1) {
+ utf8_set(&new.data, c);
+ new.attr |= GRID_ATTR_CHARSET;
+ return (&new);
+ }
+
+ /* Replace by the right number of underscores. */
+ new.data.size = gc->data.width;
+ if (new.data.size > UTF8_SIZE)
+ new.data.size = UTF8_SIZE;
+ memset(new.data.data, '_', new.data.size);
+ return (&new);
+}
+
+/*
+ * Check if a single character is obstructed by the overlay and return a
+ * boolean.
+ */
+static int
+tty_check_overlay(struct tty *tty, u_int px, u_int py)
+{
+ struct overlay_ranges r;
+
+ /*
+ * A unit width range will always return nx[2] == 0 from a check, even
+ * with multiple overlays, so it's sufficient to check just the first
+ * two entries.
+ */
+ tty_check_overlay_range(tty, px, py, 1, &r);
+ if (r.nx[0] + r.nx[1] == 0)
+ return (0);
+ return (1);
+}
+
+/* Return parts of the input range which are visible. */
+static void
+tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx,
+ struct overlay_ranges *r)
+{
+ struct client *c = tty->client;
+
+ if (c->overlay_check == NULL) {
+ r->px[0] = px;
+ r->nx[0] = nx;
+ r->px[1] = 0;
+ r->nx[1] = 0;
+ r->px[2] = 0;
+ r->nx[2] = 0;
+ return;
+ }
+
+ c->overlay_check(c, c->overlay_data, px, py, nx, r);
+}
+
+void
+tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
+ u_int atx, u_int aty, const struct grid_cell *defaults,
+ struct colour_palette *palette)
+{
+ struct grid *gd = s->grid;
+ struct grid_cell gc, last;
+ const struct grid_cell *gcp;
+ struct grid_line *gl;
+ struct client *c = tty->client;
+ struct overlay_ranges r;
+ u_int i, j, ux, sx, width, hidden, eux, nxx;
+ u_int cellsize;
+ int flags, cleared = 0, wrapped = 0;
+ char buf[512];
+ size_t len;
+
+ log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__,
+ px, py, nx, atx, aty);
+ log_debug("%s: defaults: fg=%d, bg=%d", __func__, defaults->fg,
+ defaults->bg);
+
+ /*
+ * py is the line in the screen to draw.
+ * px is the start x and nx is the width to draw.
+ * atx,aty is the line on the terminal to draw it.
+ */
+
+ flags = (tty->flags & TTY_NOCURSOR);
+ tty->flags |= TTY_NOCURSOR;
+ tty_update_mode(tty, tty->mode, s);
+
+ tty_region_off(tty);
+ tty_margin_off(tty);
+
+ /*
+ * Clamp the width to cellsize - note this is not cellused, because
+ * there may be empty background cells after it (from BCE).
+ */
+ sx = screen_size_x(s);
+ if (nx > sx)
+ nx = sx;
+ cellsize = grid_get_line(gd, gd->hsize + py)->cellsize;
+ if (sx > cellsize)
+ sx = cellsize;
+ if (sx > tty->sx)
+ sx = tty->sx;
+ if (sx > nx)
+ sx = nx;
+ ux = 0;
+
+ if (py == 0)
+ gl = NULL;
+ else
+ gl = grid_get_line(gd, gd->hsize + py - 1);
+ if (gl == NULL ||
+ (~gl->flags & GRID_LINE_WRAPPED) ||
+ atx != 0 ||
+ tty->cx < tty->sx ||
+ nx < tty->sx) {
+ if (nx < tty->sx &&
+ atx == 0 &&
+ px + sx != 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_cursor(tty, nx - 1, aty);
+ tty_putcode(tty, TTYC_EL1);
+ cleared = 1;
+ }
+ } else {
+ log_debug("%s: wrapped line %u", __func__, aty);
+ wrapped = 1;
+ }
+
+ memcpy(&last, &grid_default_cell, sizeof last);
+ len = 0;
+ width = 0;
+
+ for (i = 0; i < sx; i++) {
+ grid_view_get_cell(gd, px + i, py, &gc);
+ gcp = tty_check_codeset(tty, &gc);
+ if (len != 0 &&
+ (!tty_check_overlay(tty, atx + ux + width, aty) ||
+ (gcp->attr & GRID_ATTR_CHARSET) ||
+ gcp->flags != last.flags ||
+ gcp->attr != last.attr ||
+ gcp->fg != last.fg ||
+ gcp->bg != last.bg ||
+ gcp->us != last.us ||
+ ux + width + gcp->data.width > nx ||
+ (sizeof buf) - len < gcp->data.size)) {
+ tty_attributes(tty, &last, defaults, palette);
+ if (last.flags & GRID_FLAG_CLEARED) {
+ log_debug("%s: %zu cleared", __func__, len);
+ tty_clear_line(tty, defaults, aty, atx + ux,
+ width, last.bg);
+ } else {
+ if (!wrapped || atx != 0 || ux != 0)
+ tty_cursor(tty, atx + ux, aty);
+ tty_putn(tty, buf, len, width);
+ }
+ ux += width;
+
+ len = 0;
+ width = 0;
+ wrapped = 0;
+ }
+
+ if (gcp->flags & GRID_FLAG_SELECTED)
+ screen_select_cell(s, &last, gcp);
+ else
+ memcpy(&last, gcp, sizeof last);
+
+ tty_check_overlay_range(tty, atx + ux, aty, gcp->data.width,
+ &r);
+ hidden = 0;
+ for (j = 0; j < OVERLAY_MAX_RANGES; j++)
+ hidden += r.nx[j];
+ hidden = gcp->data.width - hidden;
+ if (hidden != 0 && hidden == gcp->data.width) {
+ if (~gcp->flags & GRID_FLAG_PADDING)
+ ux += gcp->data.width;
+ } else if (hidden != 0 || ux + gcp->data.width > nx) {
+ if (~gcp->flags & GRID_FLAG_PADDING) {
+ tty_attributes(tty, &last, defaults, palette);
+ for (j = 0; j < OVERLAY_MAX_RANGES; j++) {
+ if (r.nx[j] == 0)
+ continue;
+ /* Effective width drawn so far. */
+ eux = r.px[j] - atx;
+ if (eux < nx) {
+ tty_cursor(tty, r.px[j], aty);
+ nxx = nx - eux;
+ if (r.nx[j] > nxx)
+ r.nx[j] = nxx;
+ tty_repeat_space(tty, r.nx[j]);
+ ux = eux + r.nx[j];
+ }
+ }
+ }
+ } else if (gcp->attr & GRID_ATTR_CHARSET) {
+ tty_attributes(tty, &last, defaults, palette);
+ tty_cursor(tty, atx + ux, aty);
+ for (j = 0; j < gcp->data.size; j++)
+ tty_putc(tty, gcp->data.data[j]);
+ ux += gcp->data.width;
+ } else if (~gcp->flags & GRID_FLAG_PADDING) {
+ memcpy(buf + len, gcp->data.data, gcp->data.size);
+ len += gcp->data.size;
+ width += gcp->data.width;
+ }
+ }
+ if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) {
+ tty_attributes(tty, &last, defaults, palette);
+ if (last.flags & GRID_FLAG_CLEARED) {
+ log_debug("%s: %zu cleared (end)", __func__, len);
+ tty_clear_line(tty, defaults, aty, atx + ux, width,
+ last.bg);
+ } else {
+ if (!wrapped || atx != 0 || ux != 0)
+ tty_cursor(tty, atx + ux, aty);
+ tty_putn(tty, buf, len, width);
+ }
+ ux += width;
+ }
+
+ 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_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8);
+ }
+
+ tty->flags = (tty->flags & ~TTY_NOCURSOR) | flags;
+ tty_update_mode(tty, tty->mode, s);
+}
+
+void
+tty_sync_start(struct tty *tty)
+{
+ if (tty->flags & TTY_BLOCK)
+ return;
+ if (tty->flags & TTY_SYNCING)
+ return;
+ tty->flags |= TTY_SYNCING;
+
+ if (tty_term_has(tty->term, TTYC_SYNC)) {
+ log_debug("%s sync start", tty->client->name);
+ tty_putcode1(tty, TTYC_SYNC, 1);
+ }
+}
+
+void
+tty_sync_end(struct tty *tty)
+{
+ if (tty->flags & TTY_BLOCK)
+ return;
+ if (~tty->flags & TTY_SYNCING)
+ return;
+ tty->flags &= ~TTY_SYNCING;
+
+ if (tty_term_has(tty->term, TTYC_SYNC)) {
+ log_debug("%s sync end", tty->client->name);
+ tty_putcode1(tty, TTYC_SYNC, 2);
+ }
+}
+
+static int
+tty_client_ready(struct client *c)
+{
+ if (c->session == NULL || c->tty.term == NULL)
+ return (0);
+ if (c->flags & (CLIENT_REDRAWWINDOW|CLIENT_SUSPENDED))
+ return (0);
+ if (c->tty.flags & TTY_FREEZE)
+ return (0);
+ return (1);
+}
+
+void
+tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
+ struct tty_ctx *ctx)
+{
+ struct client *c;
+ int state;
+
+ 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);
+ }
+}
+
+void
+tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+
+ if (ctx->bigger ||
+ !tty_full_width(tty, ctx) ||
+ tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
+ (!tty_term_has(tty->term, TTYC_ICH) &&
+ !tty_term_has(tty->term, TTYC_ICH1)) ||
+ c->overlay_check != NULL) {
+ tty_draw_pane(tty, ctx, ctx->ocy);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
+
+ tty_emulate_repeat(tty, TTYC_ICH, TTYC_ICH1, ctx->num);
+}
+
+void
+tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+
+ if (ctx->bigger ||
+ !tty_full_width(tty, ctx) ||
+ tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
+ (!tty_term_has(tty->term, TTYC_DCH) &&
+ !tty_term_has(tty->term, TTYC_DCH1)) ||
+ c->overlay_check != NULL) {
+ tty_draw_pane(tty, ctx, ctx->ocy);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
+
+ tty_emulate_repeat(tty, TTYC_DCH, TTYC_DCH1, ctx->num);
+}
+
+void
+tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx)
+{
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg);
+}
+
+void
+tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+
+ if (ctx->bigger ||
+ !tty_full_width(tty, ctx) ||
+ tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
+ !tty_term_has(tty->term, TTYC_CSR) ||
+ !tty_term_has(tty->term, TTYC_IL1) ||
+ ctx->sx == 1 ||
+ ctx->sy == 1 ||
+ c->overlay_check != NULL) {
+ tty_redraw_region(tty, ctx);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
+ tty_margin_off(tty);
+ tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
+
+ tty_emulate_repeat(tty, TTYC_IL, TTYC_IL1, ctx->num);
+ tty->cx = tty->cy = UINT_MAX;
+}
+
+void
+tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+
+ if (ctx->bigger ||
+ !tty_full_width(tty, ctx) ||
+ tty_fake_bce(tty, &ctx->defaults, ctx->bg) ||
+ !tty_term_has(tty->term, TTYC_CSR) ||
+ !tty_term_has(tty->term, TTYC_DL1) ||
+ ctx->sx == 1 ||
+ ctx->sy == 1 ||
+ c->overlay_check != NULL) {
+ tty_redraw_region(tty, ctx);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
+ tty_margin_off(tty);
+ tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
+
+ tty_emulate_repeat(tty, TTYC_DL, TTYC_DL1, ctx->num);
+ tty->cx = tty->cy = UINT_MAX;
+}
+
+void
+tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx)
+{
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg);
+}
+
+void
+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_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg);
+}
+
+void
+tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx)
+{
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg);
+}
+
+void
+tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+
+ if (ctx->ocy != ctx->orupper)
+ return;
+
+ if (ctx->bigger ||
+ (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
+ tty_fake_bce(tty, &ctx->defaults, 8) ||
+ !tty_term_has(tty->term, TTYC_CSR) ||
+ (!tty_term_has(tty->term, TTYC_RI) &&
+ !tty_term_has(tty->term, TTYC_RIN)) ||
+ ctx->sx == 1 ||
+ ctx->sy == 1 ||
+ c->overlay_check != NULL) {
+ tty_redraw_region(tty, ctx);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ 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_RI))
+ tty_putcode(tty, TTYC_RI);
+ else
+ tty_putcode1(tty, TTYC_RIN, 1);
+}
+
+void
+tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+
+ if (ctx->ocy != ctx->orlower)
+ return;
+
+ if (ctx->bigger ||
+ (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
+ tty_fake_bce(tty, &ctx->defaults, 8) ||
+ !tty_term_has(tty->term, TTYC_CSR) ||
+ ctx->sx == 1 ||
+ ctx->sy == 1 ||
+ c->overlay_check != NULL) {
+ tty_redraw_region(tty, ctx);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
+ tty_margin_pane(tty, ctx);
+
+ /*
+ * If we want to wrap a pane while using margins, the cursor needs to
+ * be exactly on the right of the region. If the cursor is entirely off
+ * the edge - move it back to the right. Some terminals are funny about
+ * this and insert extra spaces, so only use the right if margins are
+ * enabled.
+ */
+ if (ctx->xoff + ctx->ocx > tty->rright) {
+ if (!tty_use_margin(tty))
+ tty_cursor(tty, 0, ctx->yoff + ctx->ocy);
+ else
+ tty_cursor(tty, tty->rright, ctx->yoff + ctx->ocy);
+ } else
+ tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy);
+
+ tty_putc(tty, '\n');
+}
+
+void
+tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct client *c = tty->client;
+ u_int i;
+
+ if (ctx->bigger ||
+ (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
+ tty_fake_bce(tty, &ctx->defaults, 8) ||
+ !tty_term_has(tty->term, TTYC_CSR) ||
+ ctx->sx == 1 ||
+ ctx->sy == 1 ||
+ c->overlay_check != NULL) {
+ tty_redraw_region(tty, ctx);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
+ tty_margin_pane(tty, ctx);
+
+ if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) {
+ if (!tty_use_margin(tty))
+ tty_cursor(tty, 0, tty->rlower);
+ else
+ tty_cursor(tty, tty->rright, tty->rlower);
+ for (i = 0; i < ctx->num; i++)
+ tty_putc(tty, '\n');
+ } else {
+ if (tty->cy == UINT_MAX)
+ tty_cursor(tty, 0, 0);
+ else
+ tty_cursor(tty, 0, tty->cy);
+ tty_putcode1(tty, TTYC_INDN, ctx->num);
+ }
+}
+
+void
+tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx)
+{
+ u_int i;
+ struct client *c = tty->client;
+
+ if (ctx->bigger ||
+ (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) ||
+ tty_fake_bce(tty, &ctx->defaults, 8) ||
+ !tty_term_has(tty->term, TTYC_CSR) ||
+ (!tty_term_has(tty->term, TTYC_RI) &&
+ !tty_term_has(tty->term, TTYC_RIN)) ||
+ ctx->sx == 1 ||
+ ctx->sy == 1 ||
+ c->overlay_check != NULL) {
+ tty_redraw_region(tty, ctx);
+ return;
+ }
+
+ tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg);
+
+ 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);
+ else {
+ for (i = 0; i < ctx->num; i++)
+ tty_putcode(tty, TTYC_RI);
+ }
+}
+
+void
+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_region_pane(tty, ctx, 0, ctx->sy - 1);
+ tty_margin_off(tty);
+
+ px = 0;
+ nx = ctx->sx;
+ py = ctx->ocy + 1;
+ ny = ctx->sy - ctx->ocy - 1;
+
+ tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg);
+
+ px = ctx->ocx;
+ nx = ctx->sx - ctx->ocx;
+ py = ctx->ocy;
+
+ tty_clear_pane_line(tty, ctx, py, px, nx, ctx->bg);
+}
+
+void
+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_region_pane(tty, ctx, 0, ctx->sy - 1);
+ tty_margin_off(tty);
+
+ px = 0;
+ nx = ctx->sx;
+ py = 0;
+ ny = ctx->ocy;
+
+ tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg);
+
+ px = 0;
+ nx = ctx->ocx + 1;
+ py = ctx->ocy;
+
+ tty_clear_pane_line(tty, ctx, py, px, nx, ctx->bg);
+}
+
+void
+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_region_pane(tty, ctx, 0, ctx->sy - 1);
+ tty_margin_off(tty);
+
+ px = 0;
+ nx = ctx->sx;
+ py = 0;
+ ny = ctx->sy;
+
+ tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg);
+}
+
+void
+tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
+{
+ u_int i, j;
+
+ if (ctx->bigger) {
+ ctx->redraw_cb(ctx);
+ return;
+ }
+
+ tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette);
+
+ tty_region_pane(tty, ctx, 0, ctx->sy - 1);
+ tty_margin_off(tty);
+
+ for (j = 0; j < ctx->sy; j++) {
+ tty_cursor_pane(tty, ctx, 0, j);
+ for (i = 0; i < ctx->sx; i++)
+ tty_putc(tty, 'E');
+ }
+}
+
+void
+tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
+{
+ const struct grid_cell *gcp = ctx->cell;
+ struct screen *s = ctx->s;
+ struct overlay_ranges r;
+ u_int px, py, i, vis = 0;
+
+ px = ctx->xoff + ctx->ocx - ctx->wox;
+ py = ctx->yoff + ctx->ocy - ctx->woy;
+ if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1) ||
+ (gcp->data.width == 1 && !tty_check_overlay(tty, px, py)))
+ return;
+
+ /* Handle partially obstructed wide characters. */
+ if (gcp->data.width > 1) {
+ tty_check_overlay_range(tty, px, py, gcp->data.width, &r);
+ for (i = 0; i < OVERLAY_MAX_RANGES; i++)
+ vis += r.nx[i];
+ if (vis < gcp->data.width) {
+ tty_draw_line(tty, s, s->cx, s->cy, gcp->data.width,
+ px, py, &ctx->defaults, ctx->palette);
+ return;
+ }
+ }
+
+ if (ctx->xoff + ctx->ocx - ctx->wox > tty->sx - 1 &&
+ ctx->ocy == ctx->orlower &&
+ tty_full_width(tty, ctx))
+ tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower);
+
+ tty_margin_off(tty);
+ tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
+
+ tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette);
+}
+
+void
+tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx)
+{
+ struct overlay_ranges r;
+ u_int i, px, py, cx;
+ char *cp = ctx->ptr;
+
+ if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, ctx->num, 1))
+ return;
+
+ if (ctx->bigger &&
+ (ctx->xoff + ctx->ocx < ctx->wox ||
+ ctx->xoff + ctx->ocx + ctx->num > ctx->wox + ctx->wsx)) {
+ if (!ctx->wrapped ||
+ !tty_full_width(tty, ctx) ||
+ (tty->term->flags & TERM_NOAM) ||
+ ctx->xoff + ctx->ocx != 0 ||
+ ctx->yoff + ctx->ocy != tty->cy + 1 ||
+ tty->cx < tty->sx ||
+ tty->cy == tty->rlower)
+ tty_draw_pane(tty, ctx, ctx->ocy);
+ else
+ ctx->redraw_cb(ctx);
+ return;
+ }
+
+ tty_margin_off(tty);
+ tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
+ tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette);
+
+ /* Get tty position from pane position for overlay check. */
+ px = ctx->xoff + ctx->ocx - ctx->wox;
+ py = ctx->yoff + ctx->ocy - ctx->woy;
+
+ tty_check_overlay_range(tty, px, py, ctx->num, &r);
+ for (i = 0; i < OVERLAY_MAX_RANGES; i++) {
+ if (r.nx[i] == 0)
+ continue;
+ /* Convert back to pane position for printing. */
+ cx = r.px[i] - ctx->xoff + ctx->wox;
+ tty_cursor_pane_unless_wrap(tty, ctx, cx, ctx->ocy);
+ tty_putn(tty, cp + r.px[i] - px, r.nx[i], r.nx[i]);
+ }
+}
+
+void
+tty_cmd_setselection(struct tty *tty, const struct tty_ctx *ctx)
+{
+ tty_set_selection(tty, ctx->ptr, ctx->num);
+}
+
+void
+tty_set_selection(struct tty *tty, const char *buf, size_t len)
+{
+ char *encoded;
+ size_t size;
+
+ if (~tty->flags & TTY_STARTED)
+ return;
+ if (!tty_term_has(tty->term, TTYC_MS))
+ return;
+
+ size = 4 * ((len + 2) / 3) + 1; /* storage for base64 */
+ encoded = xmalloc(size);
+
+ b64_ntop(buf, len, encoded, size);
+ tty->flags |= TTY_NOBLOCK;
+ tty_putcode_ptr2(tty, TTYC_MS, "", encoded);
+
+ free(encoded);
+}
+
+void
+tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx)
+{
+ tty->flags |= TTY_NOBLOCK;
+ tty_add(tty, ctx->ptr, ctx->num);
+ tty_invalidate(tty);
+}
+
+void
+tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
+{
+ if (ctx->num == 0x11) {
+ /*
+ * This is an overlay and a command that moves the cursor so
+ * start synchronized updates.
+ */
+ tty_sync_start(tty);
+ } else if (~ctx->num & 0x10) {
+ /*
+ * This is a pane. If there is an overlay, always start;
+ * otherwise, only if requested.
+ */
+ if (ctx->num || tty->client->overlay_draw != NULL)
+ tty_sync_start(tty);
+ }
+}
+
+void
+tty_cell(struct tty *tty, const struct grid_cell *gc,
+ const struct grid_cell *defaults, struct colour_palette *palette)
+{
+ const struct grid_cell *gcp;
+
+ /* Skip last character if terminal is stupid. */
+ if ((tty->term->flags & TERM_NOAM) &&
+ tty->cy == tty->sy - 1 &&
+ tty->cx == tty->sx - 1)
+ return;
+
+ /* If this is a padding character, do nothing. */
+ if (gc->flags & GRID_FLAG_PADDING)
+ return;
+
+ /* Check the output codeset and apply attributes. */
+ gcp = tty_check_codeset(tty, gc);
+ tty_attributes(tty, gcp, defaults, palette);
+
+ /* If it is a single character, write with putc to handle ACS. */
+ if (gcp->data.size == 1) {
+ tty_attributes(tty, gcp, defaults, palette);
+ if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f)
+ return;
+ tty_putc(tty, *gcp->data.data);
+ return;
+ }
+
+ /* Write the data. */
+ tty_putn(tty, gcp->data.data, gcp->data.size, gcp->data.width);
+}
+
+void
+tty_reset(struct tty *tty)
+{
+ struct grid_cell *gc = &tty->cell;
+
+ if (!grid_cells_equal(gc, &grid_default_cell)) {
+ if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty))
+ tty_putcode(tty, TTYC_RMACS);
+ tty_putcode(tty, TTYC_SGR0);
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ }
+ memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell);
+}
+
+static void
+tty_invalidate(struct tty *tty)
+{
+ memcpy(&tty->cell, &grid_default_cell, sizeof tty->cell);
+ memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell);
+
+ tty->cx = tty->cy = UINT_MAX;
+ tty->rupper = tty->rleft = UINT_MAX;
+ tty->rlower = tty->rright = UINT_MAX;
+
+ if (tty->flags & TTY_STARTED) {
+ if (tty_use_margin(tty))
+ tty_putcode(tty, TTYC_ENMG);
+ tty_putcode(tty, TTYC_SGR0);
+
+ tty->mode = ALL_MODES;
+ tty_update_mode(tty, MODE_CURSOR, NULL);
+
+ tty_cursor(tty, 0, 0);
+ tty_region_off(tty);
+ tty_margin_off(tty);
+ } else
+ tty->mode = MODE_CURSOR;
+}
+
+/* Turn off margin. */
+void
+tty_region_off(struct tty *tty)
+{
+ tty_region(tty, 0, tty->sy - 1);
+}
+
+/* Set region inside pane. */
+static void
+tty_region_pane(struct tty *tty, const struct tty_ctx *ctx, u_int rupper,
+ u_int rlower)
+{
+ tty_region(tty, ctx->yoff + rupper - ctx->woy,
+ ctx->yoff + rlower - ctx->woy);
+}
+
+/* Set region at absolute position. */
+static void
+tty_region(struct tty *tty, u_int rupper, u_int rlower)
+{
+ if (tty->rlower == rlower && tty->rupper == rupper)
+ return;
+ if (!tty_term_has(tty->term, TTYC_CSR))
+ return;
+
+ tty->rupper = rupper;
+ tty->rlower = rlower;
+
+ /*
+ * Some terminals (such as PuTTY) do not correctly reset the cursor to
+ * 0,0 if it is beyond the last column (they do not reset their wrap
+ * flag so further output causes a line feed). As a workaround, do an
+ * explicit move to 0 first.
+ */
+ if (tty->cx >= tty->sx) {
+ if (tty->cy == UINT_MAX)
+ tty_cursor(tty, 0, 0);
+ else
+ tty_cursor(tty, 0, tty->cy);
+ }
+
+ tty_putcode2(tty, TTYC_CSR, tty->rupper, tty->rlower);
+ tty->cx = tty->cy = UINT_MAX;
+}
+
+/* Turn off margin. */
+void
+tty_margin_off(struct tty *tty)
+{
+ tty_margin(tty, 0, tty->sx - 1);
+}
+
+/* Set margin inside pane. */
+static void
+tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx)
+{
+ tty_margin(tty, ctx->xoff - ctx->wox,
+ ctx->xoff + ctx->sx - 1 - ctx->wox);
+}
+
+/* Set margin at absolute position. */
+static void
+tty_margin(struct tty *tty, u_int rleft, u_int rright)
+{
+ if (!tty_use_margin(tty))
+ return;
+ if (tty->rleft == rleft && tty->rright == rright)
+ return;
+
+ tty_putcode2(tty, TTYC_CSR, tty->rupper, tty->rlower);
+
+ tty->rleft = rleft;
+ tty->rright = rright;
+
+ if (rleft == 0 && rright == tty->sx - 1)
+ tty_putcode(tty, TTYC_CLMG);
+ else
+ tty_putcode2(tty, TTYC_CMG, rleft, rright);
+ tty->cx = tty->cy = UINT_MAX;
+}
+
+/*
+ * Move the cursor, unless it would wrap itself when the next character is
+ * printed.
+ */
+static void
+tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx,
+ u_int cx, u_int cy)
+{
+ if (!ctx->wrapped ||
+ !tty_full_width(tty, ctx) ||
+ (tty->term->flags & TERM_NOAM) ||
+ ctx->xoff + cx != 0 ||
+ ctx->yoff + cy != tty->cy + 1 ||
+ tty->cx < tty->sx ||
+ tty->cy == tty->rlower)
+ tty_cursor_pane(tty, ctx, cx, cy);
+ else
+ log_debug("%s: will wrap at %u,%u", __func__, tty->cx, tty->cy);
+}
+
+/* Move cursor inside pane. */
+static void
+tty_cursor_pane(struct tty *tty, const struct tty_ctx *ctx, u_int cx, u_int cy)
+{
+ tty_cursor(tty, ctx->xoff + cx - ctx->wox, ctx->yoff + cy - ctx->woy);
+}
+
+/* Move cursor to absolute position. */
+void
+tty_cursor(struct tty *tty, u_int cx, u_int cy)
+{
+ struct tty_term *term = tty->term;
+ u_int thisx, thisy;
+ int change;
+
+ if (tty->flags & TTY_BLOCK)
+ return;
+
+ thisx = tty->cx;
+ thisy = tty->cy;
+
+ /*
+ * If in the automargin space, and want to be there, do not move.
+ * Otherwise, force the cursor to be in range (and complain).
+ */
+ if (cx == thisx && cy == thisy && cx == tty->sx)
+ return;
+ if (cx > tty->sx - 1) {
+ log_debug("%s: x too big %u > %u", __func__, cx, tty->sx - 1);
+ cx = tty->sx - 1;
+ }
+
+ /* No change. */
+ if (cx == thisx && cy == thisy)
+ return;
+
+ /* Currently at the very end of the line - use absolute movement. */
+ if (thisx > tty->sx - 1)
+ goto absolute;
+
+ /* Move to home position (0, 0). */
+ if (cx == 0 && cy == 0 && tty_term_has(term, TTYC_HOME)) {
+ tty_putcode(tty, TTYC_HOME);
+ goto out;
+ }
+
+ /* Zero on the next line. */
+ if (cx == 0 && cy == thisy + 1 && thisy != tty->rlower &&
+ (!tty_use_margin(tty) || tty->rleft == 0)) {
+ tty_putc(tty, '\r');
+ tty_putc(tty, '\n');
+ goto out;
+ }
+
+ /* Moving column or row. */
+ if (cy == thisy) {
+ /*
+ * Moving column only, row staying the same.
+ */
+
+ /* To left edge. */
+ if (cx == 0 && (!tty_use_margin(tty) || tty->rleft == 0)) {
+ tty_putc(tty, '\r');
+ goto out;
+ }
+
+ /* One to the left. */
+ if (cx == thisx - 1 && tty_term_has(term, TTYC_CUB1)) {
+ tty_putcode(tty, TTYC_CUB1);
+ goto out;
+ }
+
+ /* One to the right. */
+ if (cx == thisx + 1 && tty_term_has(term, TTYC_CUF1)) {
+ tty_putcode(tty, TTYC_CUF1);
+ goto out;
+ }
+
+ /* Calculate difference. */
+ change = thisx - cx; /* +ve left, -ve right */
+
+ /*
+ * Use HPA if change is larger than absolute, otherwise move
+ * the cursor with CUB/CUF.
+ */
+ if ((u_int) abs(change) > cx && tty_term_has(term, TTYC_HPA)) {
+ tty_putcode1(tty, TTYC_HPA, cx);
+ goto out;
+ } else if (change > 0 &&
+ tty_term_has(term, TTYC_CUB) &&
+ !tty_use_margin(tty)) {
+ if (change == 2 && tty_term_has(term, TTYC_CUB1)) {
+ tty_putcode(tty, TTYC_CUB1);
+ tty_putcode(tty, TTYC_CUB1);
+ goto out;
+ }
+ tty_putcode1(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);
+ goto out;
+ }
+ } else if (cx == thisx) {
+ /*
+ * Moving row only, column staying the same.
+ */
+
+ /* One above. */
+ if (thisy != tty->rupper &&
+ cy == thisy - 1 && tty_term_has(term, TTYC_CUU1)) {
+ tty_putcode(tty, TTYC_CUU1);
+ goto out;
+ }
+
+ /* One below. */
+ if (thisy != tty->rlower &&
+ cy == thisy + 1 && tty_term_has(term, TTYC_CUD1)) {
+ tty_putcode(tty, TTYC_CUD1);
+ goto out;
+ }
+
+ /* Calculate difference. */
+ change = thisy - cy; /* +ve up, -ve down */
+
+ /*
+ * Try to use VPA if change is larger than absolute or if this
+ * change would cross the scroll region, otherwise use CUU/CUD.
+ */
+ if ((u_int) abs(change) > 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);
+ goto out;
+ }
+ } else if (change > 0 && tty_term_has(term, TTYC_CUU)) {
+ tty_putcode1(tty, TTYC_CUU, change);
+ goto out;
+ } else if (change < 0 && tty_term_has(term, TTYC_CUD)) {
+ tty_putcode1(tty, TTYC_CUD, -change);
+ goto out;
+ }
+ }
+
+absolute:
+ /* Absolute movement. */
+ tty_putcode2(tty, TTYC_CUP, cy, cx);
+
+out:
+ tty->cx = cx;
+ tty->cy = cy;
+}
+
+void
+tty_attributes(struct tty *tty, const struct grid_cell *gc,
+ const struct grid_cell *defaults, struct colour_palette *palette)
+{
+ struct grid_cell *tc = &tty->cell, gc2;
+ int changed;
+
+ /* Copy cell and update default colours. */
+ memcpy(&gc2, gc, sizeof gc2);
+ if (~gc->flags & GRID_FLAG_NOPALETTE) {
+ if (gc2.fg == 8)
+ gc2.fg = defaults->fg;
+ if (gc2.bg == 8)
+ gc2.bg = defaults->bg;
+ }
+
+ /* Ignore cell if it is the same as the last one. */
+ 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)
+ return;
+
+ /*
+ * If no setab, try to use the reverse attribute as a best-effort for a
+ * non-default background. This is a bit of a hack but it doesn't do
+ * any serious harm and makes a couple of applications happier.
+ */
+ if (!tty_term_has(tty->term, TTYC_SETAB)) {
+ if (gc2.attr & GRID_ATTR_REVERSE) {
+ if (gc2.fg != 7 && !COLOUR_DEFAULT(gc2.fg))
+ gc2.attr &= ~GRID_ATTR_REVERSE;
+ } else {
+ if (gc2.bg != 0 && !COLOUR_DEFAULT(gc2.bg))
+ gc2.attr |= GRID_ATTR_REVERSE;
+ }
+ }
+
+ /* Fix up the colours if necessary. */
+ tty_check_fg(tty, palette, &gc2);
+ tty_check_bg(tty, palette, &gc2);
+ tty_check_us(tty, palette, &gc2);
+
+ /*
+ * If any bits are being cleared or the underline colour is now default,
+ * reset everything.
+ */
+ if ((tc->attr & ~gc2.attr) || (tc->us != gc2.us && gc2.us == 0))
+ tty_reset(tty);
+
+ /*
+ * Set the colours. This may call tty_reset() (so it comes next) and
+ * may add to (NOT remove) the desired attributes.
+ */
+ tty_colours(tty, &gc2);
+
+ /* Filter out attribute bits already set. */
+ changed = gc2.attr & ~tc->attr;
+ tc->attr = gc2.attr;
+
+ /* Set the attributes. */
+ if (changed & GRID_ATTR_BRIGHT)
+ tty_putcode(tty, TTYC_BOLD);
+ if (changed & GRID_ATTR_DIM)
+ tty_putcode(tty, TTYC_DIM);
+ if (changed & GRID_ATTR_ITALICS)
+ tty_set_italics(tty);
+ if (changed & GRID_ATTR_ALL_UNDERSCORE) {
+ if ((changed & GRID_ATTR_UNDERSCORE) ||
+ !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);
+ else if (changed & GRID_ATTR_UNDERSCORE_3)
+ tty_putcode1(tty, TTYC_SMULX, 3);
+ else if (changed & GRID_ATTR_UNDERSCORE_4)
+ tty_putcode1(tty, TTYC_SMULX, 4);
+ else if (changed & GRID_ATTR_UNDERSCORE_5)
+ tty_putcode1(tty, TTYC_SMULX, 5);
+ }
+ if (changed & GRID_ATTR_BLINK)
+ tty_putcode(tty, TTYC_BLINK);
+ if (changed & GRID_ATTR_REVERSE) {
+ if (tty_term_has(tty->term, TTYC_REV))
+ tty_putcode(tty, TTYC_REV);
+ else if (tty_term_has(tty->term, TTYC_SMSO))
+ tty_putcode(tty, TTYC_SMSO);
+ }
+ if (changed & GRID_ATTR_HIDDEN)
+ tty_putcode(tty, TTYC_INVIS);
+ if (changed & GRID_ATTR_STRIKETHROUGH)
+ tty_putcode(tty, TTYC_SMXX);
+ if (changed & GRID_ATTR_OVERLINE)
+ tty_putcode(tty, TTYC_SMOL);
+ if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty))
+ tty_putcode(tty, TTYC_SMACS);
+
+ memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell);
+}
+
+static void
+tty_colours(struct tty *tty, const struct grid_cell *gc)
+{
+ struct grid_cell *tc = &tty->cell;
+ int have_ax;
+
+ /* No changes? Nothing is necessary. */
+ if (gc->fg == tc->fg && gc->bg == tc->bg && gc->us == tc->us)
+ return;
+
+ /*
+ * Is either the default colour? This is handled specially because the
+ * best solution might be to reset both colours to default, in which
+ * case if only one is default need to fall onward to set the other
+ * colour.
+ */
+ if (COLOUR_DEFAULT(gc->fg) || COLOUR_DEFAULT(gc->bg)) {
+ /*
+ * If don't have AX but do have op, send sgr0 (op can't
+ * actually be used because it is sometimes the same as sgr0
+ * and sometimes isn't). This resets both colours to default.
+ *
+ * Otherwise, try to set the default colour only as needed.
+ */
+ have_ax = tty_term_flag(tty->term, TTYC_AX);
+ if (!have_ax && tty_term_has(tty->term, TTYC_OP))
+ tty_reset(tty);
+ else {
+ if (COLOUR_DEFAULT(gc->fg) && !COLOUR_DEFAULT(tc->fg)) {
+ if (have_ax)
+ tty_puts(tty, "\033[39m");
+ else if (tc->fg != 7)
+ tty_putcode1(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);
+ tc->bg = gc->bg;
+ }
+ }
+ }
+
+ /* Set the foreground colour. */
+ if (!COLOUR_DEFAULT(gc->fg) && gc->fg != tc->fg)
+ tty_colours_fg(tty, gc);
+
+ /*
+ * Set the background colour. This must come after the foreground as
+ * tty_colour_fg() can call tty_reset().
+ */
+ if (!COLOUR_DEFAULT(gc->bg) && gc->bg != tc->bg)
+ tty_colours_bg(tty, gc);
+
+ /* Set the underscore colour. */
+ if (gc->us != tc->us)
+ tty_colours_us(tty, gc);
+}
+
+static void
+tty_check_fg(struct tty *tty, struct colour_palette *palette,
+ struct grid_cell *gc)
+{
+ u_char r, g, b;
+ u_int colours;
+ int c;
+
+ /*
+ * 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.
+ */
+ if (~gc->flags & GRID_FLAG_NOPALETTE) {
+ c = gc->fg;
+ if (c < 8 && gc->attr & GRID_ATTR_BRIGHT)
+ c += 90;
+ if ((c = colour_palette_get(palette, c)) != -1)
+ gc->fg = c;
+ }
+
+ /* Is this a 24-bit colour? */
+ if (gc->fg & COLOUR_FLAG_RGB) {
+ /* Not a 24-bit terminal? Translate to 256-colour palette. */
+ if (tty->term->flags & TERM_RGBCOLOURS)
+ return;
+ colour_split_rgb(gc->fg, &r, &g, &b);
+ gc->fg = colour_find_rgb(r, g, b);
+ }
+
+ /* How many colours does this terminal have? */
+ if (tty->term->flags & TERM_256COLOURS)
+ colours = 256;
+ else
+ colours = tty_term_number(tty->term, TTYC_COLORS);
+
+ /* Is this a 256-colour colour? */
+ if (gc->fg & COLOUR_FLAG_256) {
+ /* And not a 256 colour mode? */
+ if (colours < 256) {
+ gc->fg = colour_256to16(gc->fg);
+ if (gc->fg & 8) {
+ gc->fg &= 7;
+ if (colours >= 16)
+ gc->fg += 90;
+ }
+ }
+ return;
+ }
+
+ /* Is this an aixterm colour? */
+ if (gc->fg >= 90 && gc->fg <= 97 && colours < 16) {
+ gc->fg -= 90;
+ gc->attr |= GRID_ATTR_BRIGHT;
+ }
+}
+
+static void
+tty_check_bg(struct tty *tty, struct colour_palette *palette,
+ struct grid_cell *gc)
+{
+ u_char r, g, b;
+ u_int colours;
+ int c;
+
+ /* Perform substitution if this pane has a palette. */
+ if (~gc->flags & GRID_FLAG_NOPALETTE) {
+ if ((c = colour_palette_get(palette, gc->bg)) != -1)
+ gc->bg = c;
+ }
+
+ /* Is this a 24-bit colour? */
+ if (gc->bg & COLOUR_FLAG_RGB) {
+ /* Not a 24-bit terminal? Translate to 256-colour palette. */
+ if (tty->term->flags & TERM_RGBCOLOURS)
+ return;
+ colour_split_rgb(gc->bg, &r, &g, &b);
+ gc->bg = colour_find_rgb(r, g, b);
+ }
+
+ /* How many colours does this terminal have? */
+ if (tty->term->flags & TERM_256COLOURS)
+ colours = 256;
+ else
+ colours = tty_term_number(tty->term, TTYC_COLORS);
+
+ /* Is this a 256-colour colour? */
+ if (gc->bg & COLOUR_FLAG_256) {
+ /*
+ * And not a 256 colour mode? Translate to 16-colour
+ * palette. Bold background doesn't exist portably, so just
+ * discard the bold bit if set.
+ */
+ if (colours < 256) {
+ gc->bg = colour_256to16(gc->bg);
+ if (gc->bg & 8) {
+ gc->bg &= 7;
+ if (colours >= 16)
+ gc->bg += 90;
+ }
+ }
+ return;
+ }
+
+ /* Is this an aixterm colour? */
+ if (gc->bg >= 90 && gc->bg <= 97 && colours < 16)
+ gc->bg -= 90;
+}
+
+static void
+tty_check_us(__unused struct tty *tty, struct colour_palette *palette,
+ struct grid_cell *gc)
+{
+ int c;
+
+ /* Perform substitution if this pane has a palette. */
+ if (~gc->flags & GRID_FLAG_NOPALETTE) {
+ if ((c = colour_palette_get(palette, gc->us)) != -1)
+ 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);
+}
+
+static void
+tty_colours_fg(struct tty *tty, const struct grid_cell *gc)
+{
+ struct grid_cell *tc = &tty->cell;
+ char s[32];
+
+ /* Is this a 24-bit or 256-colour colour? */
+ if (gc->fg & COLOUR_FLAG_RGB || gc->fg & COLOUR_FLAG_256) {
+ if (tty_try_colour(tty, gc->fg, "38") == 0)
+ goto save;
+ /* Should not get here, already converted in tty_check_fg. */
+ return;
+ }
+
+ /* Is this an aixterm bright colour? */
+ if (gc->fg >= 90 && gc->fg <= 97) {
+ if (tty->term->flags & TERM_256COLOURS) {
+ xsnprintf(s, sizeof s, "\033[%dm", gc->fg);
+ tty_puts(tty, s);
+ } else
+ tty_putcode1(tty, TTYC_SETAF, gc->fg - 90 + 8);
+ goto save;
+ }
+
+ /* Otherwise set the foreground colour. */
+ tty_putcode1(tty, TTYC_SETAF, gc->fg);
+
+save:
+ /* Save the new values in the terminal current cell. */
+ tc->fg = gc->fg;
+}
+
+static void
+tty_colours_bg(struct tty *tty, const struct grid_cell *gc)
+{
+ struct grid_cell *tc = &tty->cell;
+ char s[32];
+
+ /* Is this a 24-bit or 256-colour colour? */
+ if (gc->bg & COLOUR_FLAG_RGB || gc->bg & COLOUR_FLAG_256) {
+ if (tty_try_colour(tty, gc->bg, "48") == 0)
+ goto save;
+ /* Should not get here, already converted in tty_check_bg. */
+ return;
+ }
+
+ /* Is this an aixterm bright colour? */
+ if (gc->bg >= 90 && gc->bg <= 97) {
+ if (tty->term->flags & TERM_256COLOURS) {
+ xsnprintf(s, sizeof s, "\033[%dm", gc->bg + 10);
+ tty_puts(tty, s);
+ } else
+ tty_putcode1(tty, TTYC_SETAB, gc->bg - 90 + 8);
+ goto save;
+ }
+
+ /* Otherwise set the background colour. */
+ tty_putcode1(tty, TTYC_SETAB, gc->bg);
+
+save:
+ /* Save the new values in the terminal current cell. */
+ tc->bg = gc->bg;
+}
+
+static void
+tty_colours_us(struct tty *tty, const struct grid_cell *gc)
+{
+ struct grid_cell *tc = &tty->cell;
+ u_int c;
+ u_char r, g, b;
+
+ /* Clear underline colour. */
+ if (gc->us == 0) {
+ tty_putcode(tty, TTYC_OL);
+ goto save;
+ }
+
+ /* Must be an RGB colour - this should never happen. */
+ if (~gc->us & COLOUR_FLAG_RGB)
+ return;
+
+ /*
+ * Setulc and setal follows the ncurses(3) one argument "direct colour"
+ * capability format. Calculate the colour value.
+ */
+ colour_split_rgb(gc->us, &r, &g, &b);
+ c = (65536 * r) + (256 * g) + b;
+
+ /*
+ * Write the colour. Only use setal if the RGB flag is set because the
+ * non-RGB version may be wrong.
+ */
+ if (tty_term_has(tty->term, TTYC_SETULC))
+ tty_putcode1(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);
+
+save:
+ /* Save the new values in the terminal current cell. */
+ tc->us = gc->us;
+}
+
+static int
+tty_try_colour(struct tty *tty, int colour, const char *type)
+{
+ u_char r, g, b;
+
+ if (colour & COLOUR_FLAG_256) {
+ if (*type == '3' && tty_term_has(tty->term, TTYC_SETAF))
+ tty_putcode1(tty, TTYC_SETAF, colour & 0xff);
+ else if (tty_term_has(tty->term, TTYC_SETAB))
+ tty_putcode1(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);
+ else if (tty_term_has(tty->term, TTYC_SETRGBB))
+ tty_putcode3(tty, TTYC_SETRGBB, r, g, b);
+ return (0);
+ }
+
+ return (-1);
+}
+
+static void
+tty_window_default_style(struct grid_cell *gc, struct window_pane *wp)
+{
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+ gc->fg = wp->palette.fg;
+ gc->bg = wp->palette.bg;
+}
+
+void
+tty_default_colours(struct grid_cell *gc, struct window_pane *wp)
+{
+ struct options *oo = wp->options;
+ struct format_tree *ft;
+
+ memcpy(gc, &grid_default_cell, sizeof *gc);
+
+ if (wp->flags & PANE_STYLECHANGED) {
+ log_debug("%%%u: style changed", wp->id);
+ wp->flags &= ~PANE_STYLECHANGED;
+
+ ft = format_create(NULL, NULL, FORMAT_PANE|wp->id,
+ FORMAT_NOJOBS);
+ format_defaults(ft, NULL, NULL, NULL, wp);
+ tty_window_default_style(&wp->cached_active_gc, wp);
+ style_add(&wp->cached_active_gc, oo, "window-active-style", ft);
+ tty_window_default_style(&wp->cached_gc, wp);
+ style_add(&wp->cached_gc, oo, "window-style", ft);
+ format_free(ft);
+ }
+
+ if (gc->fg == 8) {
+ if (wp == wp->window->active && wp->cached_active_gc.fg != 8)
+ gc->fg = wp->cached_active_gc.fg;
+ else
+ gc->fg = wp->cached_gc.fg;
+ }
+
+ if (gc->bg == 8) {
+ if (wp == wp->window->active && wp->cached_active_gc.bg != 8)
+ gc->bg = wp->cached_active_gc.bg;
+ else
+ gc->bg = wp->cached_gc.bg;
+ }
+}
+
+static void
+tty_default_attributes(struct tty *tty, const struct grid_cell *defaults,
+ struct colour_palette *palette, u_int bg)
+{
+ struct grid_cell gc;
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.bg = bg;
+ tty_attributes(tty, &gc, defaults, palette);
+}
+
+static void
+tty_clipboard_query_callback(__unused int fd, __unused short events, void *data)
+{
+ struct tty *tty = data;
+ struct client *c = tty->client;
+
+ c->flags &= ~CLIENT_CLIPBOARDBUFFER;
+ free(c->clipboard_panes);
+ c->clipboard_panes = NULL;
+ c->clipboard_npanes = 0;
+
+ tty->flags &= ~TTY_OSC52QUERY;
+}
+
+void
+tty_clipboard_query(struct tty *tty)
+{
+ struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT };
+
+ if ((~tty->flags & TTY_STARTED) || (tty->flags & TTY_OSC52QUERY))
+ return;
+ tty_putcode_ptr2(tty, TTYC_MS, "", "?");
+
+ tty->flags |= TTY_OSC52QUERY;
+ evtimer_set(&tty->clipboard_timer, tty_clipboard_query_callback, tty);
+ evtimer_add(&tty->clipboard_timer, &tv);
+}
diff --git a/utf8.c b/utf8.c
new file mode 100644
index 0000000..df75a76
--- /dev/null
+++ b/utf8.c
@@ -0,0 +1,586 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2008 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 <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "tmux.h"
+
+struct utf8_item {
+ RB_ENTRY(utf8_item) index_entry;
+ u_int index;
+
+ RB_ENTRY(utf8_item) data_entry;
+ char data[UTF8_SIZE];
+ u_char size;
+};
+
+static int
+utf8_data_cmp(struct utf8_item *ui1, struct utf8_item *ui2)
+{
+ if (ui1->size < ui2->size)
+ return (-1);
+ if (ui1->size > ui2->size)
+ return (1);
+ return (memcmp(ui1->data, ui2->data, ui1->size));
+}
+RB_HEAD(utf8_data_tree, utf8_item);
+RB_GENERATE_STATIC(utf8_data_tree, utf8_item, data_entry, utf8_data_cmp);
+static struct utf8_data_tree utf8_data_tree = RB_INITIALIZER(utf8_data_tree);
+
+static int
+utf8_index_cmp(struct utf8_item *ui1, struct utf8_item *ui2)
+{
+ if (ui1->index < ui2->index)
+ return (-1);
+ if (ui1->index > ui2->index)
+ return (1);
+ return (0);
+}
+RB_HEAD(utf8_index_tree, utf8_item);
+RB_GENERATE_STATIC(utf8_index_tree, utf8_item, index_entry, utf8_index_cmp);
+static struct utf8_index_tree utf8_index_tree = RB_INITIALIZER(utf8_index_tree);
+
+static u_int utf8_next_index;
+
+#define UTF8_GET_SIZE(uc) (((uc) >> 24) & 0x1f)
+#define UTF8_GET_WIDTH(uc) (((uc) >> 29) - 1)
+
+#define UTF8_SET_SIZE(size) (((utf8_char)(size)) << 24)
+#define UTF8_SET_WIDTH(width) ((((utf8_char)(width)) + 1) << 29)
+
+/* Get a UTF-8 item from data. */
+static struct utf8_item *
+utf8_item_by_data(const char *data, size_t size)
+{
+ struct utf8_item ui;
+
+ memcpy(ui.data, data, size);
+ ui.size = size;
+
+ return (RB_FIND(utf8_data_tree, &utf8_data_tree, &ui));
+}
+
+/* Get a UTF-8 item from data. */
+static struct utf8_item *
+utf8_item_by_index(u_int index)
+{
+ struct utf8_item ui;
+
+ ui.index = index;
+
+ return (RB_FIND(utf8_index_tree, &utf8_index_tree, &ui));
+}
+
+/* Add a UTF-8 item. */
+static int
+utf8_put_item(const char *data, size_t size, u_int *index)
+{
+ struct utf8_item *ui;
+
+ ui = utf8_item_by_data(data, size);
+ if (ui != NULL) {
+ *index = ui->index;
+ log_debug("%s: found %.*s = %u", __func__, (int)size, data,
+ *index);
+ return (0);
+ }
+
+ if (utf8_next_index == 0xffffff + 1)
+ return (-1);
+
+ ui = xcalloc(1, sizeof *ui);
+ ui->index = utf8_next_index++;
+ RB_INSERT(utf8_index_tree, &utf8_index_tree, ui);
+
+ memcpy(ui->data, data, size);
+ ui->size = size;
+ RB_INSERT(utf8_data_tree, &utf8_data_tree, ui);
+
+ *index = ui->index;
+ log_debug("%s: added %.*s = %u", __func__, (int)size, data, *index);
+ return (0);
+}
+
+/* Get UTF-8 character from data. */
+enum utf8_state
+utf8_from_data(const struct utf8_data *ud, utf8_char *uc)
+{
+ u_int index;
+
+ if (ud->width > 2)
+ fatalx("invalid UTF-8 width: %u", ud->width);
+
+ if (ud->size > UTF8_SIZE)
+ goto fail;
+ if (ud->size <= 3) {
+ index = (((utf8_char)ud->data[2] << 16)|
+ ((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;
+ log_debug("%s: (%d %d %.*s) -> %08x", __func__, ud->width, ud->size,
+ (int)ud->size, ud->data, *uc);
+ return (UTF8_DONE);
+
+fail:
+ if (ud->width == 0)
+ *uc = UTF8_SET_SIZE(0)|UTF8_SET_WIDTH(0);
+ else if (ud->width == 1)
+ *uc = UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|0x20;
+ else
+ *uc = UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|0x2020;
+ return (UTF8_ERROR);
+}
+
+/* Get UTF-8 data from character. */
+void
+utf8_to_data(utf8_char uc, struct utf8_data *ud)
+{
+ struct utf8_item *ui;
+ u_int index;
+
+ memset(ud, 0, sizeof *ud);
+ ud->size = ud->have = UTF8_GET_SIZE(uc);
+ ud->width = UTF8_GET_WIDTH(uc);
+
+ if (ud->size <= 3) {
+ ud->data[2] = (uc >> 16);
+ ud->data[1] = ((uc >> 8) & 0xff);
+ ud->data[0] = (uc & 0xff);
+ } else {
+ index = (uc & 0xffffff);
+ if ((ui = utf8_item_by_index(index)) == NULL)
+ memset(ud->data, ' ', ud->size);
+ else
+ memcpy(ud->data, ui->data, ud->size);
+ }
+
+ log_debug("%s: %08x -> (%d %d %.*s)", __func__, uc, ud->width, ud->size,
+ (int)ud->size, ud->data);
+}
+
+/* Get UTF-8 character from a single ASCII character. */
+u_int
+utf8_build_one(u_char ch)
+{
+ return (UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|ch);
+}
+
+/* Set a single character. */
+void
+utf8_set(struct utf8_data *ud, u_char ch)
+{
+ static const struct utf8_data empty = { { 0 }, 1, 1, 1 };
+
+ memcpy(ud, &empty, sizeof *ud);
+ *ud->data = ch;
+}
+
+/* Copy UTF-8 character. */
+void
+utf8_copy(struct utf8_data *to, const struct utf8_data *from)
+{
+ u_int i;
+
+ memcpy(to, from, sizeof *to);
+
+ for (i = to->size; i < sizeof to->data; i++)
+ to->data[i] = '\0';
+}
+
+/* Get width of Unicode character. */
+static enum utf8_state
+utf8_width(struct utf8_data *ud, int *width)
+{
+ wchar_t wc;
+
+#ifdef HAVE_UTF8PROC
+ switch (utf8proc_mbtowc(&wc, ud->data, ud->size)) {
+#else
+ switch (mbtowc(&wc, ud->data, ud->size)) {
+#endif
+ case -1:
+ log_debug("UTF-8 %.*s, mbtowc() %d", (int)ud->size, ud->data,
+ errno);
+ mbtowc(NULL, NULL, MB_CUR_MAX);
+ return (UTF8_ERROR);
+ 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);
+}
+
+/*
+ * Open UTF-8 sequence.
+ *
+ * 11000010-11011111 C2-DF start of 2-byte sequence
+ * 11100000-11101111 E0-EF start of 3-byte sequence
+ * 11110000-11110100 F0-F4 start of 4-byte sequence
+ */
+enum utf8_state
+utf8_open(struct utf8_data *ud, u_char ch)
+{
+ memset(ud, 0, sizeof *ud);
+ if (ch >= 0xc2 && ch <= 0xdf)
+ ud->size = 2;
+ else if (ch >= 0xe0 && ch <= 0xef)
+ ud->size = 3;
+ else if (ch >= 0xf0 && ch <= 0xf4)
+ ud->size = 4;
+ else
+ return (UTF8_ERROR);
+ utf8_append(ud, ch);
+ return (UTF8_MORE);
+}
+
+/* Append character to UTF-8, closing if finished. */
+enum utf8_state
+utf8_append(struct utf8_data *ud, u_char ch)
+{
+ int width;
+
+ if (ud->have >= ud->size)
+ fatalx("UTF-8 character overflow");
+ if (ud->size > sizeof ud->data)
+ fatalx("UTF-8 character size too large");
+
+ if (ud->have != 0 && (ch & 0xc0) != 0x80)
+ ud->width = 0xff;
+
+ ud->data[ud->have++] = ch;
+ if (ud->have != ud->size)
+ return (UTF8_MORE);
+
+ if (ud->width == 0xff)
+ return (UTF8_ERROR);
+ if (utf8_width(ud, &width) != UTF8_DONE)
+ return (UTF8_ERROR);
+ ud->width = width;
+
+ return (UTF8_DONE);
+}
+
+/*
+ * Encode len characters from src into dst, which is guaranteed to have four
+ * bytes available for each character from src (for \abc or UTF-8) plus space
+ * for \0.
+ */
+int
+utf8_strvis(char *dst, const char *src, size_t len, int flag)
+{
+ struct utf8_data ud;
+ const char *start = dst, *end = src + len;
+ enum utf8_state more;
+ size_t i;
+
+ while (src < end) {
+ if ((more = utf8_open(&ud, *src)) == UTF8_MORE) {
+ while (++src < end && more == UTF8_MORE)
+ more = utf8_append(&ud, *src);
+ if (more == UTF8_DONE) {
+ /* UTF-8 character finished. */
+ for (i = 0; i < ud.size; i++)
+ *dst++ = ud.data[i];
+ continue;
+ }
+ /* Not a complete, valid UTF-8 character. */
+ src -= ud.have;
+ }
+ if (src[0] == '$' && src < end - 1) {
+ if (isalpha((u_char)src[1]) ||
+ src[1] == '_' ||
+ src[1] == '{')
+ *dst++ = '\\';
+ *dst++ = '$';
+ } else if (src < end - 1)
+ dst = vis(dst, src[0], flag, src[1]);
+ else if (src < end)
+ dst = vis(dst, src[0], flag, '\0');
+ src++;
+ }
+ *dst = '\0';
+ return (dst - start);
+}
+
+/* Same as utf8_strvis but allocate the buffer. */
+int
+utf8_stravis(char **dst, const char *src, int flag)
+{
+ char *buf;
+ int len;
+
+ buf = xreallocarray(NULL, 4, strlen(src) + 1);
+ len = utf8_strvis(buf, src, strlen(src), flag);
+
+ *dst = xrealloc(buf, len + 1);
+ return (len);
+}
+
+/* Same as utf8_strvis but allocate the buffer. */
+int
+utf8_stravisx(char **dst, const char *src, size_t srclen, int flag)
+{
+ char *buf;
+ int len;
+
+ buf = xreallocarray(NULL, 4, srclen + 1);
+ len = utf8_strvis(buf, src, srclen, flag);
+
+ *dst = xrealloc(buf, len + 1);
+ return (len);
+}
+
+/* Does this string contain anything that isn't valid UTF-8? */
+int
+utf8_isvalid(const char *s)
+{
+ struct utf8_data ud;
+ const char *end;
+ enum utf8_state more;
+
+ end = s + strlen(s);
+ while (s < end) {
+ if ((more = utf8_open(&ud, *s)) == UTF8_MORE) {
+ while (++s < end && more == UTF8_MORE)
+ more = utf8_append(&ud, *s);
+ if (more == UTF8_DONE)
+ continue;
+ return (0);
+ }
+ if (*s < 0x20 || *s > 0x7e)
+ return (0);
+ s++;
+ }
+ return (1);
+}
+
+/*
+ * Sanitize a string, changing any UTF-8 characters to '_'. Caller should free
+ * the returned string. Anything not valid printable ASCII or UTF-8 is
+ * stripped.
+ */
+char *
+utf8_sanitize(const char *src)
+{
+ char *dst = NULL;
+ size_t n = 0;
+ enum utf8_state more;
+ struct utf8_data ud;
+ u_int i;
+
+ while (*src != '\0') {
+ dst = xreallocarray(dst, n + 1, sizeof *dst);
+ if ((more = utf8_open(&ud, *src)) == UTF8_MORE) {
+ while (*++src != '\0' && more == UTF8_MORE)
+ more = utf8_append(&ud, *src);
+ if (more == UTF8_DONE) {
+ dst = xreallocarray(dst, n + ud.width,
+ sizeof *dst);
+ for (i = 0; i < ud.width; i++)
+ dst[n++] = '_';
+ continue;
+ }
+ src -= ud.have;
+ }
+ if (*src > 0x1f && *src < 0x7f)
+ dst[n++] = *src;
+ else
+ dst[n++] = '_';
+ src++;
+ }
+ dst = xreallocarray(dst, n + 1, sizeof *dst);
+ dst[n] = '\0';
+ return (dst);
+}
+
+/* Get UTF-8 buffer length. */
+size_t
+utf8_strlen(const struct utf8_data *s)
+{
+ size_t i;
+
+ for (i = 0; s[i].size != 0; i++)
+ /* nothing */;
+ return (i);
+}
+
+/* Get UTF-8 string width. */
+u_int
+utf8_strwidth(const struct utf8_data *s, ssize_t n)
+{
+ ssize_t i;
+ u_int width = 0;
+
+ for (i = 0; s[i].size != 0; i++) {
+ if (n != -1 && n == i)
+ break;
+ width += s[i].width;
+ }
+ return (width);
+}
+
+/*
+ * Convert a string into a buffer of UTF-8 characters. Terminated by size == 0.
+ * Caller frees.
+ */
+struct utf8_data *
+utf8_fromcstr(const char *src)
+{
+ struct utf8_data *dst = NULL;
+ size_t n = 0;
+ enum utf8_state more;
+
+ while (*src != '\0') {
+ dst = xreallocarray(dst, n + 1, sizeof *dst);
+ if ((more = utf8_open(&dst[n], *src)) == UTF8_MORE) {
+ while (*++src != '\0' && more == UTF8_MORE)
+ more = utf8_append(&dst[n], *src);
+ if (more == UTF8_DONE) {
+ n++;
+ continue;
+ }
+ src -= dst[n].have;
+ }
+ utf8_set(&dst[n], *src);
+ n++;
+ src++;
+ }
+ dst = xreallocarray(dst, n + 1, sizeof *dst);
+ dst[n].size = 0;
+ return (dst);
+}
+
+/* Convert from a buffer of UTF-8 characters into a string. Caller frees. */
+char *
+utf8_tocstr(struct utf8_data *src)
+{
+ char *dst = NULL;
+ size_t n = 0;
+
+ for(; src->size != 0; src++) {
+ dst = xreallocarray(dst, n + src->size, 1);
+ memcpy(dst + n, src->data, src->size);
+ n += src->size;
+ }
+ dst = xreallocarray(dst, n + 1, 1);
+ dst[n] = '\0';
+ return (dst);
+}
+
+/* Get width of UTF-8 string. */
+u_int
+utf8_cstrwidth(const char *s)
+{
+ struct utf8_data tmp;
+ u_int width;
+ enum utf8_state more;
+
+ width = 0;
+ while (*s != '\0') {
+ if ((more = utf8_open(&tmp, *s)) == UTF8_MORE) {
+ while (*++s != '\0' && more == UTF8_MORE)
+ more = utf8_append(&tmp, *s);
+ if (more == UTF8_DONE) {
+ width += tmp.width;
+ continue;
+ }
+ s -= tmp.have;
+ }
+ if (*s > 0x1f && *s != 0x7f)
+ width++;
+ s++;
+ }
+ return (width);
+}
+
+/* Pad UTF-8 string to width on the left. Caller frees. */
+char *
+utf8_padcstr(const char *s, u_int width)
+{
+ size_t slen;
+ char *out;
+ u_int n, i;
+
+ n = utf8_cstrwidth(s);
+ if (n >= width)
+ return (xstrdup(s));
+
+ slen = strlen(s);
+ out = xmalloc(slen + 1 + (width - n));
+ memcpy(out, s, slen);
+ for (i = n; i < width; i++)
+ out[slen++] = ' ';
+ out[slen] = '\0';
+ return (out);
+}
+
+/* Pad UTF-8 string to width on the right. Caller frees. */
+char *
+utf8_rpadcstr(const char *s, u_int width)
+{
+ size_t slen;
+ char *out;
+ u_int n, i;
+
+ n = utf8_cstrwidth(s);
+ if (n >= width)
+ return (xstrdup(s));
+
+ slen = strlen(s);
+ out = xmalloc(slen + 1 + (width - n));
+ for (i = 0; i < width - n; i++)
+ out[i] = ' ';
+ memcpy(out + i, s, slen);
+ out[i + slen] = '\0';
+ return (out);
+}
+
+int
+utf8_cstrhas(const char *s, const struct utf8_data *ud)
+{
+ struct utf8_data *copy, *loop;
+ int found = 0;
+
+ copy = utf8_fromcstr(s);
+ for (loop = copy; loop->size != 0; loop++) {
+ if (loop->size != ud->size)
+ continue;
+ if (memcmp(loop->data, ud->data, loop->size) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ free(copy);
+
+ return (found);
+}
diff --git a/window-buffer.c b/window-buffer.c
new file mode 100644
index 0000000..2f52ee3
--- /dev/null
+++ b/window-buffer.c
@@ -0,0 +1,543 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2017 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static struct screen *window_buffer_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static void window_buffer_free(struct window_mode_entry *);
+static void window_buffer_resize(struct window_mode_entry *, u_int,
+ u_int);
+static void window_buffer_update(struct window_mode_entry *);
+static void window_buffer_key(struct window_mode_entry *,
+ struct client *, struct session *,
+ struct winlink *, key_code, struct mouse_event *);
+
+#define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'"
+
+#define WINDOW_BUFFER_DEFAULT_FORMAT \
+ "#{t/p:buffer_created}: #{buffer_sample}"
+
+#define WINDOW_BUFFER_DEFAULT_KEY_FORMAT \
+ "#{?#{e|<:#{line},10}," \
+ "#{line}" \
+ "," \
+ "#{?#{e|<:#{line},36}," \
+ "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
+ "," \
+ "" \
+ "}" \
+ "}"
+
+static const struct menu_item window_buffer_menu_items[] = {
+ { "Paste", 'p', NULL },
+ { "Paste Tagged", 'P', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Tag", 't', NULL },
+ { "Tag All", '\024', NULL },
+ { "Tag None", 'T', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Delete", 'd', NULL },
+ { "Delete Tagged", 'D', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Cancel", 'q', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+const struct window_mode window_buffer_mode = {
+ .name = "buffer-mode",
+ .default_format = WINDOW_BUFFER_DEFAULT_FORMAT,
+
+ .init = window_buffer_init,
+ .free = window_buffer_free,
+ .resize = window_buffer_resize,
+ .update = window_buffer_update,
+ .key = window_buffer_key,
+};
+
+enum window_buffer_sort_type {
+ WINDOW_BUFFER_BY_TIME,
+ WINDOW_BUFFER_BY_NAME,
+ WINDOW_BUFFER_BY_SIZE,
+};
+static const char *window_buffer_sort_list[] = {
+ "time",
+ "name",
+ "size"
+};
+static struct mode_tree_sort_criteria *window_buffer_sort;
+
+struct window_buffer_itemdata {
+ const char *name;
+ u_int order;
+ size_t size;
+};
+
+struct window_buffer_modedata {
+ struct window_pane *wp;
+ struct cmd_find_state fs;
+
+ struct mode_tree_data *data;
+ char *command;
+ char *format;
+ char *key_format;
+
+ struct window_buffer_itemdata **item_list;
+ u_int item_size;
+};
+
+struct window_buffer_editdata {
+ u_int wp_id;
+ char *name;
+ struct paste_buffer *pb;
+};
+
+static struct window_buffer_itemdata *
+window_buffer_add_item(struct window_buffer_modedata *data)
+{
+ struct window_buffer_itemdata *item;
+
+ data->item_list = xreallocarray(data->item_list, data->item_size + 1,
+ sizeof *data->item_list);
+ item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
+ return (item);
+}
+
+static void
+window_buffer_free_item(struct window_buffer_itemdata *item)
+{
+ free((void *)item->name);
+ free(item);
+}
+
+static int
+window_buffer_cmp(const void *a0, const void *b0)
+{
+ const struct window_buffer_itemdata *const *a = a0;
+ const struct window_buffer_itemdata *const *b = b0;
+ int result = 0;
+
+ if (window_buffer_sort->field == WINDOW_BUFFER_BY_TIME)
+ result = (*b)->order - (*a)->order;
+ else if (window_buffer_sort->field == WINDOW_BUFFER_BY_SIZE)
+ result = (*b)->size - (*a)->size;
+
+ /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */
+ if (result == 0)
+ result = strcmp((*a)->name, (*b)->name);
+
+ if (window_buffer_sort->reversed)
+ result = -result;
+ return (result);
+}
+
+static void
+window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
+ __unused uint64_t *tag, const char *filter)
+{
+ struct window_buffer_modedata *data = modedata;
+ struct window_buffer_itemdata *item;
+ u_int i;
+ struct paste_buffer *pb;
+ char *text, *cp;
+ struct format_tree *ft;
+ struct session *s = NULL;
+ struct winlink *wl = NULL;
+ struct window_pane *wp = NULL;
+
+ for (i = 0; i < data->item_size; i++)
+ window_buffer_free_item(data->item_list[i]);
+ free(data->item_list);
+ data->item_list = NULL;
+ data->item_size = 0;
+
+ pb = NULL;
+ while ((pb = paste_walk(pb)) != NULL) {
+ item = window_buffer_add_item(data);
+ item->name = xstrdup(paste_buffer_name(pb));
+ paste_buffer_data(pb, &item->size);
+ item->order = paste_buffer_order(pb);
+ }
+
+ window_buffer_sort = sort_crit;
+ qsort(data->item_list, data->item_size, sizeof *data->item_list,
+ window_buffer_cmp);
+
+ if (cmd_find_valid_state(&data->fs)) {
+ s = data->fs.s;
+ wl = data->fs.wl;
+ wp = data->fs.wp;
+ }
+
+ for (i = 0; i < data->item_size; i++) {
+ item = data->item_list[i];
+
+ pb = paste_get_name(item->name);
+ if (pb == NULL)
+ continue;
+ ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+ format_defaults(ft, NULL, s, wl, wp);
+ format_defaults_paste_buffer(ft, pb);
+
+ if (filter != NULL) {
+ cp = format_expand(ft, filter);
+ if (!format_true(cp)) {
+ free(cp);
+ format_free(ft);
+ continue;
+ }
+ free(cp);
+ }
+
+ text = format_expand(ft, data->format);
+ mode_tree_add(data->data, NULL, item, item->order, item->name,
+ text, -1);
+ free(text);
+
+ format_free(ft);
+ }
+
+}
+
+static void
+window_buffer_draw(__unused void *modedata, void *itemdata,
+ struct screen_write_ctx *ctx, u_int sx, u_int sy)
+{
+ struct window_buffer_itemdata *item = itemdata;
+ struct paste_buffer *pb;
+ const char *pdata, *start, *end;
+ char *buf = NULL;
+ size_t psize;
+ u_int i, cx = ctx->s->cx, cy = ctx->s->cy;
+
+ pb = paste_get_name(item->name);
+ if (pb == NULL)
+ return;
+
+ pdata = end = paste_buffer_data(pb, &psize);
+ for (i = 0; i < sy; i++) {
+ start = end;
+ while (end != pdata + psize && *end != '\n')
+ end++;
+ buf = xreallocarray(buf, 4, end - start + 1);
+ utf8_strvis(buf, start, end - start,
+ VIS_OCTAL|VIS_CSTYLE|VIS_TAB);
+ if (*buf != '\0') {
+ screen_write_cursormove(ctx, cx, cy + i, 0);
+ screen_write_nputs(ctx, sx, &grid_default_cell, "%s",
+ buf);
+ }
+
+ if (end == pdata + psize)
+ break;
+ end++;
+ }
+ free(buf);
+}
+
+static int
+window_buffer_search(__unused void *modedata, void *itemdata, const char *ss)
+{
+ struct window_buffer_itemdata *item = itemdata;
+ struct paste_buffer *pb;
+ const char *bufdata;
+ size_t bufsize;
+
+ if ((pb = paste_get_name(item->name)) == NULL)
+ return (0);
+ if (strstr(item->name, ss) != NULL)
+ return (1);
+ bufdata = paste_buffer_data(pb, &bufsize);
+ return (memmem(bufdata, bufsize, ss, strlen(ss)) != NULL);
+}
+
+static void
+window_buffer_menu(void *modedata, struct client *c, key_code key)
+{
+ struct window_buffer_modedata *data = modedata;
+ struct window_pane *wp = data->wp;
+ struct window_mode_entry *wme;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL || wme->data != modedata)
+ return;
+ window_buffer_key(wme, c, NULL, NULL, key, NULL);
+}
+
+static key_code
+window_buffer_get_key(void *modedata, void *itemdata, u_int line)
+{
+ struct window_buffer_modedata *data = modedata;
+ struct window_buffer_itemdata *item = itemdata;
+ struct format_tree *ft;
+ struct session *s = NULL;
+ struct winlink *wl = NULL;
+ struct window_pane *wp = NULL;
+ struct paste_buffer *pb;
+ char *expanded;
+ key_code key;
+
+ if (cmd_find_valid_state(&data->fs)) {
+ s = data->fs.s;
+ wl = data->fs.wl;
+ wp = data->fs.wp;
+ }
+ pb = paste_get_name(item->name);
+ if (pb == NULL)
+ return (KEYC_NONE);
+
+ ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+ format_defaults(ft, NULL, NULL, 0, NULL);
+ format_defaults(ft, NULL, s, wl, wp);
+ format_defaults_paste_buffer(ft, pb);
+ format_add(ft, "line", "%u", line);
+
+ expanded = format_expand(ft, data->key_format);
+ key = key_string_lookup_string(expanded);
+ free(expanded);
+ format_free(ft);
+ return (key);
+}
+
+static struct screen *
+window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
+ struct args *args)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_buffer_modedata *data;
+ struct screen *s;
+
+ wme->data = data = xcalloc(1, sizeof *data);
+ data->wp = wp;
+ cmd_find_copy_state(&data->fs, fs);
+
+ if (args == NULL || !args_has(args, 'F'))
+ data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT);
+ else
+ data->format = xstrdup(args_get(args, 'F'));
+ if (args == NULL || !args_has(args, 'K'))
+ data->key_format = xstrdup(WINDOW_BUFFER_DEFAULT_KEY_FORMAT);
+ else
+ data->key_format = xstrdup(args_get(args, 'K'));
+ if (args == NULL || args_count(args) == 0)
+ data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND);
+ else
+ data->command = xstrdup(args_string(args, 0));
+
+ data->data = mode_tree_start(wp, args, window_buffer_build,
+ window_buffer_draw, window_buffer_search, window_buffer_menu, NULL,
+ window_buffer_get_key, data, window_buffer_menu_items,
+ window_buffer_sort_list, nitems(window_buffer_sort_list), &s);
+ mode_tree_zoom(data->data, args);
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+
+ return (s);
+}
+
+static void
+window_buffer_free(struct window_mode_entry *wme)
+{
+ struct window_buffer_modedata *data = wme->data;
+ u_int i;
+
+ if (data == NULL)
+ return;
+
+ mode_tree_free(data->data);
+
+ for (i = 0; i < data->item_size; i++)
+ window_buffer_free_item(data->item_list[i]);
+ free(data->item_list);
+
+ free(data->format);
+ free(data->key_format);
+ free(data->command);
+
+ free(data);
+}
+
+static void
+window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
+{
+ struct window_buffer_modedata *data = wme->data;
+
+ mode_tree_resize(data->data, sx, sy);
+}
+
+static void
+window_buffer_update(struct window_mode_entry *wme)
+{
+ struct window_buffer_modedata *data = wme->data;
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+}
+
+static void
+window_buffer_do_delete(void *modedata, void *itemdata,
+ __unused struct client *c, __unused key_code key)
+{
+ struct window_buffer_modedata *data = modedata;
+ struct window_buffer_itemdata *item = itemdata;
+ struct paste_buffer *pb;
+
+ if (item == mode_tree_get_current(data->data))
+ mode_tree_down(data->data, 0);
+ if ((pb = paste_get_name(item->name)) != NULL)
+ paste_free(pb);
+}
+
+static void
+window_buffer_do_paste(void *modedata, void *itemdata, struct client *c,
+ __unused key_code key)
+{
+ struct window_buffer_modedata *data = modedata;
+ struct window_buffer_itemdata *item = itemdata;
+
+ if (paste_get_name(item->name) != NULL)
+ mode_tree_run_command(c, NULL, data->command, item->name);
+}
+
+static void
+window_buffer_finish_edit(struct window_buffer_editdata *ed)
+{
+ free(ed->name);
+ free(ed);
+}
+
+static void
+window_buffer_edit_close_cb(char *buf, size_t len, void *arg)
+{
+ struct window_buffer_editdata *ed = arg;
+ size_t oldlen;
+ const char *oldbuf;
+ struct paste_buffer *pb;
+ struct window_pane *wp;
+ struct window_buffer_modedata *data;
+ struct window_mode_entry *wme;
+
+ if (buf == NULL || len == 0) {
+ window_buffer_finish_edit(ed);
+ return;
+ }
+
+ pb = paste_get_name(ed->name);
+ if (pb == NULL || pb != ed->pb) {
+ window_buffer_finish_edit(ed);
+ return;
+ }
+
+ oldbuf = paste_buffer_data(pb, &oldlen);
+ if (oldlen != '\0' &&
+ oldbuf[oldlen - 1] != '\n' &&
+ buf[len - 1] == '\n')
+ len--;
+ if (len != 0)
+ paste_replace(pb, buf, len);
+
+ wp = window_pane_find_by_id(ed->wp_id);
+ if (wp != NULL) {
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme->mode == &window_buffer_mode) {
+ data = wme->data;
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ }
+ wp->flags |= PANE_REDRAW;
+ }
+ window_buffer_finish_edit(ed);
+}
+
+static void
+window_buffer_start_edit(struct window_buffer_modedata *data,
+ struct window_buffer_itemdata *item, struct client *c)
+{
+ struct paste_buffer *pb;
+ const char *buf;
+ size_t len;
+ struct window_buffer_editdata *ed;
+
+ if ((pb = paste_get_name(item->name)) == NULL)
+ return;
+ buf = paste_buffer_data(pb, &len);
+
+ ed = xcalloc(1, sizeof *ed);
+ ed->wp_id = data->wp->id;
+ ed->name = xstrdup(paste_buffer_name(pb));
+ ed->pb = pb;
+
+ if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0)
+ window_buffer_finish_edit(ed);
+}
+
+static void
+window_buffer_key(struct window_mode_entry *wme, struct client *c,
+ __unused struct session *s, __unused struct winlink *wl, key_code key,
+ struct mouse_event *m)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_buffer_modedata *data = wme->data;
+ struct mode_tree_data *mtd = data->data;
+ struct window_buffer_itemdata *item;
+ int finished;
+
+ finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
+ switch (key) {
+ case 'e':
+ item = mode_tree_get_current(mtd);
+ window_buffer_start_edit(data, item, c);
+ break;
+ case 'd':
+ item = mode_tree_get_current(mtd);
+ window_buffer_do_delete(data, item, c, key);
+ mode_tree_build(mtd);
+ break;
+ case 'D':
+ mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0);
+ mode_tree_build(mtd);
+ break;
+ case 'P':
+ mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0);
+ finished = 1;
+ break;
+ case 'p':
+ case '\r':
+ item = mode_tree_get_current(mtd);
+ window_buffer_do_paste(data, item, c, key);
+ finished = 1;
+ break;
+ }
+ if (finished || paste_get_top(NULL) == NULL)
+ window_pane_reset_mode(wp);
+ else {
+ mode_tree_draw(mtd);
+ wp->flags |= PANE_REDRAW;
+ }
+}
diff --git a/window-client.c b/window-client.c
new file mode 100644
index 0000000..8d501b0
--- /dev/null
+++ b/window-client.c
@@ -0,0 +1,418 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "tmux.h"
+
+static struct screen *window_client_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static void window_client_free(struct window_mode_entry *);
+static void window_client_resize(struct window_mode_entry *, u_int,
+ u_int);
+static void window_client_update(struct window_mode_entry *);
+static void window_client_key(struct window_mode_entry *,
+ struct client *, struct session *,
+ struct winlink *, key_code, struct mouse_event *);
+
+#define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'"
+
+#define WINDOW_CLIENT_DEFAULT_FORMAT \
+ "#{t/p:client_activity}: session #{session_name}"
+
+#define WINDOW_CLIENT_DEFAULT_KEY_FORMAT \
+ "#{?#{e|<:#{line},10}," \
+ "#{line}" \
+ "," \
+ "#{?#{e|<:#{line},36}," \
+ "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
+ "," \
+ "" \
+ "}" \
+ "}"
+
+static const struct menu_item window_client_menu_items[] = {
+ { "Detach", 'd', NULL },
+ { "Detach Tagged", 'D', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Tag", 't', NULL },
+ { "Tag All", '\024', NULL },
+ { "Tag None", 'T', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Cancel", 'q', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+const struct window_mode window_client_mode = {
+ .name = "client-mode",
+ .default_format = WINDOW_CLIENT_DEFAULT_FORMAT,
+
+ .init = window_client_init,
+ .free = window_client_free,
+ .resize = window_client_resize,
+ .update = window_client_update,
+ .key = window_client_key,
+};
+
+enum window_client_sort_type {
+ WINDOW_CLIENT_BY_NAME,
+ WINDOW_CLIENT_BY_SIZE,
+ WINDOW_CLIENT_BY_CREATION_TIME,
+ WINDOW_CLIENT_BY_ACTIVITY_TIME,
+};
+static const char *window_client_sort_list[] = {
+ "name",
+ "size",
+ "creation",
+ "activity"
+};
+static struct mode_tree_sort_criteria *window_client_sort;
+
+struct window_client_itemdata {
+ struct client *c;
+};
+
+struct window_client_modedata {
+ struct window_pane *wp;
+
+ struct mode_tree_data *data;
+ char *format;
+ char *key_format;
+ char *command;
+
+ struct window_client_itemdata **item_list;
+ u_int item_size;
+};
+
+static struct window_client_itemdata *
+window_client_add_item(struct window_client_modedata *data)
+{
+ struct window_client_itemdata *item;
+
+ data->item_list = xreallocarray(data->item_list, data->item_size + 1,
+ sizeof *data->item_list);
+ item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
+ return (item);
+}
+
+static void
+window_client_free_item(struct window_client_itemdata *item)
+{
+ server_client_unref(item->c);
+ free(item);
+}
+
+static int
+window_client_cmp(const void *a0, const void *b0)
+{
+ const struct window_client_itemdata *const *a = a0;
+ const struct window_client_itemdata *const *b = b0;
+ const struct window_client_itemdata *itema = *a;
+ const struct window_client_itemdata *itemb = *b;
+ struct client *ca = itema->c;
+ struct client *cb = itemb->c;
+ int result = 0;
+
+ switch (window_client_sort->field) {
+ case WINDOW_CLIENT_BY_SIZE:
+ result = ca->tty.sx - cb->tty.sx;
+ if (result == 0)
+ result = ca->tty.sy - cb->tty.sy;
+ break;
+ case WINDOW_CLIENT_BY_CREATION_TIME:
+ if (timercmp(&ca->creation_time, &cb->creation_time, >))
+ result = -1;
+ else if (timercmp(&ca->creation_time, &cb->creation_time, <))
+ result = 1;
+ break;
+ case WINDOW_CLIENT_BY_ACTIVITY_TIME:
+ if (timercmp(&ca->activity_time, &cb->activity_time, >))
+ result = -1;
+ else if (timercmp(&ca->activity_time, &cb->activity_time, <))
+ result = 1;
+ break;
+ }
+
+ /* Use WINDOW_CLIENT_BY_NAME as default order and tie breaker. */
+ if (result == 0)
+ result = strcmp(ca->name, cb->name);
+
+ if (window_client_sort->reversed)
+ result = -result;
+ return (result);
+}
+
+static void
+window_client_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
+ __unused uint64_t *tag, const char *filter)
+{
+ struct window_client_modedata *data = modedata;
+ struct window_client_itemdata *item;
+ u_int i;
+ struct client *c;
+ char *text, *cp;
+
+ for (i = 0; i < data->item_size; i++)
+ window_client_free_item(data->item_list[i]);
+ free(data->item_list);
+ data->item_list = NULL;
+ data->item_size = 0;
+
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
+ continue;
+
+ item = window_client_add_item(data);
+ item->c = c;
+
+ c->references++;
+ }
+
+ window_client_sort = sort_crit;
+ qsort(data->item_list, data->item_size, sizeof *data->item_list,
+ window_client_cmp);
+
+ for (i = 0; i < data->item_size; i++) {
+ item = data->item_list[i];
+ c = item->c;
+
+ if (filter != NULL) {
+ cp = format_single(NULL, filter, c, NULL, NULL, NULL);
+ if (!format_true(cp)) {
+ free(cp);
+ continue;
+ }
+ free(cp);
+ }
+
+ text = format_single(NULL, data->format, c, NULL, NULL, NULL);
+ mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name,
+ text, -1);
+ free(text);
+ }
+}
+
+static void
+window_client_draw(__unused void *modedata, void *itemdata,
+ struct screen_write_ctx *ctx, u_int sx, u_int sy)
+{
+ struct window_client_itemdata *item = itemdata;
+ struct client *c = item->c;
+ struct screen *s = ctx->s;
+ struct window_pane *wp;
+ u_int cx = s->cx, cy = s->cy, lines, at;
+
+ if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
+ return;
+ wp = c->session->curw->window->active;
+
+ lines = status_line_size(c);
+ if (lines >= sy)
+ lines = 0;
+ if (status_at_line(c) == 0)
+ at = lines;
+ else
+ at = 0;
+
+ screen_write_cursormove(ctx, cx, cy + at, 0);
+ screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines);
+
+ if (at != 0)
+ 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);
+
+ if (at != 0)
+ screen_write_cursormove(ctx, cx, cy, 0);
+ else
+ screen_write_cursormove(ctx, cx, cy + sy - lines, 0);
+ screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines);
+}
+
+static void
+window_client_menu(void *modedata, struct client *c, key_code key)
+{
+ struct window_client_modedata *data = modedata;
+ struct window_pane *wp = data->wp;
+ struct window_mode_entry *wme;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL || wme->data != modedata)
+ return;
+ window_client_key(wme, c, NULL, NULL, key, NULL);
+}
+
+static key_code
+window_client_get_key(void *modedata, void *itemdata, u_int line)
+{
+ struct window_client_modedata *data = modedata;
+ struct window_client_itemdata *item = itemdata;
+ struct format_tree *ft;
+ char *expanded;
+ key_code key;
+
+ ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+ format_defaults(ft, item->c, NULL, 0, NULL);
+ format_add(ft, "line", "%u", line);
+
+ expanded = format_expand(ft, data->key_format);
+ key = key_string_lookup_string(expanded);
+ free(expanded);
+ format_free(ft);
+ return (key);
+}
+
+static struct screen *
+window_client_init(struct window_mode_entry *wme,
+ __unused struct cmd_find_state *fs, struct args *args)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_client_modedata *data;
+ struct screen *s;
+
+ wme->data = data = xcalloc(1, sizeof *data);
+ data->wp = wp;
+
+ if (args == NULL || !args_has(args, 'F'))
+ data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT);
+ else
+ data->format = xstrdup(args_get(args, 'F'));
+ if (args == NULL || !args_has(args, 'K'))
+ data->key_format = xstrdup(WINDOW_CLIENT_DEFAULT_KEY_FORMAT);
+ else
+ data->key_format = xstrdup(args_get(args, 'K'));
+ if (args == NULL || args_count(args) == 0)
+ data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND);
+ else
+ data->command = xstrdup(args_string(args, 0));
+
+ data->data = mode_tree_start(wp, args, window_client_build,
+ window_client_draw, NULL, window_client_menu, NULL,
+ window_client_get_key, data, window_client_menu_items,
+ window_client_sort_list, nitems(window_client_sort_list), &s);
+ mode_tree_zoom(data->data, args);
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+
+ return (s);
+}
+
+static void
+window_client_free(struct window_mode_entry *wme)
+{
+ struct window_client_modedata *data = wme->data;
+ u_int i;
+
+ if (data == NULL)
+ return;
+
+ mode_tree_free(data->data);
+
+ for (i = 0; i < data->item_size; i++)
+ window_client_free_item(data->item_list[i]);
+ free(data->item_list);
+
+ free(data->format);
+ free(data->key_format);
+ free(data->command);
+
+ free(data);
+}
+
+static void
+window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
+{
+ struct window_client_modedata *data = wme->data;
+
+ mode_tree_resize(data->data, sx, sy);
+}
+
+static void
+window_client_update(struct window_mode_entry *wme)
+{
+ struct window_client_modedata *data = wme->data;
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+}
+
+static void
+window_client_do_detach(void *modedata, void *itemdata,
+ __unused struct client *c, key_code key)
+{
+ struct window_client_modedata *data = modedata;
+ struct window_client_itemdata *item = itemdata;
+
+ if (item == mode_tree_get_current(data->data))
+ mode_tree_down(data->data, 0);
+ if (key == 'd' || key == 'D')
+ server_client_detach(item->c, MSG_DETACH);
+ else if (key == 'x' || key == 'X')
+ server_client_detach(item->c, MSG_DETACHKILL);
+ else if (key == 'z' || key == 'Z')
+ server_client_suspend(item->c);
+}
+
+static void
+window_client_key(struct window_mode_entry *wme, struct client *c,
+ __unused struct session *s, __unused struct winlink *wl, key_code key,
+ struct mouse_event *m)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_client_modedata *data = wme->data;
+ struct mode_tree_data *mtd = data->data;
+ struct window_client_itemdata *item;
+ int finished;
+
+ finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
+ switch (key) {
+ case 'd':
+ case 'x':
+ case 'z':
+ item = mode_tree_get_current(mtd);
+ window_client_do_detach(data, item, c, key);
+ mode_tree_build(mtd);
+ break;
+ case 'D':
+ case 'X':
+ case 'Z':
+ mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0);
+ mode_tree_build(mtd);
+ break;
+ case '\r':
+ item = mode_tree_get_current(mtd);
+ mode_tree_run_command(c, NULL, data->command, item->c->ttyname);
+ finished = 1;
+ break;
+ }
+ if (finished || server_client_how_many() == 0)
+ window_pane_reset_mode(wp);
+ else {
+ mode_tree_draw(mtd);
+ wp->flags |= PANE_REDRAW;
+ }
+}
diff --git a/window-clock.c b/window-clock.c
new file mode 100644
index 0000000..8cef3f9
--- /dev/null
+++ b/window-clock.c
@@ -0,0 +1,286 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 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 <time.h>
+
+#include "tmux.h"
+
+static struct screen *window_clock_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static void window_clock_free(struct window_mode_entry *);
+static void window_clock_resize(struct window_mode_entry *, u_int, u_int);
+static void window_clock_key(struct window_mode_entry *, struct client *,
+ struct session *, struct winlink *, key_code,
+ struct mouse_event *);
+
+static void window_clock_timer_callback(int, short, void *);
+static void window_clock_draw_screen(struct window_mode_entry *);
+
+const struct window_mode window_clock_mode = {
+ .name = "clock-mode",
+
+ .init = window_clock_init,
+ .free = window_clock_free,
+ .resize = window_clock_resize,
+ .key = window_clock_key,
+};
+
+struct window_clock_mode_data {
+ struct screen screen;
+ time_t tim;
+ struct event timer;
+};
+
+const char window_clock_table[14][5][5] = {
+ { { 1,1,1,1,1 }, /* 0 */
+ { 1,0,0,0,1 },
+ { 1,0,0,0,1 },
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 } },
+ { { 0,0,0,0,1 }, /* 1 */
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 } },
+ { { 1,1,1,1,1 }, /* 2 */
+ { 0,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 1,0,0,0,0 },
+ { 1,1,1,1,1 } },
+ { { 1,1,1,1,1 }, /* 3 */
+ { 0,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 0,0,0,0,1 },
+ { 1,1,1,1,1 } },
+ { { 1,0,0,0,1 }, /* 4 */
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 } },
+ { { 1,1,1,1,1 }, /* 5 */
+ { 1,0,0,0,0 },
+ { 1,1,1,1,1 },
+ { 0,0,0,0,1 },
+ { 1,1,1,1,1 } },
+ { { 1,1,1,1,1 }, /* 6 */
+ { 1,0,0,0,0 },
+ { 1,1,1,1,1 },
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 } },
+ { { 1,1,1,1,1 }, /* 7 */
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 },
+ { 0,0,0,0,1 } },
+ { { 1,1,1,1,1 }, /* 8 */
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 } },
+ { { 1,1,1,1,1 }, /* 9 */
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 0,0,0,0,1 },
+ { 1,1,1,1,1 } },
+ { { 0,0,0,0,0 }, /* : */
+ { 0,0,1,0,0 },
+ { 0,0,0,0,0 },
+ { 0,0,1,0,0 },
+ { 0,0,0,0,0 } },
+ { { 1,1,1,1,1 }, /* A */
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 1,0,0,0,1 },
+ { 1,0,0,0,1 } },
+ { { 1,1,1,1,1 }, /* P */
+ { 1,0,0,0,1 },
+ { 1,1,1,1,1 },
+ { 1,0,0,0,0 },
+ { 1,0,0,0,0 } },
+ { { 1,0,0,0,1 }, /* M */
+ { 1,1,0,1,1 },
+ { 1,0,1,0,1 },
+ { 1,0,0,0,1 },
+ { 1,0,0,0,1 } },
+};
+
+static void
+window_clock_timer_callback(__unused int fd, __unused short events, void *arg)
+{
+ struct window_mode_entry *wme = arg;
+ struct window_pane *wp = wme->wp;
+ struct window_clock_mode_data *data = wme->data;
+ struct tm now, then;
+ time_t t;
+ struct timeval tv = { .tv_sec = 1 };
+
+ evtimer_del(&data->timer);
+ evtimer_add(&data->timer, &tv);
+
+ if (TAILQ_FIRST(&wp->modes) != wme)
+ return;
+
+ t = time(NULL);
+ gmtime_r(&t, &now);
+ gmtime_r(&data->tim, &then);
+ if (now.tm_min == then.tm_min)
+ return;
+ data->tim = t;
+
+ window_clock_draw_screen(wme);
+ wp->flags |= PANE_REDRAW;
+}
+
+static struct screen *
+window_clock_init(struct window_mode_entry *wme,
+ __unused struct cmd_find_state *fs, __unused struct args *args)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_clock_mode_data *data;
+ struct screen *s;
+ struct timeval tv = { .tv_sec = 1 };
+
+ wme->data = data = xmalloc(sizeof *data);
+ data->tim = time(NULL);
+
+ evtimer_set(&data->timer, window_clock_timer_callback, wme);
+ evtimer_add(&data->timer, &tv);
+
+ s = &data->screen;
+ screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0);
+ s->mode &= ~MODE_CURSOR;
+
+ window_clock_draw_screen(wme);
+
+ return (s);
+}
+
+static void
+window_clock_free(struct window_mode_entry *wme)
+{
+ struct window_clock_mode_data *data = wme->data;
+
+ evtimer_del(&data->timer);
+ screen_free(&data->screen);
+ free(data);
+}
+
+static void
+window_clock_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
+{
+ struct window_clock_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+
+ screen_resize(s, sx, sy, 0);
+ window_clock_draw_screen(wme);
+}
+
+static void
+window_clock_key(struct window_mode_entry *wme, __unused struct client *c,
+ __unused struct session *s, __unused struct winlink *wl,
+ __unused key_code key, __unused struct mouse_event *m)
+{
+ window_pane_reset_mode(wme->wp);
+}
+
+static void
+window_clock_draw_screen(struct window_mode_entry *wme)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_clock_mode_data *data = wme->data;
+ struct screen_write_ctx ctx;
+ int colour, style;
+ struct screen *s = &data->screen;
+ struct grid_cell gc;
+ char tim[64], *ptr;
+ time_t t;
+ struct tm *tm;
+ u_int i, j, x, y, idx;
+
+ colour = options_get_number(wp->window->options, "clock-mode-colour");
+ style = options_get_number(wp->window->options, "clock-mode-style");
+
+ screen_write_start(&ctx, s);
+
+ t = time(NULL);
+ tm = localtime(&t);
+ if (style == 0) {
+ strftime(tim, sizeof tim, "%l:%M ", localtime(&t));
+ if (tm->tm_hour >= 12)
+ strlcat(tim, "PM", sizeof tim);
+ else
+ strlcat(tim, "AM", sizeof tim);
+ } else
+ strftime(tim, sizeof tim, "%H:%M", tm);
+
+ screen_write_clearscreen(&ctx, 8);
+
+ if (screen_size_x(s) < 6 * strlen(tim) || screen_size_y(s) < 6) {
+ if (screen_size_x(s) >= strlen(tim) && screen_size_y(s) != 0) {
+ x = (screen_size_x(s) / 2) - (strlen(tim) / 2);
+ y = screen_size_y(s) / 2;
+ screen_write_cursormove(&ctx, x, y, 0);
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.flags |= GRID_FLAG_NOPALETTE;
+ gc.fg = colour;
+ screen_write_puts(&ctx, &gc, "%s", tim);
+ }
+
+ screen_write_stop(&ctx);
+ return;
+ }
+
+ x = (screen_size_x(s) / 2) - 3 * strlen(tim);
+ y = (screen_size_y(s) / 2) - 3;
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.flags |= GRID_FLAG_NOPALETTE;
+ gc.bg = colour;
+ for (ptr = tim; *ptr != '\0'; ptr++) {
+ if (*ptr >= '0' && *ptr <= '9')
+ idx = *ptr - '0';
+ else if (*ptr == ':')
+ idx = 10;
+ else if (*ptr == 'A')
+ idx = 11;
+ else if (*ptr == 'P')
+ idx = 12;
+ else if (*ptr == 'M')
+ idx = 13;
+ else {
+ x += 6;
+ continue;
+ }
+
+ for (j = 0; j < 5; j++) {
+ for (i = 0; i < 5; i++) {
+ screen_write_cursormove(&ctx, x + i, y + j, 0);
+ if (window_clock_table[idx][j][i])
+ screen_write_putc(&ctx, &gc, ' ');
+ }
+ }
+ x += 6;
+ }
+
+ screen_write_stop(&ctx);
+}
diff --git a/window-copy.c b/window-copy.c
new file mode 100644
index 0000000..0307055
--- /dev/null
+++ b/window-copy.c
@@ -0,0 +1,5575 @@
+/* $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 <ctype.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "tmux.h"
+
+struct window_copy_mode_data;
+
+static const char *window_copy_key_table(struct window_mode_entry *);
+static void window_copy_command(struct window_mode_entry *, struct client *,
+ struct session *, struct winlink *, struct args *,
+ struct mouse_event *);
+static struct screen *window_copy_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static struct screen *window_copy_view_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static void window_copy_free(struct window_mode_entry *);
+static void window_copy_resize(struct window_mode_entry *, u_int, u_int);
+static void window_copy_formats(struct window_mode_entry *,
+ struct format_tree *);
+static void window_copy_pageup1(struct window_mode_entry *, int);
+static int window_copy_pagedown(struct window_mode_entry *, int, int);
+static void window_copy_next_paragraph(struct window_mode_entry *);
+static void window_copy_previous_paragraph(struct window_mode_entry *);
+static void window_copy_redraw_selection(struct window_mode_entry *, u_int);
+static void window_copy_redraw_lines(struct window_mode_entry *, u_int,
+ u_int);
+static void window_copy_redraw_screen(struct window_mode_entry *);
+static void window_copy_write_line(struct window_mode_entry *,
+ struct screen_write_ctx *, u_int);
+static void window_copy_write_lines(struct window_mode_entry *,
+ struct screen_write_ctx *, u_int, u_int);
+static char *window_copy_match_at_cursor(struct window_copy_mode_data *);
+static void window_copy_scroll_to(struct window_mode_entry *, u_int, u_int,
+ int);
+static int window_copy_search_compare(struct grid *, u_int, u_int,
+ struct grid *, u_int, int);
+static int window_copy_search_lr(struct grid *, struct grid *, u_int *,
+ u_int, u_int, u_int, int);
+static int window_copy_search_rl(struct grid *, struct grid *, u_int *,
+ u_int, u_int, u_int, int);
+static int window_copy_last_regex(struct grid *, u_int, u_int, u_int,
+ u_int, u_int *, u_int *, const char *, const regex_t *,
+ int);
+static int window_copy_search_mark_at(struct window_copy_mode_data *,
+ u_int, u_int, u_int *);
+static char *window_copy_stringify(struct grid *, u_int, u_int, u_int,
+ char *, u_int *);
+static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *,
+ u_int *, const char *);
+static int window_copy_search_marks(struct window_mode_entry *,
+ struct screen *, int, int);
+static void window_copy_clear_marks(struct window_mode_entry *);
+static int window_copy_is_lowercase(const char *);
+static void window_copy_search_back_overlap(struct grid *, regex_t *,
+ u_int *, u_int *, u_int *, u_int);
+static int window_copy_search_jump(struct window_mode_entry *,
+ struct grid *, struct grid *, u_int, u_int, u_int, int, int,
+ int, int);
+static int window_copy_search(struct window_mode_entry *, int, int);
+static int window_copy_search_up(struct window_mode_entry *, int);
+static int window_copy_search_down(struct window_mode_entry *, int);
+static void window_copy_goto_line(struct window_mode_entry *, const char *);
+static void window_copy_update_cursor(struct window_mode_entry *, u_int,
+ u_int);
+static void window_copy_start_selection(struct window_mode_entry *);
+static int window_copy_adjust_selection(struct window_mode_entry *,
+ u_int *, u_int *);
+static int window_copy_set_selection(struct window_mode_entry *, int, int);
+static int window_copy_update_selection(struct window_mode_entry *, int,
+ int);
+static void window_copy_synchronize_cursor(struct window_mode_entry *, int);
+static void *window_copy_get_selection(struct window_mode_entry *, size_t *);
+static void window_copy_copy_buffer(struct window_mode_entry *,
+ const char *, void *, size_t);
+static void window_copy_pipe(struct window_mode_entry *,
+ struct session *, const char *);
+static void window_copy_copy_pipe(struct window_mode_entry *,
+ struct session *, const char *, const char *);
+static void window_copy_copy_selection(struct window_mode_entry *,
+ const char *);
+static void window_copy_append_selection(struct window_mode_entry *);
+static void window_copy_clear_selection(struct window_mode_entry *);
+static void window_copy_copy_line(struct window_mode_entry *, char **,
+ size_t *, u_int, u_int, u_int);
+static int window_copy_in_set(struct window_mode_entry *, u_int, u_int,
+ const char *);
+static u_int window_copy_find_length(struct window_mode_entry *, u_int);
+static void window_copy_cursor_start_of_line(struct window_mode_entry *);
+static void window_copy_cursor_back_to_indentation(
+ struct window_mode_entry *);
+static void window_copy_cursor_end_of_line(struct window_mode_entry *);
+static void window_copy_other_end(struct window_mode_entry *);
+static void window_copy_cursor_left(struct window_mode_entry *);
+static void window_copy_cursor_right(struct window_mode_entry *, int);
+static void window_copy_cursor_up(struct window_mode_entry *, int);
+static void window_copy_cursor_down(struct window_mode_entry *, int);
+static void window_copy_cursor_jump(struct window_mode_entry *);
+static void window_copy_cursor_jump_back(struct window_mode_entry *);
+static void window_copy_cursor_jump_to(struct window_mode_entry *);
+static void window_copy_cursor_jump_to_back(struct window_mode_entry *);
+static void window_copy_cursor_next_word(struct window_mode_entry *,
+ const char *);
+static void window_copy_cursor_next_word_end_pos(struct window_mode_entry *,
+ const char *, u_int *, u_int *);
+static void window_copy_cursor_next_word_end(struct window_mode_entry *,
+ const char *, int);
+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_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);
+static void window_copy_move_mouse(struct mouse_event *);
+static void window_copy_drag_update(struct client *, struct mouse_event *);
+static void window_copy_drag_release(struct client *, struct mouse_event *);
+static void window_copy_jump_to_mark(struct window_mode_entry *);
+static void window_copy_acquire_cursor_up(struct window_mode_entry *,
+ u_int, u_int, u_int, u_int, u_int);
+static void window_copy_acquire_cursor_down(struct window_mode_entry *,
+ u_int, u_int, u_int, u_int, u_int, u_int, int);
+
+const struct window_mode window_copy_mode = {
+ .name = "copy-mode",
+
+ .init = window_copy_init,
+ .free = window_copy_free,
+ .resize = window_copy_resize,
+ .key_table = window_copy_key_table,
+ .command = window_copy_command,
+ .formats = window_copy_formats,
+};
+
+const struct window_mode window_view_mode = {
+ .name = "view-mode",
+
+ .init = window_copy_view_init,
+ .free = window_copy_free,
+ .resize = window_copy_resize,
+ .key_table = window_copy_key_table,
+ .command = window_copy_command,
+ .formats = window_copy_formats,
+};
+
+enum {
+ WINDOW_COPY_OFF,
+ WINDOW_COPY_SEARCHUP,
+ WINDOW_COPY_SEARCHDOWN,
+ WINDOW_COPY_JUMPFORWARD,
+ WINDOW_COPY_JUMPBACKWARD,
+ WINDOW_COPY_JUMPTOFORWARD,
+ WINDOW_COPY_JUMPTOBACKWARD,
+};
+
+enum {
+ WINDOW_COPY_REL_POS_ABOVE,
+ WINDOW_COPY_REL_POS_ON_SCREEN,
+ WINDOW_COPY_REL_POS_BELOW,
+};
+
+enum window_copy_cmd_action {
+ WINDOW_COPY_CMD_NOTHING,
+ WINDOW_COPY_CMD_REDRAW,
+ WINDOW_COPY_CMD_CANCEL,
+};
+
+enum window_copy_cmd_clear {
+ WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ WINDOW_COPY_CMD_CLEAR_NEVER,
+ WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+};
+
+struct window_copy_cmd_state {
+ struct window_mode_entry *wme;
+ struct args *args;
+ struct mouse_event *m;
+
+ struct client *c;
+ struct session *s;
+ struct winlink *wl;
+};
+
+/*
+ * Copy mode's visible screen (the "screen" field) is filled from one of two
+ * sources: the original contents of the pane (used when we actually enter via
+ * the "copy-mode" command, to copy the contents of the current pane), or else
+ * a series of lines containing the output from an output-writing tmux command
+ * (such as any of the "show-*" or "list-*" commands).
+ *
+ * In either case, the full content of the copy-mode grid is pointed at by the
+ * "backing" field, and is copied into "screen" as needed (that is, when
+ * scrolling occurs). When copy-mode is backed by a pane, backing points
+ * directly at that pane's screen structure (&wp->base); when backed by a list
+ * of output-lines from a command, it points at a newly-allocated screen
+ * structure (which is deallocated when the mode ends).
+ */
+struct window_copy_mode_data {
+ struct screen screen;
+
+ struct screen *backing;
+ int backing_written; /* backing display started */
+ struct screen *writing;
+ struct input_ctx *ictx;
+
+ int viewmode; /* view mode entered */
+
+ u_int oy; /* number of lines scrolled up */
+
+ u_int selx; /* beginning of selection */
+ u_int sely;
+
+ u_int endselx; /* end of selection */
+ u_int endsely;
+
+ enum {
+ CURSORDRAG_NONE, /* selection is independent of cursor */
+ CURSORDRAG_ENDSEL, /* end is synchronized with cursor */
+ CURSORDRAG_SEL, /* start is synchronized with cursor */
+ } cursordrag;
+
+ int modekeys;
+ enum {
+ LINE_SEL_NONE,
+ LINE_SEL_LEFT_RIGHT,
+ LINE_SEL_RIGHT_LEFT,
+ } lineflag; /* line selection mode */
+ int rectflag; /* in rectangle copy mode? */
+ int scroll_exit; /* exit on scroll to end? */
+ int hide_position; /* hide position marker */
+
+ enum {
+ SEL_CHAR, /* select one char at a time */
+ SEL_WORD, /* select one word at a time */
+ SEL_LINE, /* select one line at a time */
+ } selflag;
+
+ const char *separators; /* word separators */
+
+ u_int dx; /* drag start position */
+ u_int dy;
+
+ u_int selrx; /* selection reset positions */
+ u_int selry;
+ u_int endselrx;
+ u_int endselry;
+
+ u_int cx;
+ u_int cy;
+
+ u_int lastcx; /* position in last line w/ content */
+ u_int lastsx; /* size of last line w/ content */
+
+ u_int mx; /* mark position */
+ u_int my;
+ int showmark;
+
+ int searchtype;
+ int searchdirection;
+ int searchregex;
+ char *searchstr;
+ u_char *searchmark;
+ int searchcount;
+ int searchmore;
+ int searchall;
+ int searchx;
+ int searchy;
+ int searcho;
+ u_char searchgen;
+
+ int timeout; /* search has timed out */
+#define WINDOW_COPY_SEARCH_TIMEOUT 10000
+#define WINDOW_COPY_SEARCH_ALL_TIMEOUT 200
+
+ int jumptype;
+ struct utf8_data *jumpchar;
+
+ struct event dragtimer;
+#define WINDOW_COPY_DRAG_REPEAT_TIME 50000
+};
+
+static void
+window_copy_scroll_timer(__unused int fd, __unused short events, void *arg)
+{
+ struct window_mode_entry *wme = arg;
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct timeval tv = {
+ .tv_usec = WINDOW_COPY_DRAG_REPEAT_TIME
+ };
+
+ evtimer_del(&data->dragtimer);
+
+ if (TAILQ_FIRST(&wp->modes) != wme)
+ return;
+
+ if (data->cy == 0) {
+ evtimer_add(&data->dragtimer, &tv);
+ window_copy_cursor_up(wme, 1);
+ } else if (data->cy == screen_size_y(&data->screen) - 1) {
+ evtimer_add(&data->dragtimer, &tv);
+ window_copy_cursor_down(wme, 1);
+ }
+}
+
+static struct screen *
+window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx,
+ u_int *cy, int trim)
+{
+ struct screen *dst;
+ const struct grid_line *gl;
+ u_int sy, wx, wy;
+ int reflow;
+
+ dst = xcalloc(1, sizeof *dst);
+
+ sy = screen_hsize(src) + screen_size_y(src);
+ if (trim) {
+ while (sy > screen_hsize(src)) {
+ gl = grid_peek_line(src->grid, sy - 1);
+ if (gl->cellused != 0)
+ break;
+ sy--;
+ }
+ }
+ log_debug("%s: target screen is %ux%u, source %ux%u", __func__,
+ screen_size_x(src), sy, screen_size_x(hint),
+ screen_hsize(src) + screen_size_y(src));
+ screen_init(dst, screen_size_x(src), sy, screen_hlimit(src));
+
+ /*
+ * Ensure history is on for the backing grid so lines are not deleted
+ * during resizing.
+ */
+ dst->grid->flags |= GRID_HISTORY;
+ grid_duplicate_lines(dst->grid, 0, src->grid, 0, sy);
+
+ dst->grid->sy = sy - screen_hsize(src);
+ dst->grid->hsize = screen_hsize(src);
+ dst->grid->hscrolled = src->grid->hscrolled;
+ if (src->cy > dst->grid->sy - 1) {
+ dst->cx = 0;
+ dst->cy = dst->grid->sy - 1;
+ } else {
+ dst->cx = src->cx;
+ dst->cy = src->cy;
+ }
+
+ if (cx != NULL && cy != NULL) {
+ *cx = dst->cx;
+ *cy = screen_hsize(dst) + dst->cy;
+ reflow = (screen_size_x(hint) != screen_size_x(dst));
+ }
+ else
+ reflow = 0;
+ if (reflow)
+ grid_wrap_position(dst->grid, *cx, *cy, &wx, &wy);
+ screen_resize_cursor(dst, screen_size_x(hint), screen_size_y(hint), 1,
+ 0, 0);
+ if (reflow)
+ grid_unwrap_position(dst->grid, cx, cy, wx, wy);
+
+ return (dst);
+}
+
+static struct window_copy_mode_data *
+window_copy_common_init(struct window_mode_entry *wme)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data;
+ struct screen *base = &wp->base;
+
+ wme->data = data = xcalloc(1, sizeof *data);
+
+ data->cursordrag = CURSORDRAG_NONE;
+ data->lineflag = LINE_SEL_NONE;
+ data->selflag = SEL_CHAR;
+
+ if (wp->searchstr != NULL) {
+ data->searchtype = WINDOW_COPY_SEARCHUP;
+ data->searchregex = wp->searchregex;
+ data->searchstr = xstrdup(wp->searchstr);
+ } else {
+ data->searchtype = WINDOW_COPY_OFF;
+ data->searchregex = 0;
+ data->searchstr = NULL;
+ }
+ data->searchx = data->searchy = data->searcho = -1;
+ data->searchall = 1;
+
+ data->jumptype = WINDOW_COPY_OFF;
+ data->jumpchar = NULL;
+
+ screen_init(&data->screen, screen_size_x(base), screen_size_y(base), 0);
+ data->modekeys = options_get_number(wp->window->options, "mode-keys");
+
+ evtimer_set(&data->dragtimer, window_copy_scroll_timer, wme);
+
+ return (data);
+}
+
+static struct screen *
+window_copy_init(struct window_mode_entry *wme,
+ __unused struct cmd_find_state *fs, struct args *args)
+{
+ struct window_pane *wp = wme->swp;
+ struct window_copy_mode_data *data;
+ struct screen *base = &wp->base;
+ struct screen_write_ctx ctx;
+ u_int i, cx, cy;
+
+ data = window_copy_common_init(wme);
+ data->backing = window_copy_clone_screen(base, &data->screen, &cx, &cy,
+ wme->swp != wme->wp);
+
+ data->cx = cx;
+ if (cy < screen_hsize(data->backing)) {
+ data->cy = 0;
+ data->oy = screen_hsize(data->backing) - cy;
+ } else {
+ data->cy = cy - screen_hsize(data->backing);
+ data->oy = 0;
+ }
+
+ data->scroll_exit = args_has(args, 'e');
+ data->hide_position = args_has(args, 'H');
+
+ data->screen.cx = data->cx;
+ data->screen.cy = data->cy;
+ data->mx = data->cx;
+ data->my = screen_hsize(data->backing) + data->cy - data->oy;
+ data->showmark = 0;
+
+ screen_write_start(&ctx, &data->screen);
+ for (i = 0; i < screen_size_y(&data->screen); i++)
+ window_copy_write_line(wme, &ctx, i);
+ screen_write_cursormove(&ctx, data->cx, data->cy, 0);
+ screen_write_stop(&ctx);
+
+ return (&data->screen);
+}
+
+static struct screen *
+window_copy_view_init(struct window_mode_entry *wme,
+ __unused struct cmd_find_state *fs, __unused struct args *args)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data;
+ struct screen *base = &wp->base;
+ u_int sx = screen_size_x(base);
+
+ data = window_copy_common_init(wme);
+ data->viewmode = 1;
+
+ data->backing = xmalloc(sizeof *data->backing);
+ screen_init(data->backing, sx, screen_size_y(base), UINT_MAX);
+ data->writing = xmalloc(sizeof *data->writing);
+ screen_init(data->writing, sx, screen_size_y(base), 0);
+ data->ictx = input_init(NULL, NULL, NULL);
+ data->mx = data->cx;
+ data->my = screen_hsize(data->backing) + data->cy - data->oy;
+ data->showmark = 0;
+
+ return (&data->screen);
+}
+
+static void
+window_copy_free(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ evtimer_del(&data->dragtimer);
+
+ free(data->searchmark);
+ free(data->searchstr);
+ free(data->jumpchar);
+
+ if (data->writing != NULL) {
+ screen_free(data->writing);
+ free(data->writing);
+ }
+ if (data->ictx != NULL)
+ input_free(data->ictx);
+ screen_free(data->backing);
+ free(data->backing);
+
+ screen_free(&data->screen);
+ free(data);
+}
+
+void
+window_copy_add(struct window_pane *wp, int parse, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ window_copy_vadd(wp, parse, fmt, ap);
+ va_end(ap);
+}
+
+static void
+window_copy_init_ctx_cb(__unused struct screen_write_ctx *ctx,
+ struct tty_ctx *ttyctx)
+{
+ memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults);
+ ttyctx->palette = NULL;
+ ttyctx->redraw_cb = NULL;
+ ttyctx->set_client_cb = NULL;
+ ttyctx->arg = NULL;
+}
+
+void
+window_copy_vadd(struct window_pane *wp, int parse, const char *fmt, va_list ap)
+{
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *backing = data->backing;
+ struct screen *writing = data->writing;
+ struct screen_write_ctx writing_ctx, backing_ctx, ctx;
+ struct grid_cell gc;
+ u_int old_hsize, old_cy;
+ u_int sx = screen_size_x(backing);
+ char *text;
+
+ if (parse) {
+ vasprintf(&text, fmt, ap);
+ screen_write_start(&writing_ctx, writing);
+ screen_write_reset(&writing_ctx);
+ input_parse_screen(data->ictx, writing, window_copy_init_ctx_cb,
+ data, text, strlen(text));
+ free(text);
+ }
+
+ old_hsize = screen_hsize(data->backing);
+ screen_write_start(&backing_ctx, backing);
+ if (data->backing_written) {
+ /*
+ * On the second or later line, do a CRLF before writing
+ * (so it's on a new line).
+ */
+ screen_write_carriagereturn(&backing_ctx);
+ screen_write_linefeed(&backing_ctx, 0, 8);
+ } else
+ data->backing_written = 1;
+ old_cy = backing->cy;
+ if (parse)
+ screen_write_fast_copy(&backing_ctx, writing, 0, 0, sx, 1);
+ else {
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ screen_write_vnputs(&backing_ctx, 0, &gc, fmt, ap);
+ }
+ screen_write_stop(&backing_ctx);
+
+ data->oy += screen_hsize(data->backing) - old_hsize;
+
+ screen_write_start_pane(&ctx, wp, &data->screen);
+
+ /*
+ * If the history has changed, draw the top line.
+ * (If there's any history at all, it has changed.)
+ */
+ if (screen_hsize(data->backing))
+ window_copy_redraw_lines(wme, 0, 1);
+
+ /* Write the new lines. */
+ window_copy_redraw_lines(wme, old_cy, backing->cy - old_cy + 1);
+
+ screen_write_stop(&ctx);
+}
+
+void
+window_copy_pageup(struct window_pane *wp, int half_page)
+{
+ window_copy_pageup1(TAILQ_FIRST(&wp->modes), half_page);
+}
+
+static void
+window_copy_pageup1(struct window_mode_entry *wme, int half_page)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int n, ox, oy, px, py;
+
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+ ox = window_copy_find_length(wme, oy);
+
+ if (data->cx != ox) {
+ data->lastcx = data->cx;
+ data->lastsx = ox;
+ }
+ data->cx = data->lastcx;
+
+ n = 1;
+ if (screen_size_y(s) > 2) {
+ if (half_page)
+ n = screen_size_y(s) / 2;
+ else
+ n = screen_size_y(s) - 2;
+ }
+
+ if (data->oy + n > screen_hsize(data->backing)) {
+ data->oy = screen_hsize(data->backing);
+ if (data->cy < n)
+ data->cy = 0;
+ else
+ data->cy -= n;
+ } else
+ data->oy += n;
+
+ if (data->screen.sel == NULL || !data->rectflag) {
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ px = window_copy_find_length(wme, py);
+ if ((data->cx >= data->lastsx && data->cx != px) ||
+ data->cx > px)
+ window_copy_cursor_end_of_line(wme);
+ }
+
+ if (data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 1, 0);
+ window_copy_redraw_screen(wme);
+}
+
+static int
+window_copy_pagedown(struct window_mode_entry *wme, int half_page,
+ int scroll_exit)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int n, ox, oy, px, py;
+
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+ ox = window_copy_find_length(wme, oy);
+
+ if (data->cx != ox) {
+ data->lastcx = data->cx;
+ data->lastsx = ox;
+ }
+ data->cx = data->lastcx;
+
+ n = 1;
+ if (screen_size_y(s) > 2) {
+ if (half_page)
+ n = screen_size_y(s) / 2;
+ else
+ n = screen_size_y(s) - 2;
+ }
+
+ if (data->oy < n) {
+ data->oy = 0;
+ if (data->cy + (n - data->oy) >= screen_size_y(data->backing))
+ data->cy = screen_size_y(data->backing) - 1;
+ else
+ data->cy += n - data->oy;
+ } else
+ data->oy -= n;
+
+ if (data->screen.sel == NULL || !data->rectflag) {
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ px = window_copy_find_length(wme, py);
+ if ((data->cx >= data->lastsx && data->cx != px) ||
+ data->cx > px)
+ window_copy_cursor_end_of_line(wme);
+ }
+
+ if (scroll_exit && data->oy == 0)
+ return (1);
+ if (data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 1, 0);
+ window_copy_redraw_screen(wme);
+ return (0);
+}
+
+static void
+window_copy_previous_paragraph(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ u_int oy;
+
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+
+ while (oy > 0 && window_copy_find_length(wme, oy) == 0)
+ oy--;
+
+ while (oy > 0 && window_copy_find_length(wme, oy) > 0)
+ oy--;
+
+ window_copy_scroll_to(wme, 0, oy, 0);
+}
+
+static void
+window_copy_next_paragraph(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int maxy, ox, oy;
+
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+ maxy = screen_hsize(data->backing) + screen_size_y(s) - 1;
+
+ while (oy < maxy && window_copy_find_length(wme, oy) == 0)
+ oy++;
+
+ while (oy < maxy && window_copy_find_length(wme, oy) > 0)
+ oy++;
+
+ ox = window_copy_find_length(wme, oy);
+ window_copy_scroll_to(wme, ox, oy, 0);
+}
+
+char *
+window_copy_get_word(struct window_pane *wp, u_int x, u_int y)
+{
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct window_copy_mode_data *data = wme->data;
+ struct grid *gd = data->screen.grid;
+
+ return (format_grid_word(gd, x, gd->hsize + y));
+}
+
+char *
+window_copy_get_line(struct window_pane *wp, u_int y)
+{
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct window_copy_mode_data *data = wme->data;
+ struct grid *gd = data->screen.grid;
+
+ return (format_grid_line(gd, gd->hsize + y));
+}
+
+static void *
+window_copy_cursor_word_cb(struct format_tree *ft)
+{
+ struct window_pane *wp = format_get_pane(ft);
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct window_copy_mode_data *data = wme->data;
+
+ return (window_copy_get_word(wp, data->cx, data->cy));
+}
+
+static void *
+window_copy_cursor_line_cb(struct format_tree *ft)
+{
+ struct window_pane *wp = format_get_pane(ft);
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct window_copy_mode_data *data = wme->data;
+
+ return (window_copy_get_line(wp, data->cy));
+}
+
+static void *
+window_copy_search_match_cb(struct format_tree *ft)
+{
+ struct window_pane *wp = format_get_pane(ft);
+ struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes);
+ struct window_copy_mode_data *data = wme->data;
+
+ return (window_copy_match_at_cursor(data));
+}
+
+static void
+window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ format_add(ft, "scroll_position", "%d", data->oy);
+ format_add(ft, "rectangle_toggle", "%d", data->rectflag);
+
+ format_add(ft, "copy_cursor_x", "%d", data->cx);
+ format_add(ft, "copy_cursor_y", "%d", data->cy);
+
+ format_add(ft, "selection_present", "%d", data->screen.sel != NULL);
+ if (data->screen.sel != NULL) {
+ format_add(ft, "selection_start_x", "%d", data->selx);
+ format_add(ft, "selection_start_y", "%d", data->sely);
+ format_add(ft, "selection_end_x", "%d", data->endselx);
+ format_add(ft, "selection_end_y", "%d", data->endsely);
+ format_add(ft, "selection_active", "%d",
+ data->cursordrag != CURSORDRAG_NONE);
+ } else
+ format_add(ft, "selection_active", "%d", 0);
+
+ format_add(ft, "search_present", "%d", data->searchmark != NULL);
+ format_add_cb(ft, "search_match", window_copy_search_match_cb);
+
+ format_add_cb(ft, "copy_cursor_word", window_copy_cursor_word_cb);
+ format_add_cb(ft, "copy_cursor_line", window_copy_cursor_line_cb);
+}
+
+static void
+window_copy_size_changed(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct screen_write_ctx ctx;
+ int search = (data->searchmark != NULL);
+
+ window_copy_clear_selection(wme);
+ window_copy_clear_marks(wme);
+
+ screen_write_start(&ctx, s);
+ window_copy_write_lines(wme, &ctx, 0, screen_size_y(s));
+ screen_write_stop(&ctx);
+
+ if (search && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 0);
+ data->searchx = data->cx;
+ data->searchy = data->cy;
+ data->searcho = data->oy;
+}
+
+static void
+window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct grid *gd = data->backing->grid;
+ u_int cx, cy, wx, wy;
+ int reflow;
+
+ screen_resize(s, sx, sy, 0);
+ cx = data->cx;
+ cy = gd->hsize + data->cy - data->oy;
+ reflow = (gd->sx != sx);
+ if (reflow)
+ grid_wrap_position(gd, cx, cy, &wx, &wy);
+ screen_resize_cursor(data->backing, sx, sy, 1, 0, 0);
+ if (reflow)
+ grid_unwrap_position(gd, &cx, &cy, wx, wy);
+
+ data->cx = cx;
+ if (cy < gd->hsize) {
+ data->cy = 0;
+ data->oy = gd->hsize - cy;
+ } else {
+ data->cy = cy - gd->hsize;
+ data->oy = 0;
+ }
+
+ window_copy_size_changed(wme);
+ window_copy_redraw_screen(wme);
+}
+
+static const char *
+window_copy_key_table(struct window_mode_entry *wme)
+{
+ struct window_pane *wp = wme->wp;
+
+ if (options_get_number(wp->window->options, "mode-keys") == MODEKEY_VI)
+ return ("copy-mode-vi");
+ return ("copy-mode");
+}
+
+static int
+window_copy_expand_search_string(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ const char *ss = args_string(cs->args, 1);
+ char *expanded;
+
+ if (ss == NULL || *ss == '\0')
+ return (0);
+
+ if (args_has(cs->args, 'F')) {
+ expanded = format_single(NULL, ss, NULL, NULL, NULL, wme->wp);
+ if (*expanded == '\0') {
+ free(expanded);
+ return (0);
+ }
+ free(data->searchstr);
+ data->searchstr = expanded;
+ } else {
+ free(data->searchstr);
+ data->searchstr = xstrdup(ss);
+ }
+ return (1);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_append_selection(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct session *s = cs->s;
+
+ if (s != NULL)
+ window_copy_append_selection(wme);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_append_selection_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct session *s = cs->s;
+
+ if (s != NULL)
+ window_copy_append_selection(wme);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_CANCEL);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_back_to_indentation(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cursor_back_to_indentation(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_begin_selection(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct client *c = cs->c;
+ struct mouse_event *m = cs->m;
+ struct window_copy_mode_data *data = wme->data;
+
+ if (m != NULL) {
+ window_copy_start_drag(c, m);
+ return (WINDOW_COPY_CMD_NOTHING);
+ }
+
+ data->lineflag = LINE_SEL_NONE;
+ data->selflag = SEL_CHAR;
+ window_copy_start_selection(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_stop_selection(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->cursordrag = CURSORDRAG_NONE;
+ data->lineflag = LINE_SEL_NONE;
+ data->selflag = SEL_CHAR;
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_bottom_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->cx = 0;
+ data->cy = screen_size_y(&data->screen) - 1;
+
+ window_copy_update_selection(wme, 1, 0);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_cancel(__unused struct window_copy_cmd_state *cs)
+{
+ return (WINDOW_COPY_CMD_CANCEL);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_clear_selection(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_do_copy_end_of_line(struct window_copy_cmd_state *cs, int pipe,
+ int cancel)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct client *c = cs->c;
+ struct session *s = cs->s;
+ struct winlink *wl = cs->wl;
+ struct window_pane *wp = wme->wp;
+ u_int count = args_count(cs->args);
+ u_int np = wme->prefix, ocx, ocy, ooy;
+ struct window_copy_mode_data *data = wme->data;
+ char *prefix = NULL, *command = NULL;
+ const char *arg1 = args_string(cs->args, 1);
+ const char *arg2 = args_string(cs->args, 2);
+
+ if (pipe) {
+ if (count == 3)
+ prefix = format_single(NULL, arg2, c, s, wl, wp);
+ if (s != NULL && count > 1 && *arg1 != '\0')
+ command = format_single(NULL, arg1, c, s, wl, wp);
+ } else {
+ if (count == 2)
+ prefix = format_single(NULL, arg1, c, s, wl, wp);
+ }
+
+ ocx = data->cx;
+ ocy = data->cy;
+ ooy = data->oy;
+
+ window_copy_start_selection(wme);
+ for (; np > 1; np--)
+ window_copy_cursor_down(wme, 0);
+ window_copy_cursor_end_of_line(wme);
+
+ if (s != NULL) {
+ if (pipe)
+ window_copy_copy_pipe(wme, s, prefix, command);
+ else
+ window_copy_copy_selection(wme, prefix);
+
+ if (cancel) {
+ free(prefix);
+ free(command);
+ return (WINDOW_COPY_CMD_CANCEL);
+ }
+ }
+ window_copy_clear_selection(wme);
+
+ data->cx = ocx;
+ data->cy = ocy;
+ data->oy = ooy;
+
+ free(prefix);
+ free(command);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_end_of_line(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_end_of_line(cs, 0, 0));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_end_of_line_and_cancel(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_end_of_line(cs, 0, 1));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe_end_of_line(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_end_of_line(cs, 1, 0));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe_end_of_line_and_cancel(
+ struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_end_of_line(cs, 1, 1));
+}
+
+static enum window_copy_cmd_action
+window_copy_do_copy_line(struct window_copy_cmd_state *cs, int pipe, int cancel)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct client *c = cs->c;
+ struct session *s = cs->s;
+ struct winlink *wl = cs->wl;
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ u_int count = args_count(cs->args);
+ u_int np = wme->prefix, ocx, ocy, ooy;
+ char *prefix = NULL, *command = NULL;
+ const char *arg1 = args_string(cs->args, 1);
+ const char *arg2 = args_string(cs->args, 2);
+
+ if (pipe) {
+ if (count == 3)
+ prefix = format_single(NULL, arg2, c, s, wl, wp);
+ if (s != NULL && count > 1 && *arg1 != '\0')
+ command = format_single(NULL, arg1, c, s, wl, wp);
+ } else {
+ if (count == 2)
+ prefix = format_single(NULL, arg1, c, s, wl, wp);
+ }
+
+ ocx = data->cx;
+ ocy = data->cy;
+ ooy = data->oy;
+
+ data->selflag = SEL_CHAR;
+ window_copy_cursor_start_of_line(wme);
+ window_copy_start_selection(wme);
+ for (; np > 1; np--)
+ window_copy_cursor_down(wme, 0);
+ window_copy_cursor_end_of_line(wme);
+
+ if (s != NULL) {
+ if (pipe)
+ window_copy_copy_pipe(wme, s, prefix, command);
+ else
+ window_copy_copy_selection(wme, prefix);
+
+ if (cancel) {
+ free(prefix);
+ free(command);
+ return (WINDOW_COPY_CMD_CANCEL);
+ }
+ }
+ window_copy_clear_selection(wme);
+
+ data->cx = ocx;
+ data->cy = ocy;
+ data->oy = ooy;
+
+ free(prefix);
+ free(command);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_line(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_line(cs, 0, 0));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_line_and_cancel(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_line(cs, 0, 1));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe_line(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_line(cs, 1, 0));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe_line_and_cancel(struct window_copy_cmd_state *cs)
+{
+ return (window_copy_do_copy_line(cs, 1, 1));
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_selection_no_clear(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct client *c = cs->c;
+ struct session *s = cs->s;
+ struct winlink *wl = cs->wl;
+ struct window_pane *wp = wme->wp;
+ char *prefix = NULL;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (arg1 != NULL)
+ prefix = format_single(NULL, arg1, c, s, wl, wp);
+
+ if (s != NULL)
+ window_copy_copy_selection(wme, prefix);
+
+ free(prefix);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_selection(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cmd_copy_selection_no_clear(cs);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_selection_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cmd_copy_selection_no_clear(cs);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_CANCEL);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_cursor_down(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_down(wme, 0);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_cursor_down_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix, cy;
+
+ cy = data->cy;
+ for (; np != 0; np--)
+ window_copy_cursor_down(wme, 0);
+ if (cy == data->cy && data->oy == 0)
+ return (WINDOW_COPY_CMD_CANCEL);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_cursor_left(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_left(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_cursor_right(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--) {
+ window_copy_cursor_right(wme, data->screen.sel != NULL &&
+ data->rectflag);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_cursor_up(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_up(wme, 0);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_end_of_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cursor_end_of_line(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_halfpage_down(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--) {
+ if (window_copy_pagedown(wme, 1, data->scroll_exit))
+ return (WINDOW_COPY_CMD_CANCEL);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_halfpage_down_and_cancel(struct window_copy_cmd_state *cs)
+{
+
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--) {
+ if (window_copy_pagedown(wme, 1, 1))
+ return (WINDOW_COPY_CMD_CANCEL);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_halfpage_up(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_pageup1(wme, 1);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_toggle_position(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->hide_position = !data->hide_position;
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_history_bottom(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = data->backing;
+ u_int oy;
+
+ oy = screen_hsize(s) + data->cy - data->oy;
+ if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely)
+ window_copy_other_end(wme);
+
+ data->cy = screen_size_y(&data->screen) - 1;
+ data->cx = window_copy_find_length(wme, screen_hsize(s) + data->cy);
+ data->oy = 0;
+
+ if (data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 1, 0);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_history_top(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int oy;
+
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+ if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely)
+ window_copy_other_end(wme);
+
+ data->cy = 0;
+ data->cx = 0;
+ data->oy = screen_hsize(data->backing);
+
+ if (data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 1, 0);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_again(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ switch (data->jumptype) {
+ case WINDOW_COPY_JUMPFORWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump(wme);
+ break;
+ case WINDOW_COPY_JUMPBACKWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump_back(wme);
+ break;
+ case WINDOW_COPY_JUMPTOFORWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump_to(wme);
+ break;
+ case WINDOW_COPY_JUMPTOBACKWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump_to_back(wme);
+ break;
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_reverse(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ switch (data->jumptype) {
+ case WINDOW_COPY_JUMPFORWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump_back(wme);
+ break;
+ case WINDOW_COPY_JUMPBACKWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump(wme);
+ break;
+ case WINDOW_COPY_JUMPTOFORWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump_to_back(wme);
+ break;
+ case WINDOW_COPY_JUMPTOBACKWARD:
+ for (; np != 0; np--)
+ window_copy_cursor_jump_to(wme);
+ break;
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_middle_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->cx = 0;
+ data->cy = (screen_size_y(&data->screen) - 1) / 2;
+
+ window_copy_update_selection(wme, 1, 0);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_previous_matching_bracket(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = data->backing;
+ char open[] = "{[(", close[] = "}])";
+ char tried, found, start, *cp;
+ u_int px, py, xx, n;
+ struct grid_cell gc;
+ int failed;
+
+ for (; np != 0; np--) {
+ /* Get cursor position and line length. */
+ px = data->cx;
+ py = screen_hsize(s) + data->cy - data->oy;
+ xx = window_copy_find_length(wme, py);
+ if (xx == 0)
+ break;
+
+ /*
+ * Get the current character. If not on a bracket, try the
+ * previous. If still not, then behave like previous-word.
+ */
+ tried = 0;
+ retry:
+ grid_get_cell(s->grid, px, py, &gc);
+ if (gc.data.size != 1 || (gc.flags & GRID_FLAG_PADDING))
+ cp = NULL;
+ else {
+ found = *gc.data.data;
+ cp = strchr(close, found);
+ }
+ if (cp == NULL) {
+ if (data->modekeys == MODEKEY_EMACS) {
+ if (!tried && px > 0) {
+ px--;
+ tried = 1;
+ goto retry;
+ }
+ window_copy_cursor_previous_word(wme, close, 1);
+ }
+ continue;
+ }
+ start = open[cp - close];
+
+ /* Walk backward until the matching bracket is reached. */
+ n = 1;
+ failed = 0;
+ do {
+ if (px == 0) {
+ if (py == 0) {
+ failed = 1;
+ break;
+ }
+ do {
+ py--;
+ xx = window_copy_find_length(wme, py);
+ } while (xx == 0 && py > 0);
+ if (xx == 0 && py == 0) {
+ failed = 1;
+ break;
+ }
+ px = xx - 1;
+ } else
+ px--;
+
+ grid_get_cell(s->grid, px, py, &gc);
+ if (gc.data.size == 1 &&
+ (~gc.flags & GRID_FLAG_PADDING)) {
+ if (*gc.data.data == found)
+ n++;
+ else if (*gc.data.data == start)
+ n--;
+ }
+ } while (n != 0);
+
+ /* Move the cursor to the found location if any. */
+ if (!failed)
+ window_copy_scroll_to(wme, px, py, 0);
+ }
+
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = data->backing;
+ char open[] = "{[(", close[] = "}])";
+ char tried, found, end, *cp;
+ u_int px, py, xx, yy, sx, sy, n;
+ struct grid_cell gc;
+ int failed;
+ struct grid_line *gl;
+
+ for (; np != 0; np--) {
+ /* Get cursor position and line length. */
+ px = data->cx;
+ py = screen_hsize(s) + data->cy - data->oy;
+ xx = window_copy_find_length(wme, py);
+ yy = screen_hsize(s) + screen_size_y(s) - 1;
+ if (xx == 0)
+ break;
+
+ /*
+ * Get the current character. If not on a bracket, try the
+ * next. If still not, then behave like next-word.
+ */
+ tried = 0;
+ retry:
+ grid_get_cell(s->grid, px, py, &gc);
+ if (gc.data.size != 1 || (gc.flags & GRID_FLAG_PADDING))
+ cp = NULL;
+ else {
+ found = *gc.data.data;
+
+ /*
+ * In vi mode, attempt to move to previous bracket if a
+ * closing bracket is found first. If this fails,
+ * return to the original cursor position.
+ */
+ cp = strchr(close, found);
+ if (cp != NULL && data->modekeys == MODEKEY_VI) {
+ sx = data->cx;
+ sy = screen_hsize(s) + data->cy - data->oy;
+
+ window_copy_scroll_to(wme, px, py, 0);
+ window_copy_cmd_previous_matching_bracket(cs);
+
+ px = data->cx;
+ py = screen_hsize(s) + data->cy - data->oy;
+ grid_get_cell(s->grid, px, py, &gc);
+ if (gc.data.size == 1 &&
+ (~gc.flags & GRID_FLAG_PADDING) &&
+ strchr(close, *gc.data.data) != NULL)
+ window_copy_scroll_to(wme, sx, sy, 0);
+ break;
+ }
+
+ cp = strchr(open, found);
+ }
+ if (cp == NULL) {
+ if (data->modekeys == MODEKEY_EMACS) {
+ if (!tried && px <= xx) {
+ px++;
+ tried = 1;
+ goto retry;
+ }
+ window_copy_cursor_next_word_end(wme, open, 0);
+ continue;
+ }
+ /* For vi, continue searching for bracket until EOL. */
+ if (px > xx) {
+ if (py == yy)
+ continue;
+ gl = grid_get_line(s->grid, py);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ continue;
+ if (gl->cellsize > s->grid->sx)
+ continue;
+ px = 0;
+ py++;
+ xx = window_copy_find_length(wme, py);
+ } else
+ px++;
+ goto retry;
+ }
+ end = close[cp - open];
+
+ /* Walk forward until the matching bracket is reached. */
+ n = 1;
+ failed = 0;
+ do {
+ if (px > xx) {
+ if (py == yy) {
+ failed = 1;
+ break;
+ }
+ px = 0;
+ py++;
+ xx = window_copy_find_length(wme, py);
+ } else
+ px++;
+
+ grid_get_cell(s->grid, px, py, &gc);
+ if (gc.data.size == 1 &&
+ (~gc.flags & GRID_FLAG_PADDING)) {
+ if (*gc.data.data == found)
+ n++;
+ else if (*gc.data.data == end)
+ n--;
+ }
+ } while (n != 0);
+
+ /* Move the cursor to the found location if any. */
+ if (!failed)
+ window_copy_scroll_to(wme, px, py, 0);
+ }
+
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_next_paragraph(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_next_paragraph(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_next_space(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_next_word(wme, "");
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_next_space_end(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_next_word_end(wme, "", 0);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_next_word(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+ const char *separators;
+
+ separators = options_get_string(cs->s->options, "word-separators");
+
+ for (; np != 0; np--)
+ window_copy_cursor_next_word(wme, separators);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_next_word_end(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+ const char *separators;
+
+ separators = options_get_string(cs->s->options, "word-separators");
+
+ for (; np != 0; np--)
+ window_copy_cursor_next_word_end(wme, separators, 0);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_other_end(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->selflag = SEL_CHAR;
+ if ((np % 2) != 0)
+ window_copy_other_end(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_page_down(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--) {
+ if (window_copy_pagedown(wme, 0, data->scroll_exit))
+ return (WINDOW_COPY_CMD_CANCEL);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_page_down_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--) {
+ if (window_copy_pagedown(wme, 0, 1))
+ return (WINDOW_COPY_CMD_CANCEL);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_page_up(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_pageup1(wme, 0);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_previous_paragraph(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_previous_paragraph(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_previous_space(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_previous_word(wme, "", 1);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_previous_word(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+ const char *separators;
+
+ separators = options_get_string(cs->s->options, "word-separators");
+
+ for (; np != 0; np--)
+ window_copy_cursor_previous_word(wme, separators, 1);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_rectangle_on(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->lineflag = LINE_SEL_NONE;
+ window_copy_rectangle_set(wme, 1);
+
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_rectangle_off(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->lineflag = LINE_SEL_NONE;
+ window_copy_rectangle_set(wme, 0);
+
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_rectangle_toggle(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->lineflag = LINE_SEL_NONE;
+ window_copy_rectangle_set(wme, !data->rectflag);
+
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_down(wme, 1);
+ if (data->scroll_exit && data->oy == 0)
+ return (WINDOW_COPY_CMD_CANCEL);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_scroll_down_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_down(wme, 1);
+ if (data->oy == 0)
+ return (WINDOW_COPY_CMD_CANCEL);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ u_int np = wme->prefix;
+
+ for (; np != 0; np--)
+ window_copy_cursor_up(wme, 1);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_again(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ if (data->searchtype == WINDOW_COPY_SEARCHUP) {
+ for (; np != 0; np--)
+ window_copy_search_up(wme, data->searchregex);
+ } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) {
+ for (; np != 0; np--)
+ window_copy_search_down(wme, data->searchregex);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_reverse(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ if (data->searchtype == WINDOW_COPY_SEARCHUP) {
+ for (; np != 0; np--)
+ window_copy_search_down(wme, data->searchregex);
+ } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) {
+ for (; np != 0; np--)
+ window_copy_search_up(wme, data->searchregex);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_select_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ data->lineflag = LINE_SEL_LEFT_RIGHT;
+ data->rectflag = 0;
+ data->selflag = SEL_LINE;
+ data->dx = data->cx;
+ data->dy = screen_hsize(data->backing) + data->cy - data->oy;
+
+ window_copy_cursor_start_of_line(wme);
+ data->selrx = data->cx;
+ data->selry = screen_hsize(data->backing) + data->cy - data->oy;
+ data->endselry = data->selry;
+ window_copy_start_selection(wme);
+ window_copy_cursor_end_of_line(wme);
+ data->endselry = screen_hsize(data->backing) + data->cy - data->oy;
+ data->endselrx = window_copy_find_length(wme, data->endselry);
+ for (; np > 1; np--) {
+ window_copy_cursor_down(wme, 0);
+ window_copy_cursor_end_of_line(wme);
+ }
+
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_select_word(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct options *session_options = cs->s->options;
+ struct window_copy_mode_data *data = wme->data;
+ u_int px, py, nextx, nexty;
+
+ data->lineflag = LINE_SEL_LEFT_RIGHT;
+ data->rectflag = 0;
+ data->selflag = SEL_WORD;
+ data->dx = data->cx;
+ data->dy = screen_hsize(data->backing) + data->cy - data->oy;
+
+ data->separators = options_get_string(session_options,
+ "word-separators");
+ window_copy_cursor_previous_word(wme, data->separators, 0);
+ px = data->cx;
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ data->selrx = px;
+ data->selry = py;
+ window_copy_start_selection(wme);
+
+ /* Handle single character words. */
+ nextx = px + 1;
+ nexty = py;
+ if (grid_get_line(data->backing->grid, nexty)->flags &
+ GRID_LINE_WRAPPED && nextx > screen_size_x(data->backing) - 1) {
+ nextx = 0;
+ nexty++;
+ }
+ if (px >= window_copy_find_length(wme, py) ||
+ !window_copy_in_set(wme, nextx, nexty, WHITESPACE))
+ window_copy_cursor_next_word_end(wme, data->separators, 1);
+ else {
+ window_copy_update_cursor(wme, px, data->cy);
+ if (window_copy_update_selection(wme, 1, 1))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+ data->endselrx = data->cx;
+ data->endselry = screen_hsize(data->backing) + data->cy - data->oy;
+ if (data->dy > data->endselry) {
+ data->dy = data->endselry;
+ data->dx = data->endselrx;
+ } else if (data->dx > data->endselrx)
+ data->dx = data->endselrx;
+
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_set_mark(struct window_copy_cmd_state *cs)
+{
+ struct window_copy_mode_data *data = cs->wme->data;
+
+ data->mx = data->cx;
+ data->my = screen_hsize(data->backing) + data->cy - data->oy;
+ data->showmark = 1;
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_start_of_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cursor_start_of_line(wme);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_top_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+
+ data->cx = 0;
+ data->cy = 0;
+
+ window_copy_update_selection(wme, 1, 0);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe_no_clear(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct client *c = cs->c;
+ struct session *s = cs->s;
+ struct winlink *wl = cs->wl;
+ struct window_pane *wp = wme->wp;
+ char *command = NULL, *prefix = NULL;
+ const char *arg1 = args_string(cs->args, 1);
+ const char *arg2 = args_string(cs->args, 2);
+
+ if (arg2 != NULL)
+ prefix = format_single(NULL, arg2, c, s, wl, wp);
+
+ if (s != NULL && arg1 != NULL && *arg1 != '\0')
+ command = format_single(NULL, arg1, c, s, wl, wp);
+ window_copy_copy_pipe(wme, s, prefix, command);
+ free(command);
+
+ free(prefix);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cmd_copy_pipe_no_clear(cs);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_copy_pipe_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cmd_copy_pipe_no_clear(cs);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_CANCEL);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_pipe_no_clear(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct client *c = cs->c;
+ struct session *s = cs->s;
+ struct winlink *wl = cs->wl;
+ struct window_pane *wp = wme->wp;
+ char *command = NULL;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (s != NULL && arg1 != NULL && *arg1 != '\0')
+ command = format_single(NULL, arg1, c, s, wl, wp);
+ window_copy_pipe(wme, s, command);
+ free(command);
+
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_pipe(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cmd_pipe_no_clear(cs);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_pipe_and_cancel(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_cmd_pipe_no_clear(cs);
+ window_copy_clear_selection(wme);
+ return (WINDOW_COPY_CMD_CANCEL);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_goto_line(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (*arg1 != '\0')
+ window_copy_goto_line(wme, arg1);
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_backward(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (*arg1 != '\0') {
+ data->jumptype = WINDOW_COPY_JUMPBACKWARD;
+ free(data->jumpchar);
+ data->jumpchar = utf8_fromcstr(arg1);
+ for (; np != 0; np--)
+ window_copy_cursor_jump_back(wme);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_forward(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (*arg1 != '\0') {
+ data->jumptype = WINDOW_COPY_JUMPFORWARD;
+ free(data->jumpchar);
+ data->jumpchar = utf8_fromcstr(arg1);
+ for (; np != 0; np--)
+ window_copy_cursor_jump(wme);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_to_backward(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (*arg1 != '\0') {
+ data->jumptype = WINDOW_COPY_JUMPTOBACKWARD;
+ free(data->jumpchar);
+ data->jumpchar = utf8_fromcstr(arg1);
+ for (; np != 0; np--)
+ window_copy_cursor_jump_to_back(wme);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_to_forward(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+ const char *arg1 = args_string(cs->args, 1);
+
+ if (*arg1 != '\0') {
+ data->jumptype = WINDOW_COPY_JUMPTOFORWARD;
+ free(data->jumpchar);
+ data->jumpchar = utf8_fromcstr(arg1);
+ for (; np != 0; np--)
+ window_copy_cursor_jump_to(wme);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_jump_to_mark(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+
+ window_copy_jump_to_mark(wme);
+ 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;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ if (!window_copy_expand_search_string(cs))
+ return (WINDOW_COPY_CMD_NOTHING);
+
+ if (data->searchstr != NULL) {
+ data->searchtype = WINDOW_COPY_SEARCHUP;
+ data->searchregex = 1;
+ data->timeout = 0;
+ for (; np != 0; np--)
+ window_copy_search_up(wme, 1);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_backward_text(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ if (!window_copy_expand_search_string(cs))
+ return (WINDOW_COPY_CMD_NOTHING);
+
+ if (data->searchstr != NULL) {
+ data->searchtype = WINDOW_COPY_SEARCHUP;
+ data->searchregex = 0;
+ data->timeout = 0;
+ for (; np != 0; np--)
+ window_copy_search_up(wme, 0);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_forward(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ if (!window_copy_expand_search_string(cs))
+ return (WINDOW_COPY_CMD_NOTHING);
+
+ if (data->searchstr != NULL) {
+ data->searchtype = WINDOW_COPY_SEARCHDOWN;
+ data->searchregex = 1;
+ data->timeout = 0;
+ for (; np != 0; np--)
+ window_copy_search_down(wme, 1);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_forward_text(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ u_int np = wme->prefix;
+
+ if (!window_copy_expand_search_string(cs))
+ return (WINDOW_COPY_CMD_NOTHING);
+
+ if (data->searchstr != NULL) {
+ data->searchtype = WINDOW_COPY_SEARCHDOWN;
+ data->searchregex = 0;
+ data->timeout = 0;
+ for (; np != 0; np--)
+ window_copy_search_down(wme, 0);
+ }
+ return (WINDOW_COPY_CMD_NOTHING);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ const char *arg1 = args_string(cs->args, 1);
+ const char *ss = data->searchstr;
+ char prefix;
+ enum window_copy_cmd_action action = WINDOW_COPY_CMD_NOTHING;
+
+ data->timeout = 0;
+
+ log_debug("%s: %s", __func__, arg1);
+
+ prefix = *arg1++;
+ if (data->searchx == -1 || data->searchy == -1) {
+ data->searchx = data->cx;
+ data->searchy = data->cy;
+ data->searcho = data->oy;
+ } else if (ss != NULL && strcmp(arg1, ss) != 0) {
+ data->cx = data->searchx;
+ data->cy = data->searchy;
+ data->oy = data->searcho;
+ action = WINDOW_COPY_CMD_REDRAW;
+ }
+ if (*arg1 == '\0') {
+ window_copy_clear_marks(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+ }
+ switch (prefix) {
+ case '=':
+ case '-':
+ data->searchtype = WINDOW_COPY_SEARCHUP;
+ data->searchregex = 0;
+ free(data->searchstr);
+ data->searchstr = xstrdup(arg1);
+ if (!window_copy_search_up(wme, 0)) {
+ window_copy_clear_marks(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+ }
+ break;
+ case '+':
+ data->searchtype = WINDOW_COPY_SEARCHDOWN;
+ data->searchregex = 0;
+ free(data->searchstr);
+ data->searchstr = xstrdup(arg1);
+ if (!window_copy_search_down(wme, 0)) {
+ window_copy_clear_marks(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+ }
+ break;
+ }
+ return (action);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_copy_mode_data *data = wme->data;
+ const char *arg1 = args_string(cs->args, 1);
+ const char *ss = data->searchstr;
+ char prefix;
+ enum window_copy_cmd_action action = WINDOW_COPY_CMD_NOTHING;
+
+ data->timeout = 0;
+
+ log_debug("%s: %s", __func__, arg1);
+
+ prefix = *arg1++;
+ if (data->searchx == -1 || data->searchy == -1) {
+ data->searchx = data->cx;
+ data->searchy = data->cy;
+ data->searcho = data->oy;
+ } else if (ss != NULL && strcmp(arg1, ss) != 0) {
+ data->cx = data->searchx;
+ data->cy = data->searchy;
+ data->oy = data->searcho;
+ action = WINDOW_COPY_CMD_REDRAW;
+ }
+ if (*arg1 == '\0') {
+ window_copy_clear_marks(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+ }
+ switch (prefix) {
+ case '=':
+ case '+':
+ data->searchtype = WINDOW_COPY_SEARCHDOWN;
+ data->searchregex = 0;
+ free(data->searchstr);
+ data->searchstr = xstrdup(arg1);
+ if (!window_copy_search_down(wme, 0)) {
+ window_copy_clear_marks(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+ }
+ break;
+ case '-':
+ data->searchtype = WINDOW_COPY_SEARCHUP;
+ data->searchregex = 0;
+ free(data->searchstr);
+ data->searchstr = xstrdup(arg1);
+ if (!window_copy_search_up(wme, 0)) {
+ window_copy_clear_marks(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+ }
+ }
+ return (action);
+}
+
+static enum window_copy_cmd_action
+window_copy_cmd_refresh_from_pane(struct window_copy_cmd_state *cs)
+{
+ struct window_mode_entry *wme = cs->wme;
+ struct window_pane *wp = wme->swp;
+ struct window_copy_mode_data *data = wme->data;
+
+ if (data->viewmode)
+ return (WINDOW_COPY_CMD_NOTHING);
+
+ screen_free(data->backing);
+ free(data->backing);
+ data->backing = window_copy_clone_screen(&wp->base, &data->screen, NULL, NULL, wme->swp != wme->wp);
+
+ window_copy_size_changed(wme);
+ return (WINDOW_COPY_CMD_REDRAW);
+}
+
+static const struct {
+ const char *command;
+ u_int minargs;
+ u_int maxargs;
+ enum window_copy_cmd_clear clear;
+ enum window_copy_cmd_action (*f)(struct window_copy_cmd_state *);
+} window_copy_cmd_table[] = {
+ { .command = "append-selection",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_append_selection
+ },
+ { .command = "append-selection-and-cancel",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_append_selection_and_cancel
+ },
+ { .command = "back-to-indentation",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_back_to_indentation
+ },
+ { .command = "begin-selection",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_begin_selection
+ },
+ { .command = "bottom-line",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_bottom_line
+ },
+ { .command = "cancel",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_cancel
+ },
+ { .command = "clear-selection",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_clear_selection
+ },
+ { .command = "copy-end-of-line",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_end_of_line
+ },
+ { .command = "copy-end-of-line-and-cancel",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_end_of_line_and_cancel
+ },
+ { .command = "copy-pipe-end-of-line",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_pipe_end_of_line
+ },
+ { .command = "copy-pipe-end-of-line-and-cancel",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_pipe_end_of_line_and_cancel
+ },
+ { .command = "copy-line",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_line
+ },
+ { .command = "copy-line-and-cancel",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_line_and_cancel
+ },
+ { .command = "copy-pipe-line",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_pipe_line
+ },
+ { .command = "copy-pipe-line-and-cancel",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_pipe_line_and_cancel
+ },
+ { .command = "copy-pipe-no-clear",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
+ .f = window_copy_cmd_copy_pipe_no_clear
+ },
+ { .command = "copy-pipe",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_pipe
+ },
+ { .command = "copy-pipe-and-cancel",
+ .minargs = 0,
+ .maxargs = 2,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_pipe_and_cancel
+ },
+ { .command = "copy-selection-no-clear",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
+ .f = window_copy_cmd_copy_selection_no_clear
+ },
+ { .command = "copy-selection",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_selection
+ },
+ { .command = "copy-selection-and-cancel",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_copy_selection_and_cancel
+ },
+ { .command = "cursor-down",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_cursor_down
+ },
+ { .command = "cursor-down-and-cancel",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_cursor_down_and_cancel
+ },
+ { .command = "cursor-left",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_cursor_left
+ },
+ { .command = "cursor-right",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_cursor_right
+ },
+ { .command = "cursor-up",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_cursor_up
+ },
+ { .command = "end-of-line",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_end_of_line
+ },
+ { .command = "goto-line",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_goto_line
+ },
+ { .command = "halfpage-down",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_halfpage_down
+ },
+ { .command = "halfpage-down-and-cancel",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_halfpage_down_and_cancel
+ },
+ { .command = "halfpage-up",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_halfpage_up
+ },
+ { .command = "history-bottom",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_history_bottom
+ },
+ { .command = "history-top",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_history_top
+ },
+ { .command = "jump-again",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_jump_again
+ },
+ { .command = "jump-backward",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_jump_backward
+ },
+ { .command = "jump-forward",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_jump_forward
+ },
+ { .command = "jump-reverse",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_jump_reverse
+ },
+ { .command = "jump-to-backward",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_jump_to_backward
+ },
+ { .command = "jump-to-forward",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_jump_to_forward
+ },
+ { .command = "jump-to-mark",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_jump_to_mark
+ },
+ { .command = "middle-line",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_middle_line
+ },
+ { .command = "next-matching-bracket",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_next_matching_bracket
+ },
+ { .command = "next-paragraph",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_next_paragraph
+ },
+ { .command = "next-space",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_next_space
+ },
+ { .command = "next-space-end",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_next_space_end
+ },
+ { .command = "next-word",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_next_word
+ },
+ { .command = "next-word-end",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_next_word_end
+ },
+ { .command = "other-end",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_other_end
+ },
+ { .command = "page-down",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_page_down
+ },
+ { .command = "page-down-and-cancel",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_page_down_and_cancel
+ },
+ { .command = "page-up",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_page_up
+ },
+ { .command = "pipe-no-clear",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
+ .f = window_copy_cmd_pipe_no_clear
+ },
+ { .command = "pipe",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_pipe
+ },
+ { .command = "pipe-and-cancel",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_pipe_and_cancel
+ },
+ { .command = "previous-matching-bracket",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_previous_matching_bracket
+ },
+ { .command = "previous-paragraph",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_previous_paragraph
+ },
+ { .command = "previous-space",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_previous_space
+ },
+ { .command = "previous-word",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_previous_word
+ },
+ { .command = "rectangle-on",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_rectangle_on
+ },
+ { .command = "rectangle-off",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_rectangle_off
+ },
+ { .command = "rectangle-toggle",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_rectangle_toggle
+ },
+ { .command = "refresh-from-pane",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_refresh_from_pane
+ },
+ { .command = "scroll-down",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_scroll_down
+ },
+ { .command = "scroll-down-and-cancel",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_scroll_down_and_cancel
+ },
+ { .command = "scroll-up",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_scroll_up
+ },
+ { .command = "search-again",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_again
+ },
+ { .command = "search-backward",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_backward
+ },
+ { .command = "search-backward-text",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_backward_text
+ },
+ { .command = "search-backward-incremental",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_backward_incremental
+ },
+ { .command = "search-forward",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_forward
+ },
+ { .command = "search-forward-text",
+ .minargs = 0,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_forward_text
+ },
+ { .command = "search-forward-incremental",
+ .minargs = 1,
+ .maxargs = 1,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_forward_incremental
+ },
+ { .command = "search-reverse",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_search_reverse
+ },
+ { .command = "select-line",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_select_line
+ },
+ { .command = "select-word",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_select_word
+ },
+ { .command = "set-mark",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_set_mark
+ },
+ { .command = "start-of-line",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_start_of_line
+ },
+ { .command = "stop-selection",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
+ .f = window_copy_cmd_stop_selection
+ },
+ { .command = "toggle-position",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
+ .f = window_copy_cmd_toggle_position
+ },
+ { .command = "top-line",
+ .minargs = 0,
+ .maxargs = 0,
+ .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
+ .f = window_copy_cmd_top_line
+ }
+};
+
+static void
+window_copy_command(struct window_mode_entry *wme, struct client *c,
+ struct session *s, struct winlink *wl, struct args *args,
+ struct mouse_event *m)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct window_copy_cmd_state cs;
+ enum window_copy_cmd_action action;
+ enum window_copy_cmd_clear clear = WINDOW_COPY_CMD_CLEAR_NEVER;
+ const char *command;
+ u_int i, count = args_count(args);
+ int keys;
+
+ if (count == 0)
+ return;
+ command = args_string(args, 0);
+
+ if (m != NULL && m->valid && !MOUSE_WHEEL(m->b))
+ window_copy_move_mouse(m);
+
+ cs.wme = wme;
+ cs.args = args;
+ cs.m = m;
+
+ cs.c = c;
+ cs.s = s;
+ cs.wl = wl;
+
+ action = WINDOW_COPY_CMD_NOTHING;
+ for (i = 0; i < nitems(window_copy_cmd_table); i++) {
+ if (strcmp(window_copy_cmd_table[i].command, command) == 0) {
+ if (count - 1 < window_copy_cmd_table[i].minargs ||
+ count - 1 > window_copy_cmd_table[i].maxargs)
+ break;
+ clear = window_copy_cmd_table[i].clear;
+ action = window_copy_cmd_table[i].f(&cs);
+ break;
+ }
+ }
+
+ if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) {
+ keys = options_get_number(wme->wp->window->options, "mode-keys");
+ if (clear == WINDOW_COPY_CMD_CLEAR_EMACS_ONLY &&
+ keys == MODEKEY_VI)
+ clear = WINDOW_COPY_CMD_CLEAR_NEVER;
+ if (clear != WINDOW_COPY_CMD_CLEAR_NEVER) {
+ window_copy_clear_marks(wme);
+ data->searchx = data->searchy = -1;
+ }
+ if (action == WINDOW_COPY_CMD_NOTHING)
+ action = WINDOW_COPY_CMD_REDRAW;
+ }
+ wme->prefix = 1;
+
+ if (action == WINDOW_COPY_CMD_CANCEL)
+ window_pane_reset_mode(wme->wp);
+ else if (action == WINDOW_COPY_CMD_REDRAW)
+ window_copy_redraw_screen(wme);
+}
+
+static void
+window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py,
+ int no_redraw)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct grid *gd = data->backing->grid;
+ u_int offset, gap;
+
+ data->cx = px;
+
+ if (py >= gd->hsize - data->oy && py < gd->hsize - data->oy + gd->sy)
+ data->cy = py - (gd->hsize - data->oy);
+ else {
+ gap = gd->sy / 4;
+ if (py < gd->sy) {
+ offset = 0;
+ data->cy = py;
+ } else if (py > gd->hsize + gd->sy - gap) {
+ offset = gd->hsize;
+ data->cy = py - gd->hsize;
+ } else {
+ offset = py + gap - gd->sy;
+ data->cy = py - offset;
+ }
+ data->oy = gd->hsize - offset;
+ }
+
+ if (!no_redraw && data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 1, 0);
+ if (!no_redraw)
+ window_copy_redraw_screen(wme);
+}
+
+static int
+window_copy_search_compare(struct grid *gd, u_int px, u_int py,
+ struct grid *sgd, u_int spx, int cis)
+{
+ struct grid_cell gc, sgc;
+ const struct utf8_data *ud, *sud;
+
+ grid_get_cell(gd, px, py, &gc);
+ ud = &gc.data;
+ grid_get_cell(sgd, spx, 0, &sgc);
+ sud = &sgc.data;
+
+ if (ud->size != sud->size || ud->width != sud->width)
+ return (0);
+
+ if (cis && ud->size == 1)
+ return (tolower(ud->data[0]) == sud->data[0]);
+
+ return (memcmp(ud->data, sud->data, ud->size) == 0);
+}
+
+static int
+window_copy_search_lr(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py,
+ u_int first, u_int last, int cis)
+{
+ u_int ax, bx, px, pywrap, endline;
+ int matched;
+ struct grid_line *gl;
+
+ endline = gd->hsize + gd->sy - 1;
+ for (ax = first; ax < last; ax++) {
+ for (bx = 0; bx < sgd->sx; bx++) {
+ px = ax + bx;
+ pywrap = py;
+ /* Wrap line. */
+ while (px >= gd->sx && pywrap < endline) {
+ gl = grid_get_line(gd, pywrap);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ px -= gd->sx;
+ pywrap++;
+ }
+ /* We have run off the end of the grid. */
+ if (px >= gd->sx)
+ break;
+ matched = window_copy_search_compare(gd, px, pywrap,
+ sgd, bx, cis);
+ if (!matched)
+ break;
+ }
+ if (bx == sgd->sx) {
+ *ppx = ax;
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static int
+window_copy_search_rl(struct grid *gd,
+ struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis)
+{
+ u_int ax, bx, px, pywrap, endline;
+ int matched;
+ struct grid_line *gl;
+
+ endline = gd->hsize + gd->sy - 1;
+ for (ax = last; ax > first; ax--) {
+ for (bx = 0; bx < sgd->sx; bx++) {
+ px = ax - 1 + bx;
+ pywrap = py;
+ /* Wrap line. */
+ while (px >= gd->sx && pywrap < endline) {
+ gl = grid_get_line(gd, pywrap);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ px -= gd->sx;
+ pywrap++;
+ }
+ /* We have run off the end of the grid. */
+ if (px >= gd->sx)
+ break;
+ matched = window_copy_search_compare(gd, px, pywrap,
+ sgd, bx, cis);
+ if (!matched)
+ break;
+ }
+ if (bx == sgd->sx) {
+ *ppx = ax - 1;
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static int
+window_copy_search_lr_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py,
+ u_int first, u_int last, regex_t *reg)
+{
+ int eflags = 0;
+ u_int endline, foundx, foundy, len, pywrap, size = 1;
+ char *buf;
+ regmatch_t regmatch;
+ struct grid_line *gl;
+
+ /*
+ * This can happen during search if the last match was the last
+ * character on a line.
+ */
+ if (first >= last)
+ return (0);
+
+ /* Set flags for regex search. */
+ if (first != 0)
+ eflags |= REG_NOTBOL;
+
+ /* Need to look at the entire string. */
+ buf = xmalloc(size);
+ buf[0] = '\0';
+ buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size);
+ len = gd->sx - first;
+ endline = gd->hsize + gd->sy - 1;
+ pywrap = py;
+ while (buf != NULL && pywrap <= endline) {
+ gl = grid_get_line(gd, pywrap);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ pywrap++;
+ buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size);
+ len += gd->sx;
+ }
+
+ if (regexec(reg, buf, 1, &regmatch, eflags) == 0 &&
+ regmatch.rm_so != regmatch.rm_eo) {
+ foundx = first;
+ foundy = py;
+ window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
+ buf + regmatch.rm_so);
+ if (foundy == py && foundx < last) {
+ *ppx = foundx;
+ len -= foundx - first;
+ window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
+ buf + regmatch.rm_eo);
+ *psx = foundx;
+ while (foundy > py) {
+ *psx += gd->sx;
+ foundy--;
+ }
+ *psx -= *ppx;
+ free(buf);
+ return (1);
+ }
+ }
+
+ free(buf);
+ *ppx = 0;
+ *psx = 0;
+ return (0);
+}
+
+static int
+window_copy_search_rl_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py,
+ u_int first, u_int last, regex_t *reg)
+{
+ int eflags = 0;
+ u_int endline, len, pywrap, size = 1;
+ char *buf;
+ struct grid_line *gl;
+
+ /* Set flags for regex search. */
+ if (first != 0)
+ eflags |= REG_NOTBOL;
+
+ /* Need to look at the entire string. */
+ buf = xmalloc(size);
+ buf[0] = '\0';
+ buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size);
+ len = gd->sx - first;
+ endline = gd->hsize + gd->sy - 1;
+ pywrap = py;
+ while (buf != NULL && (pywrap <= endline)) {
+ gl = grid_get_line(gd, pywrap);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ pywrap++;
+ buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size);
+ len += gd->sx;
+ }
+
+ if (window_copy_last_regex(gd, py, first, last, len, ppx, psx, buf,
+ reg, eflags))
+ {
+ free(buf);
+ return (1);
+ }
+
+ free(buf);
+ *ppx = 0;
+ *psx = 0;
+ return (0);
+}
+
+static const char *
+window_copy_cellstring(const struct grid_line *gl, u_int px, size_t *size,
+ int *allocated)
+{
+ static struct utf8_data ud;
+ struct grid_cell_entry *gce;
+ char *copy;
+
+ if (px >= gl->cellsize) {
+ *size = 1;
+ *allocated = 0;
+ return (" ");
+ }
+
+ gce = &gl->celldata[px];
+ if (gce->flags & GRID_FLAG_PADDING) {
+ *size = 0;
+ *allocated = 0;
+ return (NULL);
+ }
+ if (~gce->flags & GRID_FLAG_EXTENDED) {
+ *size = 1;
+ *allocated = 0;
+ return (&gce->data.data);
+ }
+
+ utf8_to_data(gl->extddata[gce->offset].data, &ud);
+ if (ud.size == 0) {
+ *size = 0;
+ *allocated = 0;
+ return (NULL);
+ }
+ *size = ud.size;
+ *allocated = 1;
+
+ copy = xmalloc(ud.size);
+ memcpy(copy, ud.data, ud.size);
+ return (copy);
+}
+
+/* Find last match in given range. */
+static int
+window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last,
+ u_int len, u_int *ppx, u_int *psx, const char *buf, const regex_t *preg,
+ int eflags)
+{
+ u_int foundx, foundy, oldx, px = 0, savepx, savesx = 0;
+ regmatch_t regmatch;
+
+ foundx = first;
+ foundy = py;
+ oldx = first;
+ while (regexec(preg, buf + px, 1, &regmatch, eflags) == 0) {
+ if (regmatch.rm_so == regmatch.rm_eo)
+ break;
+ window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
+ buf + px + regmatch.rm_so);
+ if (foundy > py || foundx >= last)
+ break;
+ len -= foundx - oldx;
+ savepx = foundx;
+ window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
+ buf + px + regmatch.rm_eo);
+ if (foundy > py || foundx >= last) {
+ *ppx = savepx;
+ *psx = foundx;
+ while (foundy > py) {
+ *psx += gd->sx;
+ foundy--;
+ }
+ *psx -= *ppx;
+ return (1);
+ } else {
+ savesx = foundx - savepx;
+ len -= savesx;
+ oldx = foundx;
+ }
+ px += regmatch.rm_eo;
+ }
+
+ if (savesx > 0) {
+ *ppx = savepx;
+ *psx = savesx;
+ return (1);
+ } else {
+ *ppx = 0;
+ *psx = 0;
+ return (0);
+ }
+}
+
+/* Stringify line and append to input buffer. Caller frees. */
+static char *
+window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last,
+ char *buf, u_int *size)
+{
+ u_int ax, bx, newsize = *size;
+ const struct grid_line *gl;
+ const char *d;
+ size_t bufsize = 1024, dlen;
+ int allocated;
+
+ while (bufsize < newsize)
+ bufsize *= 2;
+ buf = xrealloc(buf, bufsize);
+
+ gl = grid_peek_line(gd, py);
+ bx = *size - 1;
+ for (ax = first; ax < last; ax++) {
+ d = window_copy_cellstring(gl, ax, &dlen, &allocated);
+ newsize += dlen;
+ while (bufsize < newsize) {
+ bufsize *= 2;
+ buf = xrealloc(buf, bufsize);
+ }
+ if (dlen == 1)
+ buf[bx++] = *d;
+ else {
+ memcpy(buf + bx, d, dlen);
+ bx += dlen;
+ }
+ if (allocated)
+ free((void *)d);
+ }
+ buf[newsize - 1] = '\0';
+
+ *size = newsize;
+ return (buf);
+}
+
+/* Map start of C string containing UTF-8 data to grid cell position. */
+static void
+window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy,
+ const char *str)
+{
+ u_int cell, ccell, px, pywrap, pos, len;
+ int match;
+ const struct grid_line *gl;
+ const char *d;
+ size_t dlen;
+ struct {
+ const char *d;
+ size_t dlen;
+ int allocated;
+ } *cells;
+
+ /* Populate the array of cell data. */
+ cells = xreallocarray(NULL, ncells, sizeof cells[0]);
+ cell = 0;
+ px = *ppx;
+ pywrap = *ppy;
+ gl = grid_peek_line(gd, pywrap);
+ while (cell < ncells) {
+ cells[cell].d = window_copy_cellstring(gl, px,
+ &cells[cell].dlen, &cells[cell].allocated);
+ cell++;
+ px++;
+ if (px == gd->sx) {
+ px = 0;
+ pywrap++;
+ gl = grid_peek_line(gd, pywrap);
+ }
+ }
+
+ /* Locate starting cell. */
+ cell = 0;
+ len = strlen(str);
+ while (cell < ncells) {
+ ccell = cell;
+ pos = 0;
+ match = 1;
+ while (ccell < ncells) {
+ if (str[pos] == '\0') {
+ match = 0;
+ break;
+ }
+ d = cells[ccell].d;
+ dlen = cells[ccell].dlen;
+ if (dlen == 1) {
+ if (str[pos] != *d) {
+ match = 0;
+ break;
+ }
+ pos++;
+ } else {
+ if (dlen > len - pos)
+ dlen = len - pos;
+ if (memcmp(str + pos, d, dlen) != 0) {
+ match = 0;
+ break;
+ }
+ pos += dlen;
+ }
+ ccell++;
+ }
+ if (match)
+ break;
+ cell++;
+ }
+
+ /* If not found this will be one past the end. */
+ px = *ppx + cell;
+ pywrap = *ppy;
+ while (px >= gd->sx) {
+ px -= gd->sx;
+ pywrap++;
+ }
+
+ *ppx = px;
+ *ppy = pywrap;
+
+ /* Free cell data. */
+ for (cell = 0; cell < ncells; cell++) {
+ if (cells[cell].allocated)
+ free((void *)cells[cell].d);
+ }
+ free(cells);
+}
+
+static void
+window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag)
+{
+ if (*fx == 0) { /* left */
+ if (*fy == 0) { /* top */
+ if (wrapflag) {
+ *fx = screen_size_x(s) - 1;
+ *fy = screen_hsize(s) + screen_size_y(s) - 1;
+ }
+ return;
+ }
+ *fx = screen_size_x(s) - 1;
+ *fy = *fy - 1;
+ } else
+ *fx = *fx - 1;
+}
+
+static void
+window_copy_move_right(struct screen *s, u_int *fx, u_int *fy, int wrapflag)
+{
+ if (*fx == screen_size_x(s) - 1) { /* right */
+ if (*fy == screen_hsize(s) + screen_size_y(s) - 1) { /* bottom */
+ if (wrapflag) {
+ *fx = 0;
+ *fy = 0;
+ }
+ return;
+ }
+ *fx = 0;
+ *fy = *fy + 1;
+ } else
+ *fx = *fx + 1;
+}
+
+static int
+window_copy_is_lowercase(const char *ptr)
+{
+ while (*ptr != '\0') {
+ if (*ptr != tolower((u_char)*ptr))
+ return (0);
+ ++ptr;
+ }
+ return (1);
+}
+
+/*
+ * Handle backward wrapped regex searches with overlapping matches. In this case
+ * find the longest overlapping match from previous wrapped lines.
+ */
+static void
+window_copy_search_back_overlap(struct grid *gd, regex_t *preg, u_int *ppx,
+ u_int *psx, u_int *ppy, u_int endline)
+{
+ u_int endx, endy, oldendx, oldendy, px, py, sx;
+ int found = 1;
+
+ oldendx = *ppx + *psx;
+ oldendy = *ppy - 1;
+ while (oldendx > gd->sx - 1) {
+ oldendx -= gd->sx;
+ oldendy++;
+ }
+ endx = oldendx;
+ endy = oldendy;
+ px = *ppx;
+ py = *ppy;
+ while (found && px == 0 && py - 1 > endline &&
+ grid_get_line(gd, py - 2)->flags & GRID_LINE_WRAPPED &&
+ endx == oldendx && endy == oldendy) {
+ py--;
+ found = window_copy_search_rl_regex(gd, &px, &sx, py - 1, 0,
+ gd->sx, preg);
+ if (found) {
+ endx = px + sx;
+ endy = py - 1;
+ while (endx > gd->sx - 1) {
+ endx -= gd->sx;
+ endy++;
+ }
+ if (endx == oldendx && endy == oldendy) {
+ *ppx = px;
+ *ppy = py;
+ }
+ }
+ }
+}
+
+/*
+ * Search for text stored in sgd starting from position fx,fy up to endline. If
+ * found, jump to it. If cis then ignore case. The direction is 0 for searching
+ * up, down otherwise. If wrap then go to begin/end of grid and try again if
+ * not found.
+ */
+static int
+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;
+
+ if (regex) {
+ sbuf = xmalloc(ssize);
+ sbuf[0] = '\0';
+ sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize);
+ if (cis)
+ cflags |= REG_ICASE;
+ if (regcomp(&reg, sbuf, cflags) != 0) {
+ free(sbuf);
+ return (0);
+ }
+ free(sbuf);
+ }
+
+ if (direction) {
+ for (i = fy; i <= endline; i++) {
+ if (regex) {
+ found = window_copy_search_lr_regex(gd,
+ &px, &sx, i, fx, gd->sx, &reg);
+ } else {
+ found = window_copy_search_lr(gd, sgd,
+ &px, i, fx, gd->sx, cis);
+ }
+ if (found)
+ break;
+ fx = 0;
+ }
+ } else {
+ for (i = fy + 1; endline < i; i--) {
+ if (regex) {
+ found = window_copy_search_rl_regex(gd,
+ &px, &sx, i - 1, 0, fx + 1, &reg);
+ if (found) {
+ window_copy_search_back_overlap(gd,
+ &reg, &px, &sx, &i, endline);
+ }
+ } else {
+ found = window_copy_search_rl(gd, sgd,
+ &px, i - 1, 0, fx + 1, cis);
+ }
+ if (found) {
+ i--;
+ break;
+ }
+ fx = gd->sx - 1;
+ }
+ }
+ if (regex)
+ regfree(&reg);
+
+ if (found) {
+ window_copy_scroll_to(wme, px, i, 1);
+ return (1);
+ }
+ if (wrap) {
+ return (window_copy_search_jump(wme, gd, sgd,
+ direction ? 0 : gd->sx - 1,
+ direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0,
+ direction, regex));
+ }
+ return (0);
+}
+
+static void
+window_copy_move_after_search_mark(struct window_copy_mode_data *data,
+ u_int *fx, u_int *fy, int wrapflag)
+{
+ struct screen *s = data->backing;
+ u_int at, start;
+
+ if (window_copy_search_mark_at(data, *fx, *fy, &start) == 0 &&
+ data->searchmark[start] != 0) {
+ while (window_copy_search_mark_at(data, *fx, *fy, &at) == 0) {
+ if (data->searchmark[at] != data->searchmark[start])
+ break;
+ /* Stop if not wrapping and at the end of the grid. */
+ if (!wrapflag &&
+ *fx == screen_size_x(s) - 1 &&
+ *fy == screen_hsize(s) + screen_size_y(s) - 1)
+ break;
+
+ window_copy_move_right(s, fx, fy, wrapflag);
+ }
+ }
+}
+
+/*
+ * Search in for text searchstr. If direction is 0 then search up, otherwise
+ * down.
+ */
+static int
+window_copy_search(struct window_mode_entry *wme, int direction, int regex)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = data->backing, ss;
+ struct screen_write_ctx ctx;
+ struct grid *gd = s->grid;
+ const char *str = data->searchstr;
+ u_int at, endline, fx, fy, start;
+ int cis, found, keys, visible_only;
+ int wrapflag;
+
+ if (regex && str[strcspn(str, "^$*+()?[].\\")] == '\0')
+ regex = 0;
+
+ data->searchdirection = direction;
+
+ if (data->timeout)
+ return (0);
+
+ if (data->searchall || wp->searchstr == NULL ||
+ wp->searchregex != regex) {
+ visible_only = 0;
+ data->searchall = 0;
+ } else
+ visible_only = (strcmp(wp->searchstr, str) == 0);
+ free(wp->searchstr);
+ wp->searchstr = xstrdup(str);
+ wp->searchregex = regex;
+
+ fx = data->cx;
+ fy = screen_hsize(data->backing) - data->oy + data->cy;
+
+ screen_init(&ss, screen_write_strlen("%s", str), 1, 0);
+ screen_write_start(&ctx, &ss);
+ screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", str);
+ screen_write_stop(&ctx);
+
+ wrapflag = options_get_number(wp->window->options, "wrap-search");
+ cis = window_copy_is_lowercase(str);
+
+ keys = options_get_number(wp->window->options, "mode-keys");
+
+ if (direction) {
+ /*
+ * Behave according to mode-keys. If it is emacs, search forward
+ * leaves the cursor after the match. If it is vi, the cursor
+ * remains at the beginning of the match, regardless of
+ * direction, which means that we need to start the next search
+ * after the term the cursor is currently on when searching
+ * forward.
+ */
+ if (keys == MODEKEY_VI) {
+ if (data->searchmark != NULL)
+ window_copy_move_after_search_mark(data, &fx,
+ &fy, wrapflag);
+ else {
+ /*
+ * When there are no search marks, start the
+ * search after the current cursor position.
+ */
+ window_copy_move_right(s, &fx, &fy, wrapflag);
+ }
+ }
+ endline = gd->hsize + gd->sy - 1;
+ }
+ else {
+ window_copy_move_left(s, &fx, &fy, wrapflag);
+ endline = 0;
+ }
+
+ found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis,
+ wrapflag, direction, regex);
+ if (found) {
+ window_copy_search_marks(wme, &ss, regex, visible_only);
+ fx = data->cx;
+ fy = screen_hsize(data->backing) - data->oy + data->cy;
+
+ /*
+ * When searching forward, if the cursor is not at the beginning
+ * of the mark, search again.
+ */
+ if (direction &&
+ window_copy_search_mark_at(data, fx, fy, &at) == 0 &&
+ at > 0 &&
+ data->searchmark[at] == data->searchmark[at - 1]) {
+ window_copy_move_after_search_mark(data, &fx, &fy,
+ wrapflag);
+ window_copy_search_jump(wme, gd, ss.grid, fx,
+ fy, endline, cis, wrapflag, direction,
+ regex);
+ fx = data->cx;
+ fy = screen_hsize(data->backing) - data->oy + data->cy;
+ }
+
+ if (direction) {
+ /*
+ * When in Emacs mode, position the cursor just after
+ * the mark.
+ */
+ if (keys == MODEKEY_EMACS) {
+ window_copy_move_after_search_mark(data, &fx,
+ &fy, wrapflag);
+ data->cx = fx;
+ data->cy = fy - screen_hsize(data->backing) +
+ data-> oy;
+ }
+ }
+ else {
+ /*
+ * When searching backward, position the cursor at the
+ * beginning of the mark.
+ */
+ if (window_copy_search_mark_at(data, fx, fy,
+ &start) == 0) {
+ while (window_copy_search_mark_at(data, fx, fy,
+ &at) == 0 &&
+ data->searchmark[at] ==
+ data->searchmark[start]) {
+ data->cx = fx;
+ data->cy = fy -
+ screen_hsize(data->backing) +
+ data-> oy;
+ if (at == 0)
+ break;
+
+ window_copy_move_left(s, &fx, &fy, 0);
+ }
+ }
+ }
+ }
+ window_copy_redraw_screen(wme);
+
+ screen_free(&ss);
+ return (found);
+}
+
+static void
+window_copy_visible_lines(struct window_copy_mode_data *data, u_int *start,
+ u_int *end)
+{
+ struct grid *gd = data->backing->grid;
+ const struct grid_line *gl;
+
+ for (*start = gd->hsize - data->oy; *start > 0; (*start)--) {
+ gl = grid_peek_line(gd, (*start) - 1);
+ if (~gl->flags & GRID_LINE_WRAPPED)
+ break;
+ }
+ *end = gd->hsize - data->oy + gd->sy;
+}
+
+static int
+window_copy_search_mark_at(struct window_copy_mode_data *data, u_int px,
+ u_int py, u_int *at)
+{
+ struct screen *s = data->backing;
+ struct grid *gd = s->grid;
+
+ if (py < gd->hsize - data->oy)
+ return (-1);
+ if (py > gd->hsize - data->oy + gd->sy - 1)
+ return (-1);
+ *at = ((py - (gd->hsize - data->oy)) * gd->sx) + px;
+ return (0);
+}
+
+static int
+window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
+ int regex, int visible_only)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = data->backing, ss;
+ struct screen_write_ctx ctx;
+ struct grid *gd = s->grid;
+ int found, cis, stopped = 0;
+ int cflags = REG_EXTENDED;
+ u_int px, py, i, b, nfound = 0, width;
+ u_int ssize = 1, start, end;
+ char *sbuf;
+ regex_t reg;
+ uint64_t stop = 0, tstart, t;
+
+ if (ssp == NULL) {
+ width = screen_write_strlen("%s", data->searchstr);
+ screen_init(&ss, width, 1, 0);
+ screen_write_start(&ctx, &ss);
+ screen_write_nputs(&ctx, -1, &grid_default_cell, "%s",
+ data->searchstr);
+ screen_write_stop(&ctx);
+ ssp = &ss;
+ } else
+ width = screen_size_x(ssp);
+
+ cis = window_copy_is_lowercase(data->searchstr);
+
+ if (regex) {
+ sbuf = xmalloc(ssize);
+ sbuf[0] = '\0';
+ sbuf = window_copy_stringify(ssp->grid, 0, 0, ssp->grid->sx,
+ sbuf, &ssize);
+ if (cis)
+ cflags |= REG_ICASE;
+ if (regcomp(&reg, sbuf, cflags) != 0) {
+ free(sbuf);
+ return (0);
+ }
+ free(sbuf);
+ }
+ tstart = get_timer();
+
+ if (visible_only)
+ window_copy_visible_lines(data, &start, &end);
+ else {
+ start = 0;
+ end = gd->hsize + gd->sy;
+ stop = get_timer() + WINDOW_COPY_SEARCH_ALL_TIMEOUT;
+ }
+
+again:
+ free(data->searchmark);
+ data->searchmark = xcalloc(gd->sx, gd->sy);
+ data->searchgen = 1;
+
+ for (py = start; py < end; py++) {
+ px = 0;
+ for (;;) {
+ if (regex) {
+ found = window_copy_search_lr_regex(gd,
+ &px, &width, py, px, gd->sx, &reg);
+ if (!found)
+ break;
+ } else {
+ found = window_copy_search_lr(gd, ssp->grid,
+ &px, py, px, gd->sx, cis);
+ if (!found)
+ break;
+ }
+ nfound++;
+
+ if (window_copy_search_mark_at(data, px, py, &b) == 0) {
+ if (b + width > gd->sx * gd->sy)
+ width = (gd->sx * gd->sy) - b;
+ for (i = b; i < b + width; i++) {
+ if (data->searchmark[i] != 0)
+ continue;
+ data->searchmark[i] = data->searchgen;
+ }
+ if (data->searchgen == UCHAR_MAX)
+ data->searchgen = 1;
+ else
+ data->searchgen++;
+ }
+ px += width;
+ }
+
+ t = get_timer();
+ if (t - tstart > WINDOW_COPY_SEARCH_TIMEOUT) {
+ data->timeout = 1;
+ break;
+ }
+ if (stop != 0 && t > stop) {
+ stopped = 1;
+ break;
+ }
+ }
+ if (data->timeout) {
+ window_copy_clear_marks(wme);
+ goto out;
+ }
+
+ if (stopped && stop != 0) {
+ /* Try again but just the visible context. */
+ window_copy_visible_lines(data, &start, &end);
+ stop = 0;
+ goto again;
+ }
+
+ if (!visible_only) {
+ if (stopped) {
+ if (nfound > 1000)
+ data->searchcount = 1000;
+ else if (nfound > 100)
+ data->searchcount = 100;
+ else if (nfound > 10)
+ data->searchcount = 10;
+ else
+ data->searchcount = -1;
+ data->searchmore = 1;
+ } else {
+ data->searchcount = nfound;
+ data->searchmore = 0;
+ }
+ }
+
+out:
+ if (ssp == &ss)
+ screen_free(&ss);
+ if (regex)
+ regfree(&reg);
+ return (1);
+}
+
+static void
+window_copy_clear_marks(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ free(data->searchmark);
+ data->searchmark = NULL;
+}
+
+static int
+window_copy_search_up(struct window_mode_entry *wme, int regex)
+{
+ return (window_copy_search(wme, 0, regex));
+}
+
+static int
+window_copy_search_down(struct window_mode_entry *wme, int regex)
+{
+ return (window_copy_search(wme, 1, regex));
+}
+
+static void
+window_copy_goto_line(struct window_mode_entry *wme, const char *linestr)
+{
+ struct window_copy_mode_data *data = wme->data;
+ const char *errstr;
+ int lineno;
+
+ lineno = strtonum(linestr, -1, INT_MAX, &errstr);
+ if (errstr != NULL)
+ return;
+ if (lineno < 0 || (u_int)lineno > screen_hsize(data->backing))
+ lineno = screen_hsize(data->backing);
+
+ data->oy = lineno;
+ window_copy_update_selection(wme, 1, 0);
+ window_copy_redraw_screen(wme);
+}
+
+static void
+window_copy_match_start_end(struct window_copy_mode_data *data, u_int at,
+ u_int *start, u_int *end)
+{
+ struct grid *gd = data->backing->grid;
+ u_int last = (gd->sy * gd->sx) - 1;
+ u_char mark = data->searchmark[at];
+
+ *start = *end = at;
+ while (*start != 0 && data->searchmark[*start] == mark)
+ (*start)--;
+ if (data->searchmark[*start] != mark)
+ (*start)++;
+ while (*end != last && data->searchmark[*end] == mark)
+ (*end)++;
+ if (data->searchmark[*end] != mark)
+ (*end)--;
+}
+
+static char *
+window_copy_match_at_cursor(struct window_copy_mode_data *data)
+{
+ struct grid *gd = data->backing->grid;
+ struct grid_cell gc;
+ u_int at, start, end, cy, px, py;
+ u_int sx = screen_size_x(data->backing);
+ char *buf = NULL;
+ size_t len = 0;
+
+ if (data->searchmark == NULL)
+ return (NULL);
+
+ cy = screen_hsize(data->backing) - data->oy + data->cy;
+ if (window_copy_search_mark_at(data, data->cx, cy, &at) != 0)
+ return (NULL);
+ if (data->searchmark[at] == 0) {
+ /* Allow one position after the match. */
+ if (at == 0 || data->searchmark[--at] == 0)
+ return (NULL);
+ }
+ window_copy_match_start_end(data, at, &start, &end);
+
+ /*
+ * Cells will not be set in the marked array unless they are valid text
+ * and wrapping will be taken care of, so we can just copy.
+ */
+ for (at = start; at <= end; at++) {
+ py = at / sx;
+ px = at - (py * sx);
+
+ grid_get_cell(gd, px, gd->hsize + py - data->oy, &gc);
+ buf = xrealloc(buf, len + gc.data.size + 1);
+ memcpy(buf + len, gc.data.data, gc.data.size);
+ len += gc.data.size;
+ }
+ if (len != 0)
+ buf[len] = '\0';
+ return (buf);
+}
+
+static void
+window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy,
+ struct grid_cell *gc, const struct grid_cell *mgc,
+ const struct grid_cell *cgc, const struct grid_cell *mkgc)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ u_int mark, start, end, cy, cursor, current;
+ int inv = 0, found = 0;
+ int keys;
+
+ if (data->showmark && fy == data->my) {
+ gc->attr = mkgc->attr;
+ if (fx == data->mx)
+ inv = 1;
+ if (inv) {
+ gc->fg = mkgc->bg;
+ gc->bg = mkgc->fg;
+ }
+ else {
+ gc->fg = mkgc->fg;
+ gc->bg = mkgc->bg;
+ }
+ }
+
+ if (data->searchmark == NULL)
+ return;
+
+ if (window_copy_search_mark_at(data, fx, fy, &current) != 0)
+ return;
+ mark = data->searchmark[current];
+ if (mark == 0)
+ return;
+
+ cy = screen_hsize(data->backing) - data->oy + data->cy;
+ if (window_copy_search_mark_at(data, data->cx, cy, &cursor) == 0) {
+ keys = options_get_number(wp->window->options, "mode-keys");
+ if (cursor != 0 &&
+ keys == MODEKEY_EMACS &&
+ data->searchdirection) {
+ if (data->searchmark[cursor - 1] == mark) {
+ cursor--;
+ found = 1;
+ }
+ } else if (data->searchmark[cursor] == mark)
+ found = 1;
+ if (found) {
+ window_copy_match_start_end(data, cursor, &start, &end);
+ if (current >= start && current <= end) {
+ gc->attr = cgc->attr;
+ if (inv) {
+ gc->fg = cgc->bg;
+ gc->bg = cgc->fg;
+ }
+ else {
+ gc->fg = cgc->fg;
+ gc->bg = cgc->bg;
+ }
+ return;
+ }
+ }
+ }
+
+ gc->attr = mgc->attr;
+ if (inv) {
+ gc->fg = mgc->bg;
+ gc->bg = mgc->fg;
+ }
+ else {
+ gc->fg = mgc->fg;
+ gc->bg = mgc->bg;
+ }
+}
+
+static void
+window_copy_write_one(struct window_mode_entry *wme,
+ struct screen_write_ctx *ctx, u_int py, u_int fy, u_int nx,
+ const struct grid_cell *mgc, const struct grid_cell *cgc,
+ const struct grid_cell *mkgc)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct grid *gd = data->backing->grid;
+ struct grid_cell gc;
+ u_int fx;
+
+ screen_write_cursormove(ctx, 0, py, 0);
+ for (fx = 0; fx < nx; fx++) {
+ grid_get_cell(gd, fx, fy, &gc);
+ if (fx + gc.data.width <= nx) {
+ window_copy_update_style(wme, fx, fy, &gc, mgc, cgc,
+ mkgc);
+ screen_write_cell(ctx, &gc);
+ }
+ }
+}
+
+static void
+window_copy_write_line(struct window_mode_entry *wme,
+ struct screen_write_ctx *ctx, u_int py)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct options *oo = wp->window->options;
+ struct grid_cell gc, mgc, cgc, mkgc;
+ char hdr[512];
+ size_t size = 0;
+ u_int hsize = screen_hsize(data->backing);
+
+ style_apply(&gc, oo, "mode-style", NULL);
+ gc.flags |= GRID_FLAG_NOPALETTE;
+ style_apply(&mgc, oo, "copy-mode-match-style", NULL);
+ mgc.flags |= GRID_FLAG_NOPALETTE;
+ style_apply(&cgc, oo, "copy-mode-current-match-style", NULL);
+ cgc.flags |= GRID_FLAG_NOPALETTE;
+ style_apply(&mkgc, oo, "copy-mode-mark-style", NULL);
+ mkgc.flags |= GRID_FLAG_NOPALETTE;
+
+ if (py == 0 && s->rupper < s->rlower && !data->hide_position) {
+ 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);
+ }
+ } else {
+ if (data->searchcount == -1) {
+ size = xsnprintf(hdr, sizeof hdr,
+ "[%u/%u]", data->oy, hsize);
+ } else {
+ size = xsnprintf(hdr, sizeof hdr,
+ "(%d%s results) [%u/%u]", data->searchcount,
+ data->searchmore ? "+" : "", data->oy,
+ hsize);
+ }
+ }
+ if (size > screen_size_x(s))
+ size = screen_size_x(s);
+ screen_write_cursormove(ctx, screen_size_x(s) - size, 0, 0);
+ screen_write_puts(ctx, &gc, "%s", hdr);
+ } else
+ size = 0;
+
+ if (size < screen_size_x(s)) {
+ window_copy_write_one(wme, ctx, py, hsize - data->oy + py,
+ screen_size_x(s) - size, &mgc, &cgc, &mkgc);
+ }
+
+ if (py == data->cy && data->cx == screen_size_x(s)) {
+ screen_write_cursormove(ctx, screen_size_x(s) - 1, py, 0);
+ screen_write_putc(ctx, &grid_default_cell, '$');
+ }
+}
+
+static void
+window_copy_write_lines(struct window_mode_entry *wme,
+ struct screen_write_ctx *ctx, u_int py, u_int ny)
+{
+ u_int yy;
+
+ for (yy = py; yy < py + ny; yy++)
+ window_copy_write_line(wme, ctx, py);
+}
+
+static void
+window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct grid *gd = data->backing->grid;
+ u_int new_y, start, end;
+
+ new_y = data->cy;
+ if (old_y <= new_y) {
+ start = old_y;
+ end = new_y;
+ } else {
+ start = new_y;
+ end = old_y;
+ }
+
+ /*
+ * In word selection mode the first word on the line below the cursor
+ * might be selected, so add this line to the redraw area.
+ */
+ if (data->selflag == SEL_WORD) {
+ /* Last grid line in data coordinates. */
+ if (end < gd->sy + data->oy - 1)
+ end++;
+ }
+ window_copy_redraw_lines(wme, start, end - start + 1);
+}
+
+static void
+window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen_write_ctx ctx;
+ u_int i;
+
+ screen_write_start_pane(&ctx, wp, NULL);
+ for (i = py; i < py + ny; i++)
+ window_copy_write_line(wme, &ctx, i);
+ screen_write_cursormove(&ctx, data->cx, data->cy, 0);
+ screen_write_stop(&ctx);
+}
+
+static void
+window_copy_redraw_screen(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ window_copy_redraw_lines(wme, 0, screen_size_y(&data->screen));
+}
+
+static void
+window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin,
+ int no_reset)
+{
+ struct window_copy_mode_data *data = wme->data;
+ u_int xx, yy;
+
+ xx = data->cx;
+ yy = screen_hsize(data->backing) + data->cy - data->oy;
+ switch (data->selflag) {
+ case SEL_WORD:
+ if (no_reset)
+ break;
+ begin = 0;
+ if (data->dy > yy || (data->dy == yy && data->dx > xx)) {
+ /* Right to left selection. */
+ window_copy_cursor_previous_word_pos(wme,
+ data->separators, &xx, &yy);
+ begin = 1;
+
+ /* Reset the end. */
+ data->endselx = data->endselrx;
+ data->endsely = data->endselry;
+ } else {
+ /* Left to right selection. */
+ if (xx >= window_copy_find_length(wme, yy) ||
+ !window_copy_in_set(wme, xx + 1, yy, WHITESPACE)) {
+ window_copy_cursor_next_word_end_pos(wme,
+ data->separators, &xx, &yy);
+ }
+
+ /* Reset the start. */
+ data->selx = data->selrx;
+ data->sely = data->selry;
+ }
+ break;
+ case SEL_LINE:
+ if (no_reset)
+ break;
+ begin = 0;
+ if (data->dy > yy) {
+ /* Right to left selection. */
+ xx = 0;
+ begin = 1;
+
+ /* Reset the end. */
+ data->endselx = data->endselrx;
+ data->endsely = data->endselry;
+ } else {
+ /* Left to right selection. */
+ if (yy < data->endselry)
+ yy = data->endselry;
+ xx = window_copy_find_length(wme, yy);
+
+ /* Reset the start. */
+ data->selx = data->selrx;
+ data->sely = data->selry;
+ }
+ break;
+ case SEL_CHAR:
+ break;
+ }
+ if (begin) {
+ data->selx = xx;
+ data->sely = yy;
+ } else {
+ data->endselx = xx;
+ data->endsely = yy;
+ }
+}
+
+static void
+window_copy_synchronize_cursor(struct window_mode_entry *wme, int no_reset)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ switch (data->cursordrag) {
+ case CURSORDRAG_ENDSEL:
+ window_copy_synchronize_cursor_end(wme, 0, no_reset);
+ break;
+ case CURSORDRAG_SEL:
+ window_copy_synchronize_cursor_end(wme, 1, no_reset);
+ break;
+ case CURSORDRAG_NONE:
+ break;
+ }
+}
+
+static void
+window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct screen_write_ctx ctx;
+ u_int old_cx, old_cy;
+
+ old_cx = data->cx; old_cy = data->cy;
+ data->cx = cx; data->cy = cy;
+ if (old_cx == screen_size_x(s))
+ window_copy_redraw_lines(wme, old_cy, 1);
+ if (data->cx == screen_size_x(s))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ else {
+ screen_write_start_pane(&ctx, wp, NULL);
+ screen_write_cursormove(&ctx, data->cx, data->cy, 0);
+ screen_write_stop(&ctx);
+ }
+}
+
+static void
+window_copy_start_selection(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ data->selx = data->cx;
+ data->sely = screen_hsize(data->backing) + data->cy - data->oy;
+
+ data->endselx = data->selx;
+ data->endsely = data->sely;
+
+ data->cursordrag = CURSORDRAG_ENDSEL;
+
+ window_copy_set_selection(wme, 1, 0);
+}
+
+static int
+window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx,
+ u_int *sely)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int sx, sy, ty;
+ int relpos;
+
+ sx = *selx;
+ sy = *sely;
+
+ ty = screen_hsize(data->backing) - data->oy;
+ if (sy < ty) {
+ relpos = WINDOW_COPY_REL_POS_ABOVE;
+ if (!data->rectflag)
+ sx = 0;
+ sy = 0;
+ } else if (sy > ty + screen_size_y(s) - 1) {
+ relpos = WINDOW_COPY_REL_POS_BELOW;
+ if (!data->rectflag)
+ sx = screen_size_x(s) - 1;
+ sy = screen_size_y(s) - 1;
+ } else {
+ relpos = WINDOW_COPY_REL_POS_ON_SCREEN;
+ sy -= ty;
+ }
+
+ *selx = sx;
+ *sely = sy;
+ return (relpos);
+}
+
+static int
+window_copy_update_selection(struct window_mode_entry *wme, int may_redraw,
+ int no_reset)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+
+ if (s->sel == NULL && data->lineflag == LINE_SEL_NONE)
+ return (0);
+ return (window_copy_set_selection(wme, may_redraw, no_reset));
+}
+
+static int
+window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
+ int no_reset)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct options *oo = wp->window->options;
+ struct grid_cell gc;
+ u_int sx, sy, cy, endsx, endsy;
+ int startrelpos, endrelpos;
+
+ window_copy_synchronize_cursor(wme, no_reset);
+
+ /* Adjust the selection. */
+ sx = data->selx;
+ sy = data->sely;
+ startrelpos = window_copy_adjust_selection(wme, &sx, &sy);
+
+ /* Adjust the end of selection. */
+ endsx = data->endselx;
+ endsy = data->endsely;
+ endrelpos = window_copy_adjust_selection(wme, &endsx, &endsy);
+
+ /* Selection is outside of the current screen */
+ if (startrelpos == endrelpos &&
+ startrelpos != WINDOW_COPY_REL_POS_ON_SCREEN) {
+ screen_hide_selection(s);
+ return (0);
+ }
+
+ /* Set colours and selection. */
+ style_apply(&gc, oo, "mode-style", NULL);
+ gc.flags |= GRID_FLAG_NOPALETTE;
+ screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag,
+ data->modekeys, &gc);
+
+ if (data->rectflag && may_redraw) {
+ /*
+ * Can't rely on the caller to redraw the right lines for
+ * rectangle selection - find the highest line and the number
+ * of lines, and redraw just past that in both directions
+ */
+ cy = data->cy;
+ if (data->cursordrag == CURSORDRAG_ENDSEL) {
+ if (sy < cy)
+ window_copy_redraw_lines(wme, sy, cy - sy + 1);
+ else
+ window_copy_redraw_lines(wme, cy, sy - cy + 1);
+ } else {
+ if (endsy < cy) {
+ window_copy_redraw_lines(wme, endsy,
+ cy - endsy + 1);
+ } else {
+ window_copy_redraw_lines(wme, cy,
+ endsy - cy + 1);
+ }
+ }
+ }
+
+ return (1);
+}
+
+static void *
+window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ char *buf;
+ size_t off;
+ u_int i, xx, yy, sx, sy, ex, ey, ey_last;
+ u_int firstsx, lastex, restex, restsx, selx;
+ int keys;
+
+ if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) {
+ buf = window_copy_match_at_cursor(data);
+ if (buf != NULL)
+ *len = strlen(buf);
+ else
+ *len = 0;
+ return (buf);
+ }
+
+ buf = xmalloc(1);
+ off = 0;
+
+ *buf = '\0';
+
+ /*
+ * The selection extends from selx,sely to (adjusted) cx,cy on
+ * the base screen.
+ */
+
+ /* Find start and end. */
+ xx = data->endselx;
+ yy = data->endsely;
+ if (yy < data->sely || (yy == data->sely && xx < data->selx)) {
+ sx = xx; sy = yy;
+ ex = data->selx; ey = data->sely;
+ } else {
+ sx = data->selx; sy = data->sely;
+ ex = xx; ey = yy;
+ }
+
+ /* Trim ex to end of line. */
+ ey_last = window_copy_find_length(wme, ey);
+ if (ex > ey_last)
+ ex = ey_last;
+
+ /*
+ * Deal with rectangle-copy if necessary; four situations: start of
+ * first line (firstsx), end of last line (lastex), start (restsx) and
+ * end (restex) of all other lines.
+ */
+ xx = screen_size_x(s);
+
+ /*
+ * Behave according to mode-keys. If it is emacs, copy like emacs,
+ * keeping the top-left-most character, and dropping the
+ * bottom-right-most, regardless of copy direction. If it is vi, also
+ * keep bottom-right-most character.
+ */
+ keys = options_get_number(wp->window->options, "mode-keys");
+ if (data->rectflag) {
+ /*
+ * Need to ignore the column with the cursor in it, which for
+ * rectangular copy means knowing which side the cursor is on.
+ */
+ if (data->cursordrag == CURSORDRAG_ENDSEL)
+ selx = data->selx;
+ else
+ selx = data->endselx;
+ if (selx < data->cx) {
+ /* Selection start is on the left. */
+ if (keys == MODEKEY_EMACS) {
+ lastex = data->cx;
+ restex = data->cx;
+ }
+ else {
+ lastex = data->cx + 1;
+ restex = data->cx + 1;
+ }
+ firstsx = selx;
+ restsx = selx;
+ } else {
+ /* Cursor is on the left. */
+ lastex = selx + 1;
+ restex = selx + 1;
+ firstsx = data->cx;
+ restsx = data->cx;
+ }
+ } else {
+ if (keys == MODEKEY_EMACS)
+ lastex = ex;
+ else
+ lastex = ex + 1;
+ restex = xx;
+ firstsx = sx;
+ restsx = 0;
+ }
+
+ /* Copy the lines. */
+ for (i = sy; i <= ey; i++) {
+ window_copy_copy_line(wme, &buf, &off, i,
+ (i == sy ? firstsx : restsx),
+ (i == ey ? lastex : restex));
+ }
+
+ /* Don't bother if no data. */
+ if (off == 0) {
+ free(buf);
+ *len = 0;
+ return (NULL);
+ }
+ /* Remove final \n (unless at end in vi mode). */
+ if (keys == MODEKEY_EMACS || lastex <= ey_last) {
+ if (~grid_get_line(data->backing->grid, ey)->flags &
+ GRID_LINE_WRAPPED || lastex != ey_last)
+ off -= 1;
+ }
+ *len = off;
+ return (buf);
+}
+
+static void
+window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix,
+ void *buf, size_t len)
+{
+ struct window_pane *wp = wme->wp;
+ struct screen_write_ctx ctx;
+
+ if (options_get_number(global_options, "set-clipboard") != 0) {
+ screen_write_start_pane(&ctx, wp, NULL);
+ screen_write_setselection(&ctx, buf, len);
+ screen_write_stop(&ctx);
+ notify_pane("pane-set-clipboard", wp);
+ }
+
+ paste_add(prefix, buf, len);
+}
+
+static void *
+window_copy_pipe_run(struct window_mode_entry *wme, struct session *s,
+ const char *cmd, size_t *len)
+{
+ void *buf;
+ struct job *job;
+
+ buf = window_copy_get_selection(wme, len);
+ if (cmd == NULL || *cmd == '\0')
+ cmd = options_get_string(global_options, "copy-command");
+ if (cmd != NULL && *cmd != '\0') {
+ job = job_run(cmd, 0, NULL, NULL, s, NULL, NULL, NULL, NULL,
+ NULL, JOB_NOWAIT, -1, -1);
+ bufferevent_write(job_get_event(job), buf, *len);
+ }
+ return (buf);
+}
+
+static void
+window_copy_pipe(struct window_mode_entry *wme, struct session *s,
+ const char *cmd)
+{
+ size_t len;
+
+ window_copy_pipe_run(wme, s, cmd, &len);
+}
+
+static void
+window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s,
+ const char *prefix, const char *cmd)
+{
+ void *buf;
+ size_t len;
+
+ buf = window_copy_pipe_run(wme, s, cmd, &len);
+ if (buf != NULL)
+ window_copy_copy_buffer(wme, prefix, buf, len);
+}
+
+static void
+window_copy_copy_selection(struct window_mode_entry *wme, const char *prefix)
+{
+ char *buf;
+ size_t len;
+
+ buf = window_copy_get_selection(wme, &len);
+ if (buf != NULL)
+ window_copy_copy_buffer(wme, prefix, buf, len);
+}
+
+static void
+window_copy_append_selection(struct window_mode_entry *wme)
+{
+ struct window_pane *wp = wme->wp;
+ char *buf;
+ struct paste_buffer *pb;
+ const char *bufdata, *bufname = NULL;
+ size_t len, bufsize;
+ struct screen_write_ctx ctx;
+
+ buf = window_copy_get_selection(wme, &len);
+ if (buf == NULL)
+ return;
+
+ if (options_get_number(global_options, "set-clipboard") != 0) {
+ screen_write_start_pane(&ctx, wp, NULL);
+ screen_write_setselection(&ctx, buf, len);
+ screen_write_stop(&ctx);
+ notify_pane("pane-set-clipboard", wp);
+ }
+
+ pb = paste_get_top(&bufname);
+ if (pb != NULL) {
+ bufdata = paste_buffer_data(pb, &bufsize);
+ buf = xrealloc(buf, len + bufsize);
+ memmove(buf + bufsize, buf, len);
+ memcpy(buf, bufdata, bufsize);
+ len += bufsize;
+ }
+ if (paste_set(buf, len, bufname, NULL) != 0)
+ free(buf);
+}
+
+static void
+window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off,
+ u_int sy, u_int sx, u_int ex)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct grid *gd = data->backing->grid;
+ struct grid_cell gc;
+ struct grid_line *gl;
+ struct utf8_data ud;
+ u_int i, xx, wrapped = 0;
+ const char *s;
+
+ if (sx > ex)
+ return;
+
+ /*
+ * Work out if the line was wrapped at the screen edge and all of it is
+ * on screen.
+ */
+ gl = grid_get_line(gd, sy);
+ if (gl->flags & GRID_LINE_WRAPPED && gl->cellsize <= gd->sx)
+ wrapped = 1;
+
+ /* If the line was wrapped, don't strip spaces (use the full length). */
+ if (wrapped)
+ xx = gl->cellsize;
+ else
+ xx = window_copy_find_length(wme, sy);
+ if (ex > xx)
+ ex = xx;
+ if (sx > xx)
+ sx = xx;
+
+ if (sx < ex) {
+ for (i = sx; i < ex; i++) {
+ grid_get_cell(gd, i, sy, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ continue;
+ utf8_copy(&ud, &gc.data);
+ if (ud.size == 1 && (gc.attr & GRID_ATTR_CHARSET)) {
+ s = tty_acs_get(NULL, ud.data[0]);
+ if (s != NULL && strlen(s) <= sizeof ud.data) {
+ ud.size = strlen(s);
+ memcpy(ud.data, s, ud.size);
+ }
+ }
+
+ *buf = xrealloc(*buf, (*off) + ud.size);
+ memcpy(*buf + *off, ud.data, ud.size);
+ *off += ud.size;
+ }
+ }
+
+ /* Only add a newline if the line wasn't wrapped. */
+ if (!wrapped || ex != xx) {
+ *buf = xrealloc(*buf, (*off) + 1);
+ (*buf)[(*off)++] = '\n';
+ }
+}
+
+static void
+window_copy_clear_selection(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ u_int px, py;
+
+ screen_clear_selection(&data->screen);
+
+ data->cursordrag = CURSORDRAG_NONE;
+ data->lineflag = LINE_SEL_NONE;
+ data->selflag = SEL_CHAR;
+
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ px = window_copy_find_length(wme, py);
+ if (data->cx > px)
+ window_copy_update_cursor(wme, px, data->cy);
+}
+
+static int
+window_copy_in_set(struct window_mode_entry *wme, u_int px, u_int py,
+ const char *set)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct grid_cell gc;
+
+ grid_get_cell(data->backing->grid, px, py, &gc);
+ if (gc.flags & GRID_FLAG_PADDING)
+ return (0);
+ return (utf8_cstrhas(set, &gc.data));
+}
+
+static u_int
+window_copy_find_length(struct window_mode_entry *wme, u_int py)
+{
+ struct window_copy_mode_data *data = wme->data;
+
+ return (grid_line_length(data->backing->grid, py));
+}
+
+static void
+window_copy_cursor_start_of_line(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_start_of_line(&gr, 1);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
+}
+
+static void
+window_copy_cursor_back_to_indentation(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_back_to_indentation(&gr);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
+}
+
+static void
+window_copy_cursor_end_of_line(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ if (data->screen.sel != NULL && data->rectflag)
+ grid_reader_cursor_end_of_line(&gr, 1, 1);
+ else
+ grid_reader_cursor_end_of_line(&gr, 1, 0);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
+ data->oy, oldy, px, py, 0);
+}
+
+static void
+window_copy_other_end(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int selx, sely, cy, yy, hsize;
+
+ if (s->sel == NULL && data->lineflag == LINE_SEL_NONE)
+ return;
+
+ if (data->lineflag == LINE_SEL_LEFT_RIGHT)
+ data->lineflag = LINE_SEL_RIGHT_LEFT;
+ else if (data->lineflag == LINE_SEL_RIGHT_LEFT)
+ data->lineflag = LINE_SEL_LEFT_RIGHT;
+
+ switch (data->cursordrag) {
+ case CURSORDRAG_NONE:
+ case CURSORDRAG_SEL:
+ data->cursordrag = CURSORDRAG_ENDSEL;
+ break;
+ case CURSORDRAG_ENDSEL:
+ data->cursordrag = CURSORDRAG_SEL;
+ break;
+ }
+
+ selx = data->endselx;
+ sely = data->endsely;
+ if (data->cursordrag == CURSORDRAG_SEL) {
+ selx = data->selx;
+ sely = data->sely;
+ }
+
+ cy = data->cy;
+ yy = screen_hsize(data->backing) + data->cy - data->oy;
+
+ data->cx = selx;
+
+ hsize = screen_hsize(data->backing);
+ if (sely < hsize - data->oy) { /* above */
+ data->oy = hsize - sely;
+ data->cy = 0;
+ } else if (sely > hsize - data->oy + screen_size_y(s)) { /* below */
+ data->oy = hsize - sely + screen_size_y(s) - 1;
+ data->cy = screen_size_y(s) - 1;
+ } else
+ data->cy = cy + sely - yy;
+
+ window_copy_update_selection(wme, 1, 1);
+ window_copy_redraw_screen(wme);
+}
+
+static void
+window_copy_cursor_left(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_left(&gr, 1);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
+}
+
+static void
+window_copy_cursor_right(struct window_mode_entry *wme, int all)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_right(&gr, 1, all);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
+ data->oy, oldy, px, py, 0);
+}
+
+static void
+window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int ox, oy, px, py;
+ int norectsel;
+
+ norectsel = data->screen.sel == NULL || !data->rectflag;
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+ ox = window_copy_find_length(wme, oy);
+ if (norectsel && data->cx != ox) {
+ data->lastcx = data->cx;
+ data->lastsx = ox;
+ }
+
+ if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely)
+ window_copy_other_end(wme);
+
+ if (scroll_only || data->cy == 0) {
+ if (norectsel)
+ data->cx = data->lastcx;
+ window_copy_scroll_down(wme, 1);
+ if (scroll_only) {
+ if (data->cy == screen_size_y(s) - 1)
+ window_copy_redraw_lines(wme, data->cy, 1);
+ else
+ window_copy_redraw_lines(wme, data->cy, 2);
+ }
+ } else {
+ if (norectsel) {
+ window_copy_update_cursor(wme, data->lastcx,
+ data->cy - 1);
+ } else
+ window_copy_update_cursor(wme, data->cx, data->cy - 1);
+ if (window_copy_update_selection(wme, 1, 0)) {
+ if (data->cy == screen_size_y(s) - 1)
+ window_copy_redraw_lines(wme, data->cy, 1);
+ else
+ window_copy_redraw_lines(wme, data->cy, 2);
+ }
+ }
+
+ if (norectsel) {
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ px = window_copy_find_length(wme, py);
+ if ((data->cx >= data->lastsx && data->cx != px) ||
+ data->cx > px)
+ {
+ window_copy_update_cursor(wme, px, data->cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+ }
+
+ if (data->lineflag == LINE_SEL_LEFT_RIGHT)
+ {
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ if (data->rectflag)
+ px = screen_size_x(data->backing);
+ else
+ px = window_copy_find_length(wme, py);
+ window_copy_update_cursor(wme, px, data->cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+ else if (data->lineflag == LINE_SEL_RIGHT_LEFT)
+ {
+ window_copy_update_cursor(wme, 0, data->cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+}
+
+static void
+window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ u_int ox, oy, px, py;
+ int norectsel;
+
+ norectsel = data->screen.sel == NULL || !data->rectflag;
+ oy = screen_hsize(data->backing) + data->cy - data->oy;
+ ox = window_copy_find_length(wme, oy);
+ if (norectsel && data->cx != ox) {
+ data->lastcx = data->cx;
+ data->lastsx = ox;
+ }
+
+ if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely)
+ window_copy_other_end(wme);
+
+ if (scroll_only || data->cy == screen_size_y(s) - 1) {
+ if (norectsel)
+ data->cx = data->lastcx;
+ window_copy_scroll_up(wme, 1);
+ if (scroll_only && data->cy > 0)
+ window_copy_redraw_lines(wme, data->cy - 1, 2);
+ } else {
+ if (norectsel) {
+ window_copy_update_cursor(wme, data->lastcx,
+ data->cy + 1);
+ } else
+ window_copy_update_cursor(wme, data->cx, data->cy + 1);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy - 1, 2);
+ }
+
+ if (norectsel) {
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ px = window_copy_find_length(wme, py);
+ if ((data->cx >= data->lastsx && data->cx != px) ||
+ data->cx > px)
+ {
+ window_copy_update_cursor(wme, px, data->cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+ }
+
+ if (data->lineflag == LINE_SEL_LEFT_RIGHT)
+ {
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ if (data->rectflag)
+ px = screen_size_x(data->backing);
+ else
+ px = window_copy_find_length(wme, py);
+ window_copy_update_cursor(wme, px, data->cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+ else if (data->lineflag == LINE_SEL_RIGHT_LEFT)
+ {
+ window_copy_update_cursor(wme, 0, data->cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, data->cy, 1);
+ }
+}
+
+static void
+window_copy_cursor_jump(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx + 1;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ if (grid_reader_cursor_jump(&gr, data->jumpchar)) {
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_down(wme, hsize,
+ screen_size_y(back_s), data->oy, oldy, px, py, 0);
+ }
+}
+
+static void
+window_copy_cursor_jump_back(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_left(&gr, 0);
+ if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) {
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px,
+ py);
+ }
+}
+
+static void
+window_copy_cursor_jump_to(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx + 2;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ if (grid_reader_cursor_jump(&gr, data->jumpchar)) {
+ grid_reader_cursor_left(&gr, 1);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_down(wme, hsize,
+ screen_size_y(back_s), data->oy, oldy, px, py, 0);
+ }
+}
+
+static void
+window_copy_cursor_jump_to_back(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_left(&gr, 0);
+ grid_reader_cursor_left(&gr, 0);
+ if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) {
+ grid_reader_cursor_right(&gr, 1, 0);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px,
+ py);
+ }
+}
+
+static void
+window_copy_cursor_next_word(struct window_mode_entry *wme,
+ const char *separators)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_next_word(&gr, separators);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
+ data->oy, oldy, px, py, 0);
+}
+
+/* Compute the next place where a word ends. */
+static void
+window_copy_cursor_next_word_end_pos(struct window_mode_entry *wme,
+ const char *separators, u_int *ppx, u_int *ppy)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct options *oo = wp->window->options;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ if (options_get_number(oo, "mode-keys") == MODEKEY_VI) {
+ if (!grid_reader_in_set(&gr, WHITESPACE))
+ grid_reader_cursor_right(&gr, 0, 0);
+ grid_reader_cursor_next_word_end(&gr, separators);
+ grid_reader_cursor_left(&gr, 1);
+ } else
+ grid_reader_cursor_next_word_end(&gr, separators);
+ grid_reader_get_cursor(&gr, &px, &py);
+ *ppx = px;
+ *ppy = py;
+}
+
+/* Move to the next place where a word ends. */
+static void
+window_copy_cursor_next_word_end(struct window_mode_entry *wme,
+ const char *separators, int no_reset)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct options *oo = wp->window->options;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ if (options_get_number(oo, "mode-keys") == MODEKEY_VI) {
+ if (!grid_reader_in_set(&gr, WHITESPACE))
+ grid_reader_cursor_right(&gr, 0, 0);
+ grid_reader_cursor_next_word_end(&gr, separators);
+ grid_reader_cursor_left(&gr, 1);
+ } else
+ grid_reader_cursor_next_word_end(&gr, separators);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
+ data->oy, oldy, px, py, no_reset);
+}
+
+/* Compute the previous place where a word begins. */
+static void
+window_copy_cursor_previous_word_pos(struct window_mode_entry *wme,
+ const char *separators, u_int *ppx, u_int *ppy)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, hsize;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_previous_word(&gr, separators, /* already= */ 0,
+ /* stop_at_eol= */ 1);
+ grid_reader_get_cursor(&gr, &px, &py);
+ *ppx = px;
+ *ppy = py;
+}
+
+/* Move to the previous place where a word begins. */
+static void
+window_copy_cursor_previous_word(struct window_mode_entry *wme,
+ const char *separators, int already)
+{
+ struct window_copy_mode_data *data = wme->data;
+ struct window *w = wme->wp->window;
+ struct screen *back_s = data->backing;
+ struct grid_reader gr;
+ u_int px, py, oldy, hsize;
+ int stop_at_eol;
+
+ if (options_get_number(w->options, "mode-keys") == MODEKEY_EMACS)
+ stop_at_eol = 1;
+ else
+ stop_at_eol = 0;
+
+ px = data->cx;
+ hsize = screen_hsize(back_s);
+ py = hsize + data->cy - data->oy;
+ oldy = data->cy;
+
+ grid_reader_start(&gr, back_s->grid, px, py);
+ grid_reader_cursor_previous_word(&gr, separators, already, stop_at_eol);
+ grid_reader_get_cursor(&gr, &px, &py);
+ window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
+}
+
+static void
+window_copy_scroll_up(struct window_mode_entry *wme, u_int ny)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct screen_write_ctx ctx;
+
+ if (data->oy < ny)
+ ny = data->oy;
+ if (ny == 0)
+ return;
+ data->oy -= ny;
+
+ if (data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 0, 0);
+
+ screen_write_start_pane(&ctx, wp, NULL);
+ screen_write_cursormove(&ctx, 0, 0, 0);
+ screen_write_deleteline(&ctx, ny, 8);
+ window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny);
+ window_copy_write_line(wme, &ctx, 0);
+ if (screen_size_y(s) > 1)
+ window_copy_write_line(wme, &ctx, 1);
+ if (screen_size_y(s) > 3)
+ window_copy_write_line(wme, &ctx, screen_size_y(s) - 2);
+ if (s->sel != NULL && screen_size_y(s) > ny)
+ window_copy_write_line(wme, &ctx, screen_size_y(s) - ny - 1);
+ screen_write_cursormove(&ctx, data->cx, data->cy, 0);
+ screen_write_stop(&ctx);
+}
+
+static void
+window_copy_scroll_down(struct window_mode_entry *wme, u_int ny)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_copy_mode_data *data = wme->data;
+ struct screen *s = &data->screen;
+ struct screen_write_ctx ctx;
+
+ if (ny > screen_hsize(data->backing))
+ return;
+
+ if (data->oy > screen_hsize(data->backing) - ny)
+ ny = screen_hsize(data->backing) - data->oy;
+ if (ny == 0)
+ return;
+ data->oy += ny;
+
+ if (data->searchmark != NULL && !data->timeout)
+ window_copy_search_marks(wme, NULL, data->searchregex, 1);
+ window_copy_update_selection(wme, 0, 0);
+
+ screen_write_start_pane(&ctx, wp, NULL);
+ screen_write_cursormove(&ctx, 0, 0, 0);
+ screen_write_insertline(&ctx, ny, 8);
+ window_copy_write_lines(wme, &ctx, 0, ny);
+ if (s->sel != NULL && screen_size_y(s) > ny)
+ window_copy_write_line(wme, &ctx, ny);
+ else if (ny == 1) /* nuke position */
+ window_copy_write_line(wme, &ctx, 1);
+ screen_write_cursormove(&ctx, data->cx, data->cy, 0);
+ screen_write_stop(&ctx);
+}
+
+static void
+window_copy_rectangle_set(struct window_mode_entry *wme, int rectflag)
+{
+ struct window_copy_mode_data *data = wme->data;
+ u_int px, py;
+
+ data->rectflag = rectflag;
+
+ py = screen_hsize(data->backing) + data->cy - data->oy;
+ px = window_copy_find_length(wme, py);
+ if (data->cx > px)
+ window_copy_update_cursor(wme, px, data->cy);
+
+ window_copy_update_selection(wme, 1, 0);
+ window_copy_redraw_screen(wme);
+}
+
+static void
+window_copy_move_mouse(struct mouse_event *m)
+{
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+ u_int x, y;
+
+ wp = cmd_mouse_pane(m, NULL, NULL);
+ if (wp == NULL)
+ return;
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL)
+ return;
+ if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
+ return;
+
+ if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
+ return;
+
+ window_copy_update_cursor(wme, x, y);
+}
+
+void
+window_copy_start_drag(struct client *c, struct mouse_event *m)
+{
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+ struct window_copy_mode_data *data;
+ u_int x, y, yg;
+
+ if (c == NULL)
+ return;
+
+ wp = cmd_mouse_pane(m, NULL, NULL);
+ if (wp == NULL)
+ return;
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL)
+ return;
+ if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
+ return;
+
+ if (cmd_mouse_at(wp, m, &x, &y, 1) != 0)
+ return;
+
+ c->tty.mouse_drag_update = window_copy_drag_update;
+ c->tty.mouse_drag_release = window_copy_drag_release;
+
+ data = wme->data;
+ yg = screen_hsize(data->backing) + y - data->oy;
+ if (x < data->selrx || x > data->endselrx || yg != data->selry)
+ data->selflag = SEL_CHAR;
+ switch (data->selflag) {
+ case SEL_WORD:
+ if (data->separators != NULL) {
+ window_copy_update_cursor(wme, x, y);
+ window_copy_cursor_previous_word_pos(wme,
+ data->separators, &x, &y);
+ y -= screen_hsize(data->backing) - data->oy;
+ }
+ window_copy_update_cursor(wme, x, y);
+ break;
+ case SEL_LINE:
+ window_copy_update_cursor(wme, 0, y);
+ break;
+ case SEL_CHAR:
+ window_copy_update_cursor(wme, x, y);
+ window_copy_start_selection(wme);
+ break;
+ }
+
+ window_copy_redraw_screen(wme);
+ window_copy_drag_update(c, m);
+}
+
+static void
+window_copy_drag_update(struct client *c, struct mouse_event *m)
+{
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+ struct window_copy_mode_data *data;
+ u_int x, y, old_cx, old_cy;
+ struct timeval tv = {
+ .tv_usec = WINDOW_COPY_DRAG_REPEAT_TIME
+ };
+
+ if (c == NULL)
+ return;
+
+ wp = cmd_mouse_pane(m, NULL, NULL);
+ if (wp == NULL)
+ return;
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL)
+ return;
+ if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
+ return;
+
+ data = wme->data;
+ evtimer_del(&data->dragtimer);
+
+ if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
+ return;
+ old_cx = data->cx;
+ old_cy = data->cy;
+
+ window_copy_update_cursor(wme, x, y);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_selection(wme, old_cy);
+ if (old_cy != data->cy || old_cx == data->cx) {
+ if (y == 0) {
+ evtimer_add(&data->dragtimer, &tv);
+ window_copy_cursor_up(wme, 1);
+ } else if (y == screen_size_y(&data->screen) - 1) {
+ evtimer_add(&data->dragtimer, &tv);
+ window_copy_cursor_down(wme, 1);
+ }
+ }
+}
+
+static void
+window_copy_drag_release(struct client *c, struct mouse_event *m)
+{
+ struct window_pane *wp;
+ struct window_mode_entry *wme;
+ struct window_copy_mode_data *data;
+
+ if (c == NULL)
+ return;
+
+ wp = cmd_mouse_pane(m, NULL, NULL);
+ if (wp == NULL)
+ return;
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL)
+ return;
+ if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
+ return;
+
+ data = wme->data;
+ evtimer_del(&data->dragtimer);
+}
+
+static void
+window_copy_jump_to_mark(struct window_mode_entry *wme)
+{
+ struct window_copy_mode_data *data = wme->data;
+ u_int tmx, tmy;
+
+ tmx = data->cx;
+ tmy = screen_hsize(data->backing) + data->cy - data->oy;
+ data->cx = data->mx;
+ if (data->my < screen_hsize(data->backing)) {
+ data->cy = 0;
+ data->oy = screen_hsize(data->backing) - data->my;
+ } else {
+ data->cy = data->my - screen_hsize(data->backing);
+ data->oy = 0;
+ }
+ data->mx = tmx;
+ data->my = tmy;
+ data->showmark = 1;
+ window_copy_update_selection(wme, 0, 0);
+ window_copy_redraw_screen(wme);
+}
+
+/* Scroll up if the cursor went off the visible screen. */
+static void
+window_copy_acquire_cursor_up(struct window_mode_entry *wme, u_int hsize,
+ u_int oy, u_int oldy, u_int px, u_int py)
+{
+ u_int cy, yy, ny, nd;
+
+ yy = hsize - oy;
+ if (py < yy) {
+ ny = yy - py;
+ cy = 0;
+ nd = 1;
+ } else {
+ ny = 0;
+ cy = py - yy;
+ nd = oldy - cy + 1;
+ }
+ while (ny > 0) {
+ window_copy_cursor_up(wme, 1);
+ ny--;
+ }
+ window_copy_update_cursor(wme, px, cy);
+ if (window_copy_update_selection(wme, 1, 0))
+ window_copy_redraw_lines(wme, cy, nd);
+}
+
+/* Scroll down if the cursor went off the visible screen. */
+static void
+window_copy_acquire_cursor_down(struct window_mode_entry *wme, u_int hsize,
+ u_int sy, u_int oy, u_int oldy, u_int px, u_int py, int no_reset)
+{
+ u_int cy, yy, ny, nd;
+
+ cy = py - hsize + oy;
+ yy = sy - 1;
+ if (cy > yy) {
+ ny = cy - yy;
+ oldy = yy;
+ nd = 1;
+ } else {
+ ny = 0;
+ nd = cy - oldy + 1;
+ }
+ while (ny > 0) {
+ window_copy_cursor_down(wme, 1);
+ ny--;
+ }
+ if (cy > yy)
+ window_copy_update_cursor(wme, px, yy);
+ else
+ window_copy_update_cursor(wme, px, cy);
+ if (window_copy_update_selection(wme, 1, no_reset))
+ window_copy_redraw_lines(wme, oldy, nd);
+}
diff --git a/window-customize.c b/window-customize.c
new file mode 100644
index 0000000..4a16e90
--- /dev/null
+++ b/window-customize.c
@@ -0,0 +1,1512 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static struct screen *window_customize_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static void window_customize_free(struct window_mode_entry *);
+static void window_customize_resize(struct window_mode_entry *,
+ u_int, u_int);
+static void window_customize_key(struct window_mode_entry *,
+ struct client *, struct session *,
+ struct winlink *, key_code, struct mouse_event *);
+
+#define WINDOW_CUSTOMIZE_DEFAULT_FORMAT \
+ "#{?is_option," \
+ "#{?option_is_global,,#[reverse](#{option_scope})#[default] }" \
+ "#[ignore]" \
+ "#{option_value}#{?option_unit, #{option_unit},}" \
+ "," \
+ "#{key}" \
+ "}"
+
+static const struct menu_item window_customize_menu_items[] = {
+ { "Select", '\r', NULL },
+ { "Expand", KEYC_RIGHT, NULL },
+ { "", KEYC_NONE, NULL },
+ { "Tag", 't', NULL },
+ { "Tag All", '\024', NULL },
+ { "Tag None", 'T', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Cancel", 'q', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+const struct window_mode window_customize_mode = {
+ .name = "options-mode",
+ .default_format = WINDOW_CUSTOMIZE_DEFAULT_FORMAT,
+
+ .init = window_customize_init,
+ .free = window_customize_free,
+ .resize = window_customize_resize,
+ .key = window_customize_key,
+};
+
+enum window_customize_scope {
+ WINDOW_CUSTOMIZE_NONE,
+ WINDOW_CUSTOMIZE_KEY,
+ WINDOW_CUSTOMIZE_SERVER,
+ WINDOW_CUSTOMIZE_GLOBAL_SESSION,
+ WINDOW_CUSTOMIZE_SESSION,
+ WINDOW_CUSTOMIZE_GLOBAL_WINDOW,
+ WINDOW_CUSTOMIZE_WINDOW,
+ WINDOW_CUSTOMIZE_PANE
+};
+
+enum window_customize_change {
+ WINDOW_CUSTOMIZE_UNSET,
+ WINDOW_CUSTOMIZE_RESET,
+};
+
+struct window_customize_itemdata {
+ struct window_customize_modedata *data;
+ enum window_customize_scope scope;
+
+ char *table;
+ key_code key;
+
+ struct options *oo;
+ char *name;
+ int idx;
+};
+
+struct window_customize_modedata {
+ struct window_pane *wp;
+ int dead;
+ int references;
+
+ struct mode_tree_data *data;
+ char *format;
+ int hide_global;
+
+ struct window_customize_itemdata **item_list;
+ u_int item_size;
+
+ struct cmd_find_state fs;
+ enum window_customize_change change;
+};
+
+static uint64_t
+window_customize_get_tag(struct options_entry *o, int idx,
+ const struct options_table_entry *oe)
+{
+ uint64_t offset;
+
+ if (oe == NULL)
+ return ((uint64_t)o);
+ offset = ((char *)oe - (char *)options_table) / sizeof *options_table;
+ return ((2ULL << 62)|(offset << 32)|((idx + 1) << 1)|1);
+}
+
+static struct options *
+window_customize_get_tree(enum window_customize_scope scope,
+ struct cmd_find_state *fs)
+{
+ switch (scope) {
+ case WINDOW_CUSTOMIZE_NONE:
+ case WINDOW_CUSTOMIZE_KEY:
+ return (NULL);
+ case WINDOW_CUSTOMIZE_SERVER:
+ return (global_options);
+ case WINDOW_CUSTOMIZE_GLOBAL_SESSION:
+ return (global_s_options);
+ case WINDOW_CUSTOMIZE_SESSION:
+ return (fs->s->options);
+ case WINDOW_CUSTOMIZE_GLOBAL_WINDOW:
+ return (global_w_options);
+ case WINDOW_CUSTOMIZE_WINDOW:
+ return (fs->w->options);
+ case WINDOW_CUSTOMIZE_PANE:
+ return (fs->wp->options);
+ }
+ return (NULL);
+}
+
+static int
+window_customize_check_item(struct window_customize_modedata *data,
+ struct window_customize_itemdata *item, struct cmd_find_state *fsp)
+{
+ struct cmd_find_state fs;
+
+ if (fsp == NULL)
+ fsp = &fs;
+
+ if (cmd_find_valid_state(&data->fs))
+ cmd_find_copy_state(fsp, &data->fs);
+ else
+ cmd_find_from_pane(fsp, data->wp, 0);
+ return (item->oo == window_customize_get_tree(item->scope, fsp));
+}
+
+static int
+window_customize_get_key(struct window_customize_itemdata *item,
+ struct key_table **ktp, struct key_binding **bdp)
+{
+ struct key_table *kt;
+ struct key_binding *bd;
+
+ kt = key_bindings_get_table(item->table, 0);
+ if (kt == NULL)
+ return (0);
+ bd = key_bindings_get(kt, item->key);
+ if (bd == NULL)
+ return (0);
+
+ if (ktp != NULL)
+ *ktp = kt;
+ if (bdp != NULL)
+ *bdp = bd;
+ return (1);
+}
+
+static char *
+window_customize_scope_text(enum window_customize_scope scope,
+ struct cmd_find_state *fs)
+{
+ char *s;
+ u_int idx;
+
+ switch (scope) {
+ case WINDOW_CUSTOMIZE_PANE:
+ window_pane_index(fs->wp, &idx);
+ xasprintf(&s, "pane %u", idx);
+ break;
+ case WINDOW_CUSTOMIZE_SESSION:
+ xasprintf(&s, "session %s", fs->s->name);
+ break;
+ case WINDOW_CUSTOMIZE_WINDOW:
+ xasprintf(&s, "window %u", fs->wl->idx);
+ break;
+ default:
+ s = xstrdup("");
+ break;
+ }
+ return (s);
+}
+
+static struct window_customize_itemdata *
+window_customize_add_item(struct window_customize_modedata *data)
+{
+ struct window_customize_itemdata *item;
+
+ data->item_list = xreallocarray(data->item_list, data->item_size + 1,
+ sizeof *data->item_list);
+ item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
+ return (item);
+}
+
+static void
+window_customize_free_item(struct window_customize_itemdata *item)
+{
+ free(item->table);
+ free(item->name);
+ free(item);
+}
+
+static void
+window_customize_build_array(struct window_customize_modedata *data,
+ struct mode_tree_item *top, enum window_customize_scope scope,
+ struct options_entry *o, struct format_tree *ft)
+{
+ const struct options_table_entry *oe = options_table_entry(o);
+ struct options *oo = options_owner(o);
+ struct window_customize_itemdata *item;
+ struct options_array_item *ai;
+ char *name, *value, *text;
+ u_int idx;
+ uint64_t tag;
+
+ ai = options_array_first(o);
+ while (ai != NULL) {
+ idx = options_array_item_index(ai);
+
+ xasprintf(&name, "%s[%u]", options_name(o), idx);
+ format_add(ft, "option_name", "%s", name);
+ value = options_to_string(o, idx, 0);
+ format_add(ft, "option_value", "%s", value);
+
+ item = window_customize_add_item(data);
+ item->scope = scope;
+ item->oo = oo;
+ item->name = xstrdup(options_name(o));
+ item->idx = idx;
+
+ text = format_expand(ft, data->format);
+ tag = window_customize_get_tag(o, idx, oe);
+ mode_tree_add(data->data, top, item, tag, name, text, -1);
+ free(text);
+
+ free(name);
+ free(value);
+
+ ai = options_array_next(ai);
+ }
+}
+
+static void
+window_customize_build_option(struct window_customize_modedata *data,
+ struct mode_tree_item *top, enum window_customize_scope scope,
+ struct options_entry *o, struct format_tree *ft,
+ const char *filter, struct cmd_find_state *fs)
+{
+ const struct options_table_entry *oe = options_table_entry(o);
+ struct options *oo = options_owner(o);
+ const char *name = options_name(o);
+ struct window_customize_itemdata *item;
+ char *text, *expanded, *value;
+ int global = 0, array = 0;
+ uint64_t tag;
+
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK))
+ return;
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY))
+ array = 1;
+
+ if (scope == WINDOW_CUSTOMIZE_SERVER ||
+ scope == WINDOW_CUSTOMIZE_GLOBAL_SESSION ||
+ scope == WINDOW_CUSTOMIZE_GLOBAL_WINDOW)
+ global = 1;
+ if (data->hide_global && global)
+ return;
+
+ format_add(ft, "option_name", "%s", name);
+ format_add(ft, "option_is_global", "%d", global);
+ format_add(ft, "option_is_array", "%d", array);
+
+ text = window_customize_scope_text(scope, fs);
+ format_add(ft, "option_scope", "%s", text);
+ free(text);
+
+ if (oe != NULL && oe->unit != NULL)
+ format_add(ft, "option_unit", "%s", oe->unit);
+ else
+ format_add(ft, "option_unit", "%s", "");
+
+ if (!array) {
+ value = options_to_string(o, -1, 0);
+ format_add(ft, "option_value", "%s", value);
+ free(value);
+ }
+
+ if (filter != NULL) {
+ expanded = format_expand(ft, filter);
+ if (!format_true(expanded)) {
+ free(expanded);
+ return;
+ }
+ free(expanded);
+ }
+ item = window_customize_add_item(data);
+ item->oo = oo;
+ item->scope = scope;
+ item->name = xstrdup(name);
+ item->idx = -1;
+
+ if (array)
+ text = NULL;
+ else
+ text = format_expand(ft, data->format);
+ tag = window_customize_get_tag(o, -1, oe);
+ top = mode_tree_add(data->data, top, item, tag, name, text, 0);
+ free(text);
+
+ if (array)
+ window_customize_build_array(data, top, scope, o, ft);
+}
+
+static void
+window_customize_find_user_options(struct options *oo, const char ***list,
+ u_int *size)
+{
+ struct options_entry *o;
+ const char *name;
+ u_int i;
+
+ o = options_first(oo);
+ while (o != NULL) {
+ name = options_name(o);
+ if (*name != '@') {
+ o = options_next(o);
+ continue;
+ }
+ for (i = 0; i < *size; i++) {
+ if (strcmp((*list)[i], name) == 0)
+ break;
+ }
+ if (i != *size) {
+ o = options_next(o);
+ continue;
+ }
+ *list = xreallocarray(*list, (*size) + 1, sizeof **list);
+ (*list)[(*size)++] = name;
+
+ o = options_next(o);
+ }
+}
+
+static void
+window_customize_build_options(struct window_customize_modedata *data,
+ const char *title, uint64_t tag,
+ enum window_customize_scope scope0, struct options *oo0,
+ enum window_customize_scope scope1, struct options *oo1,
+ enum window_customize_scope scope2, struct options *oo2,
+ struct format_tree *ft, const char *filter, struct cmd_find_state *fs)
+{
+ struct mode_tree_item *top;
+ struct options_entry *o = NULL, *loop;
+ const char **list = NULL, *name;
+ u_int size = 0, i;
+ enum window_customize_scope scope;
+
+ top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0);
+ mode_tree_no_tag(top);
+
+ /*
+ * We get the options from the first tree, but build it using the
+ * values from the other two. Any tree can have user options so we need
+ * to build a separate list of them.
+ */
+
+ window_customize_find_user_options(oo0, &list, &size);
+ if (oo1 != NULL)
+ window_customize_find_user_options(oo1, &list, &size);
+ if (oo2 != NULL)
+ window_customize_find_user_options(oo2, &list, &size);
+
+ for (i = 0; i < size; i++) {
+ if (oo2 != NULL)
+ o = options_get(oo2, list[i]);
+ if (o == NULL && oo1 != NULL)
+ o = options_get(oo1, list[i]);
+ if (o == NULL)
+ o = options_get(oo0, list[i]);
+ if (options_owner(o) == oo2)
+ scope = scope2;
+ else if (options_owner(o) == oo1)
+ scope = scope1;
+ else
+ scope = scope0;
+ window_customize_build_option(data, top, scope, o, ft, filter,
+ fs);
+ }
+ free(list);
+
+ loop = options_first(oo0);
+ while (loop != NULL) {
+ name = options_name(loop);
+ if (*name == '@') {
+ loop = options_next(loop);
+ continue;
+ }
+ if (oo2 != NULL)
+ o = options_get(oo2, name);
+ else if (oo1 != NULL)
+ o = options_get(oo1, name);
+ else
+ o = loop;
+ if (options_owner(o) == oo2)
+ scope = scope2;
+ else if (options_owner(o) == oo1)
+ scope = scope1;
+ else
+ scope = scope0;
+ window_customize_build_option(data, top, scope, o, ft, filter,
+ fs);
+ loop = options_next(loop);
+ }
+}
+
+static void
+window_customize_build_keys(struct window_customize_modedata *data,
+ struct key_table *kt, struct format_tree *ft, const char *filter,
+ struct cmd_find_state *fs, u_int number)
+{
+ struct mode_tree_item *top, *child, *mti;
+ struct window_customize_itemdata *item;
+ struct key_binding *bd;
+ char *title, *text, *tmp, *expanded;
+ const char *flag;
+ uint64_t tag;
+
+ tag = (1ULL << 62)|((uint64_t)number << 54)|1;
+
+ xasprintf(&title, "Key Table - %s", kt->name);
+ top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0);
+ mode_tree_no_tag(top);
+ free(title);
+
+ ft = format_create_from_state(NULL, NULL, fs);
+ format_add(ft, "is_option", "0");
+ format_add(ft, "is_key", "1");
+
+ bd = key_bindings_first(kt);
+ while (bd != NULL) {
+ format_add(ft, "key", "%s", key_string_lookup_key(bd->key, 0));
+ if (bd->note != NULL)
+ format_add(ft, "key_note", "%s", bd->note);
+ if (filter != NULL) {
+ expanded = format_expand(ft, filter);
+ if (!format_true(expanded)) {
+ free(expanded);
+ continue;
+ }
+ free(expanded);
+ }
+
+ item = window_customize_add_item(data);
+ item->scope = WINDOW_CUSTOMIZE_KEY;
+ item->table = xstrdup(kt->name);
+ item->key = bd->key;
+ item->name = xstrdup(key_string_lookup_key(item->key, 0));
+ item->idx = -1;
+
+ expanded = format_expand(ft, data->format);
+ child = mode_tree_add(data->data, top, item, (uint64_t)bd,
+ expanded, NULL, 0);
+ free(expanded);
+
+ tmp = cmd_list_print(bd->cmdlist, 0);
+ xasprintf(&text, "#[ignore]%s", tmp);
+ free(tmp);
+ mti = mode_tree_add(data->data, child, item,
+ tag|(bd->key << 3)|(0 << 1)|1, "Command", text, -1);
+ mode_tree_draw_as_parent(mti);
+ mode_tree_no_tag(mti);
+ free(text);
+
+ if (bd->note != NULL)
+ xasprintf(&text, "#[ignore]%s", bd->note);
+ else
+ text = xstrdup("");
+ mti = mode_tree_add(data->data, child, item,
+ tag|(bd->key << 3)|(1 << 1)|1, "Note", text, -1);
+ mode_tree_draw_as_parent(mti);
+ mode_tree_no_tag(mti);
+ free(text);
+
+ if (bd->flags & KEY_BINDING_REPEAT)
+ flag = "on";
+ else
+ flag = "off";
+ mti = mode_tree_add(data->data, child, item,
+ tag|(bd->key << 3)|(2 << 1)|1, "Repeat", flag, -1);
+ mode_tree_draw_as_parent(mti);
+ mode_tree_no_tag(mti);
+
+ bd = key_bindings_next(kt, bd);
+ }
+
+ format_free(ft);
+}
+
+static void
+window_customize_build(void *modedata,
+ __unused struct mode_tree_sort_criteria *sort_crit, __unused uint64_t *tag,
+ const char *filter)
+{
+ struct window_customize_modedata *data = modedata;
+ struct cmd_find_state fs;
+ struct format_tree *ft;
+ u_int i;
+ struct key_table *kt;
+
+ for (i = 0; i < data->item_size; i++)
+ window_customize_free_item(data->item_list[i]);
+ free(data->item_list);
+ data->item_list = NULL;
+ data->item_size = 0;
+
+ if (cmd_find_valid_state(&data->fs))
+ cmd_find_copy_state(&fs, &data->fs);
+ else
+ cmd_find_from_pane(&fs, data->wp, 0);
+
+ ft = format_create_from_state(NULL, NULL, &fs);
+ format_add(ft, "is_option", "1");
+ format_add(ft, "is_key", "0");
+
+ window_customize_build_options(data, "Server Options",
+ (3ULL << 62)|(OPTIONS_TABLE_SERVER << 1)|1,
+ WINDOW_CUSTOMIZE_SERVER, global_options,
+ WINDOW_CUSTOMIZE_NONE, NULL,
+ WINDOW_CUSTOMIZE_NONE, NULL,
+ ft, filter, &fs);
+ window_customize_build_options(data, "Session Options",
+ (3ULL << 62)|(OPTIONS_TABLE_SESSION << 1)|1,
+ WINDOW_CUSTOMIZE_GLOBAL_SESSION, global_s_options,
+ WINDOW_CUSTOMIZE_SESSION, fs.s->options,
+ WINDOW_CUSTOMIZE_NONE, NULL,
+ ft, filter, &fs);
+ window_customize_build_options(data, "Window & Pane Options",
+ (3ULL << 62)|(OPTIONS_TABLE_WINDOW << 1)|1,
+ WINDOW_CUSTOMIZE_GLOBAL_WINDOW, global_w_options,
+ WINDOW_CUSTOMIZE_WINDOW, fs.w->options,
+ WINDOW_CUSTOMIZE_PANE, fs.wp->options,
+ ft, filter, &fs);
+
+ format_free(ft);
+ ft = format_create_from_state(NULL, NULL, &fs);
+
+ i = 0;
+ kt = key_bindings_first_table();
+ while (kt != NULL) {
+ if (!RB_EMPTY(&kt->key_bindings)) {
+ window_customize_build_keys(data, kt, ft, filter, &fs,
+ i);
+ if (++i == 256)
+ break;
+ }
+ kt = key_bindings_next_table(kt);
+ }
+
+ format_free(ft);
+}
+
+static void
+window_customize_draw_key(__unused struct window_customize_modedata *data,
+ struct window_customize_itemdata *item, struct screen_write_ctx *ctx,
+ u_int sx, u_int sy)
+{
+ struct screen *s = ctx->s;
+ u_int cx = s->cx, cy = s->cy;
+ struct key_table *kt;
+ struct key_binding *bd, *default_bd;
+ const char *note, *period = "";
+ char *cmd, *default_cmd;
+
+ if (item == NULL || !window_customize_get_key(item, &kt, &bd))
+ return;
+
+ note = bd->note;
+ if (note == NULL)
+ note = "There is no note for this key.";
+ if (*note != '\0' && note[strlen (note) - 1] != '.')
+ period = ".";
+ if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s%s",
+ note, period))
+ return;
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */
+ if (s->cy >= cy + sy - 1)
+ return;
+
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "This key is in the %s table.", kt->name))
+ return;
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "This key %s repeat.",
+ (bd->flags & KEY_BINDING_REPEAT) ? "does" : "does not"))
+ return;
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */
+ if (s->cy >= cy + sy - 1)
+ return;
+
+ cmd = cmd_list_print(bd->cmdlist, 0);
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "Command: %s", cmd)) {
+ free(cmd);
+ return;
+ }
+ default_bd = key_bindings_get_default(kt, bd->key);
+ if (default_bd != NULL) {
+ default_cmd = cmd_list_print(default_bd->cmdlist, 0);
+ if (strcmp(cmd, default_cmd) != 0 &&
+ !screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "The default is: %s", default_cmd)) {
+ free(default_cmd);
+ free(cmd);
+ return;
+ }
+ free(default_cmd);
+ }
+ free(cmd);
+}
+
+static void
+window_customize_draw_option(struct window_customize_modedata *data,
+ struct window_customize_itemdata *item, struct screen_write_ctx *ctx,
+ u_int sx, u_int sy)
+{
+ struct screen *s = ctx->s;
+ u_int cx = s->cx, cy = s->cy;
+ int idx;
+ struct options_entry *o, *parent;
+ struct options *go, *wo;
+ const struct options_table_entry *oe;
+ struct grid_cell gc;
+ const char **choice, *text, *name;
+ const char *space = "", *unit = "";
+ char *value = NULL, *expanded;
+ char *default_value = NULL;
+ char choices[256] = "";
+ struct cmd_find_state fs;
+ struct format_tree *ft;
+
+ if (!window_customize_check_item(data, item, &fs))
+ return;
+ name = item->name;
+ idx = item->idx;
+
+ o = options_get(item->oo, name);
+ if (o == NULL)
+ return;
+ oe = options_table_entry(o);
+
+ if (oe != NULL && oe->unit != NULL) {
+ space = " ";
+ unit = oe->unit;
+ }
+ ft = format_create_from_state(NULL, NULL, &fs);
+
+ if (oe == NULL || oe->text == NULL)
+ text = "This option doesn't have a description.";
+ else
+ text = oe->text;
+ if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s",
+ text))
+ goto out;
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */
+ if (s->cy >= cy + sy - 1)
+ goto out;
+
+ if (oe == NULL)
+ text = "user";
+ else if ((oe->scope & (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) ==
+ (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE))
+ text = "window and pane";
+ else if (oe->scope & OPTIONS_TABLE_WINDOW)
+ text = "window";
+ else if (oe->scope & OPTIONS_TABLE_SESSION)
+ text = "session";
+ else
+ text = "server";
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "This is a %s option.", text))
+ goto out;
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) {
+ if (idx != -1) {
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy),
+ 0, &grid_default_cell,
+ "This is an array option, index %u.", idx))
+ goto out;
+ } else {
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy),
+ 0, &grid_default_cell, "This is an array option."))
+ goto out;
+ }
+ if (idx == -1)
+ goto out;
+ }
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */
+ if (s->cy >= cy + sy - 1)
+ goto out;
+
+ value = options_to_string(o, idx, 0);
+ if (oe != NULL && idx == -1) {
+ default_value = options_default_to_string(oe);
+ if (strcmp(default_value, value) == 0) {
+ free(default_value);
+ default_value = NULL;
+ }
+ }
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "Option value: %s%s%s", value, space, unit))
+ goto out;
+ if (oe == NULL || oe->type == OPTIONS_TABLE_STRING) {
+ expanded = format_expand(ft, value);
+ if (strcmp(expanded, value) != 0) {
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy),
+ 0, &grid_default_cell, "This expands to: %s",
+ expanded))
+ goto out;
+ }
+ free(expanded);
+ }
+ if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) {
+ for (choice = oe->choices; *choice != NULL; choice++) {
+ strlcat(choices, *choice, sizeof choices);
+ strlcat(choices, ", ", sizeof choices);
+ }
+ choices[strlen(choices) - 2] = '\0';
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "Available values are: %s",
+ choices))
+ goto out;
+ }
+ if (oe != NULL && oe->type == OPTIONS_TABLE_COLOUR) {
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1,
+ &grid_default_cell, "This is a colour option: "))
+ goto out;
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ gc.fg = options_get_number(item->oo, name);
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc,
+ "EXAMPLE"))
+ goto out;
+ }
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_STYLE)) {
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1,
+ &grid_default_cell, "This is a style option: "))
+ goto out;
+ style_apply(&gc, item->oo, name, ft);
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc,
+ "EXAMPLE"))
+ goto out;
+ }
+ if (default_value != NULL) {
+ if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0,
+ &grid_default_cell, "The default is: %s%s%s", default_value,
+ space, unit))
+ goto out;
+ }
+
+ screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */
+ if (s->cy > cy + sy - 1)
+ goto out;
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) {
+ wo = NULL;
+ go = NULL;
+ } else {
+ switch (item->scope) {
+ case WINDOW_CUSTOMIZE_PANE:
+ wo = options_get_parent(item->oo);
+ go = options_get_parent(wo);
+ break;
+ case WINDOW_CUSTOMIZE_WINDOW:
+ case WINDOW_CUSTOMIZE_SESSION:
+ wo = NULL;
+ go = options_get_parent(item->oo);
+ break;
+ default:
+ wo = NULL;
+ go = NULL;
+ break;
+ }
+ }
+ if (wo != NULL && options_owner(o) != wo) {
+ parent = options_get_only(wo, name);
+ if (parent != NULL) {
+ value = options_to_string(parent, -1 , 0);
+ if (!screen_write_text(ctx, s->cx, sx,
+ sy - (s->cy - cy), 0, &grid_default_cell,
+ "Window value (from window %u): %s%s%s", fs.wl->idx,
+ value, space, unit))
+ goto out;
+ }
+ }
+ if (go != NULL && options_owner(o) != go) {
+ parent = options_get_only(go, name);
+ if (parent != NULL) {
+ value = options_to_string(parent, -1 , 0);
+ if (!screen_write_text(ctx, s->cx, sx,
+ sy - (s->cy - cy), 0, &grid_default_cell,
+ "Global value: %s%s%s", value, space, unit))
+ goto out;
+ }
+ }
+
+out:
+ free(value);
+ free(default_value);
+ format_free(ft);
+}
+
+static void
+window_customize_draw(void *modedata, void *itemdata,
+ struct screen_write_ctx *ctx, u_int sx, u_int sy)
+{
+ struct window_customize_modedata *data = modedata;
+ struct window_customize_itemdata *item = itemdata;
+
+ if (item == NULL)
+ return;
+
+ if (item->scope == WINDOW_CUSTOMIZE_KEY)
+ window_customize_draw_key(data, item, ctx, sx, sy);
+ else
+ window_customize_draw_option(data, item, ctx, sx, sy);
+}
+
+static void
+window_customize_menu(void *modedata, struct client *c, key_code key)
+{
+ struct window_customize_modedata *data = modedata;
+ struct window_pane *wp = data->wp;
+ struct window_mode_entry *wme;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL || wme->data != modedata)
+ return;
+ window_customize_key(wme, c, NULL, NULL, key, NULL);
+}
+
+static u_int
+window_customize_height(__unused void *modedata, __unused u_int height)
+{
+ return (12);
+}
+
+static struct screen *
+window_customize_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
+ struct args *args)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_customize_modedata *data;
+ struct screen *s;
+
+ wme->data = data = xcalloc(1, sizeof *data);
+ data->wp = wp;
+ data->references = 1;
+
+ memcpy(&data->fs, fs, sizeof data->fs);
+
+ if (args == NULL || !args_has(args, 'F'))
+ data->format = xstrdup(WINDOW_CUSTOMIZE_DEFAULT_FORMAT);
+ else
+ data->format = xstrdup(args_get(args, 'F'));
+
+ data->data = mode_tree_start(wp, args, window_customize_build,
+ window_customize_draw, NULL, window_customize_menu,
+ window_customize_height, NULL, data, window_customize_menu_items,
+ NULL, 0, &s);
+ mode_tree_zoom(data->data, args);
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+
+ return (s);
+}
+
+static void
+window_customize_destroy(struct window_customize_modedata *data)
+{
+ u_int i;
+
+ if (--data->references != 0)
+ return;
+
+ for (i = 0; i < data->item_size; i++)
+ window_customize_free_item(data->item_list[i]);
+ free(data->item_list);
+
+ free(data->format);
+
+ free(data);
+}
+
+static void
+window_customize_free(struct window_mode_entry *wme)
+{
+ struct window_customize_modedata *data = wme->data;
+
+ if (data == NULL)
+ return;
+
+ data->dead = 1;
+ mode_tree_free(data->data);
+ window_customize_destroy(data);
+}
+
+static void
+window_customize_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
+{
+ struct window_customize_modedata *data = wme->data;
+
+ mode_tree_resize(data->data, sx, sy);
+}
+
+static void
+window_customize_free_callback(void *modedata)
+{
+ window_customize_destroy(modedata);
+}
+
+static void
+window_customize_free_item_callback(void *itemdata)
+{
+ struct window_customize_itemdata *item = itemdata;
+ struct window_customize_modedata *data = item->data;
+
+ window_customize_free_item(item);
+ window_customize_destroy(data);
+}
+
+static int
+window_customize_set_option_callback(struct client *c, void *itemdata,
+ const char *s, __unused int done)
+{
+ struct window_customize_itemdata *item = itemdata;
+ struct window_customize_modedata *data = item->data;
+ struct options_entry *o;
+ const struct options_table_entry *oe;
+ struct options *oo = item->oo;
+ const char *name = item->name;
+ char *cause;
+ int idx = item->idx;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (item == NULL || !window_customize_check_item(data, item, NULL))
+ return (0);
+ o = options_get(oo, name);
+ if (o == NULL)
+ return (0);
+ oe = options_table_entry(o);
+
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) {
+ if (idx == -1) {
+ for (idx = 0; idx < INT_MAX; idx++) {
+ if (options_array_get(o, idx) == NULL)
+ break;
+ }
+ }
+ if (options_array_set(o, idx, s, 0, &cause) != 0)
+ goto fail;
+ } else {
+ if (options_from_string(oo, oe, name, s, 0, &cause) != 0)
+ goto fail;
+ }
+
+ options_push_changes(item->name);
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+
+ return (0);
+
+fail:
+ *cause = toupper((u_char)*cause);
+ status_message_set(c, -1, 1, 0, "%s", cause);
+ free(cause);
+ return (0);
+}
+
+static void
+window_customize_set_option(struct client *c,
+ struct window_customize_modedata *data,
+ struct window_customize_itemdata *item, int global, int pane)
+{
+ struct options_entry *o;
+ const struct options_table_entry *oe;
+ struct options *oo;
+ struct window_customize_itemdata *new_item;
+ int flag, idx = item->idx;
+ enum window_customize_scope scope = WINDOW_CUSTOMIZE_NONE;
+ u_int choice;
+ const char *name = item->name, *space = "";
+ char *prompt, *value, *text;
+ struct cmd_find_state fs;
+
+ if (item == NULL || !window_customize_check_item(data, item, &fs))
+ return;
+ o = options_get(item->oo, name);
+ if (o == NULL)
+ return;
+
+ oe = options_table_entry(o);
+ if (oe != NULL && ~oe->scope & OPTIONS_TABLE_PANE)
+ pane = 0;
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) {
+ scope = item->scope;
+ oo = item->oo;
+ } else {
+ if (global) {
+ switch (item->scope) {
+ case WINDOW_CUSTOMIZE_NONE:
+ case WINDOW_CUSTOMIZE_KEY:
+ case WINDOW_CUSTOMIZE_SERVER:
+ case WINDOW_CUSTOMIZE_GLOBAL_SESSION:
+ case WINDOW_CUSTOMIZE_GLOBAL_WINDOW:
+ scope = item->scope;
+ break;
+ case WINDOW_CUSTOMIZE_SESSION:
+ scope = WINDOW_CUSTOMIZE_GLOBAL_SESSION;
+ break;
+ case WINDOW_CUSTOMIZE_WINDOW:
+ case WINDOW_CUSTOMIZE_PANE:
+ scope = WINDOW_CUSTOMIZE_GLOBAL_WINDOW;
+ break;
+ }
+ } else {
+ switch (item->scope) {
+ case WINDOW_CUSTOMIZE_NONE:
+ case WINDOW_CUSTOMIZE_KEY:
+ case WINDOW_CUSTOMIZE_SERVER:
+ case WINDOW_CUSTOMIZE_SESSION:
+ scope = item->scope;
+ break;
+ case WINDOW_CUSTOMIZE_WINDOW:
+ case WINDOW_CUSTOMIZE_PANE:
+ if (pane)
+ scope = WINDOW_CUSTOMIZE_PANE;
+ else
+ scope = WINDOW_CUSTOMIZE_WINDOW;
+ break;
+ case WINDOW_CUSTOMIZE_GLOBAL_SESSION:
+ scope = WINDOW_CUSTOMIZE_SESSION;
+ break;
+ case WINDOW_CUSTOMIZE_GLOBAL_WINDOW:
+ if (pane)
+ scope = WINDOW_CUSTOMIZE_PANE;
+ else
+ scope = WINDOW_CUSTOMIZE_WINDOW;
+ break;
+ }
+ }
+ if (scope == item->scope)
+ oo = item->oo;
+ else
+ oo = window_customize_get_tree(scope, &fs);
+ }
+
+ if (oe != NULL && oe->type == OPTIONS_TABLE_FLAG) {
+ flag = options_get_number(oo, name);
+ options_set_number(oo, name, !flag);
+ } else if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) {
+ choice = options_get_number(oo, name);
+ if (oe->choices[choice + 1] == NULL)
+ choice = 0;
+ else
+ choice++;
+ options_set_number(oo, name, choice);
+ } else {
+ text = window_customize_scope_text(scope, &fs);
+ if (*text != '\0')
+ space = ", for ";
+ else if (scope != WINDOW_CUSTOMIZE_SERVER)
+ space = ", global";
+ if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) {
+ if (idx == -1) {
+ xasprintf(&prompt, "(%s[+]%s%s) ", name, space,
+ text);
+ } else {
+ xasprintf(&prompt, "(%s[%d]%s%s) ", name, idx,
+ space, text);
+ }
+ } else
+ xasprintf(&prompt, "(%s%s%s) ", name, space, text);
+ free(text);
+
+ value = options_to_string(o, idx, 0);
+
+ new_item = xcalloc(1, sizeof *new_item);
+ new_item->data = data;
+ new_item->scope = scope;
+ new_item->oo = oo;
+ new_item->name = xstrdup(name);
+ new_item->idx = idx;
+
+ data->references++;
+ status_prompt_set(c, NULL, prompt, value,
+ window_customize_set_option_callback,
+ window_customize_free_item_callback, new_item,
+ PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+
+ free(prompt);
+ free(value);
+ }
+}
+
+static void
+window_customize_unset_option(struct window_customize_modedata *data,
+ struct window_customize_itemdata *item)
+{
+ struct options_entry *o;
+
+ if (item == NULL || !window_customize_check_item(data, item, NULL))
+ return;
+
+ o = options_get(item->oo, item->name);
+ if (o == NULL)
+ return;
+ if (item->idx != -1 && item == mode_tree_get_current(data->data))
+ mode_tree_up(data->data, 0);
+ options_remove_or_default(o, item->idx, NULL);
+}
+
+static void
+window_customize_reset_option(struct window_customize_modedata *data,
+ struct window_customize_itemdata *item)
+{
+ struct options *oo;
+ struct options_entry *o;
+
+ if (item == NULL || !window_customize_check_item(data, item, NULL))
+ return;
+ if (item->idx != -1)
+ return;
+
+ oo = item->oo;
+ while (oo != NULL) {
+ o = options_get_only(item->oo, item->name);
+ if (o != NULL)
+ options_remove_or_default(o, -1, NULL);
+ oo = options_get_parent(oo);
+ }
+}
+
+static int
+window_customize_set_command_callback(struct client *c, void *itemdata,
+ const char *s, __unused int done)
+{
+ struct window_customize_itemdata *item = itemdata;
+ struct window_customize_modedata *data = item->data;
+ struct key_binding *bd;
+ struct cmd_parse_result *pr;
+ char *error;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (item == NULL || !window_customize_get_key(item, NULL, &bd))
+ return (0);
+
+ pr = cmd_parse_from_string(s, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ error = pr->error;
+ goto fail;
+ case CMD_PARSE_SUCCESS:
+ break;
+ }
+ cmd_list_free(bd->cmdlist);
+ bd->cmdlist = pr->cmdlist;
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+
+ return (0);
+
+fail:
+ *error = toupper((u_char)*error);
+ status_message_set(c, -1, 1, 0, "%s", error);
+ free(error);
+ return (0);
+}
+
+static int
+window_customize_set_note_callback(__unused struct client *c, void *itemdata,
+ const char *s, __unused int done)
+{
+ struct window_customize_itemdata *item = itemdata;
+ struct window_customize_modedata *data = item->data;
+ struct key_binding *bd;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (item == NULL || !window_customize_get_key(item, NULL, &bd))
+ return (0);
+
+ free((void *)bd->note);
+ bd->note = xstrdup(s);
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+
+ return (0);
+}
+
+static void
+window_customize_set_key(struct client *c,
+ struct window_customize_modedata *data,
+ struct window_customize_itemdata *item)
+{
+ key_code key = item->key;
+ struct key_binding *bd;
+ const char *s;
+ char *prompt, *value;
+ struct window_customize_itemdata *new_item;
+
+ if (item == NULL || !window_customize_get_key(item, NULL, &bd))
+ return;
+
+ s = mode_tree_get_current_name(data->data);
+ if (strcmp(s, "Repeat") == 0)
+ bd->flags ^= KEY_BINDING_REPEAT;
+ else if (strcmp(s, "Command") == 0) {
+ xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0));
+ value = cmd_list_print(bd->cmdlist, 0);
+
+ new_item = xcalloc(1, sizeof *new_item);
+ new_item->data = data;
+ new_item->scope = item->scope;
+ new_item->table = xstrdup(item->table);
+ new_item->key = key;
+
+ data->references++;
+ status_prompt_set(c, NULL, prompt, value,
+ window_customize_set_command_callback,
+ window_customize_free_item_callback, new_item,
+ PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ free(value);
+ } else if (strcmp(s, "Note") == 0) {
+ xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0));
+
+ new_item = xcalloc(1, sizeof *new_item);
+ new_item->data = data;
+ new_item->scope = item->scope;
+ new_item->table = xstrdup(item->table);
+ new_item->key = key;
+
+ data->references++;
+ status_prompt_set(c, NULL, prompt,
+ (bd->note == NULL ? "" : bd->note),
+ window_customize_set_note_callback,
+ window_customize_free_item_callback, new_item,
+ PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ }
+}
+
+static void
+window_customize_unset_key(struct window_customize_modedata *data,
+ struct window_customize_itemdata *item)
+{
+ struct key_table *kt;
+ struct key_binding *bd;
+
+ if (item == NULL || !window_customize_get_key(item, &kt, &bd))
+ return;
+
+ if (item == mode_tree_get_current(data->data)) {
+ mode_tree_collapse_current(data->data);
+ mode_tree_up(data->data, 0);
+ }
+ key_bindings_remove(kt->name, bd->key);
+}
+
+static void
+window_customize_reset_key(struct window_customize_modedata *data,
+ struct window_customize_itemdata *item)
+{
+ struct key_table *kt;
+ struct key_binding *dd, *bd;
+
+ if (item == NULL || !window_customize_get_key(item, &kt, &bd))
+ return;
+
+ dd = key_bindings_get_default(kt, bd->key);
+ if (dd != NULL && bd->cmdlist == dd->cmdlist)
+ return;
+ if (dd == NULL && item == mode_tree_get_current(data->data)) {
+ mode_tree_collapse_current(data->data);
+ mode_tree_up(data->data, 0);
+ }
+ key_bindings_reset(kt->name, bd->key);
+}
+
+static void
+window_customize_change_each(void *modedata, void *itemdata,
+ __unused struct client *c, __unused key_code key)
+{
+ struct window_customize_modedata *data = modedata;
+ struct window_customize_itemdata *item = itemdata;
+
+ switch (data->change) {
+ case WINDOW_CUSTOMIZE_UNSET:
+ if (item->scope == WINDOW_CUSTOMIZE_KEY)
+ window_customize_unset_key(data, item);
+ else
+ window_customize_unset_option(data, item);
+ break;
+ case WINDOW_CUSTOMIZE_RESET:
+ if (item->scope == WINDOW_CUSTOMIZE_KEY)
+ window_customize_reset_key(data, item);
+ else
+ window_customize_reset_option(data, item);
+ break;
+ }
+ if (item->scope != WINDOW_CUSTOMIZE_KEY)
+ options_push_changes(item->name);
+}
+
+static int
+window_customize_change_current_callback(__unused struct client *c,
+ void *modedata, const char *s, __unused int done)
+{
+ struct window_customize_modedata *data = modedata;
+ struct window_customize_itemdata *item;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
+ return (0);
+
+ item = mode_tree_get_current(data->data);
+ switch (data->change) {
+ case WINDOW_CUSTOMIZE_UNSET:
+ if (item->scope == WINDOW_CUSTOMIZE_KEY)
+ window_customize_unset_key(data, item);
+ else
+ window_customize_unset_option(data, item);
+ break;
+ case WINDOW_CUSTOMIZE_RESET:
+ if (item->scope == WINDOW_CUSTOMIZE_KEY)
+ window_customize_reset_key(data, item);
+ else
+ window_customize_reset_option(data, item);
+ break;
+ }
+ if (item->scope != WINDOW_CUSTOMIZE_KEY)
+ options_push_changes(item->name);
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+
+ return (0);
+}
+
+static int
+window_customize_change_tagged_callback(struct client *c, void *modedata,
+ const char *s, __unused int done)
+{
+ struct window_customize_modedata *data = modedata;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
+ return (0);
+
+ mode_tree_each_tagged(data->data, window_customize_change_each, c,
+ KEYC_NONE, 0);
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+
+ return (0);
+}
+
+static void
+window_customize_key(struct window_mode_entry *wme, struct client *c,
+ __unused struct session *s, __unused struct winlink *wl, key_code key,
+ struct mouse_event *m)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_customize_modedata *data = wme->data;
+ struct window_customize_itemdata *item, *new_item;
+ int finished, idx;
+ char *prompt;
+ u_int tagged;
+
+ item = mode_tree_get_current(data->data);
+ finished = mode_tree_key(data->data, c, &key, m, NULL, NULL);
+ if (item != (new_item = mode_tree_get_current(data->data)))
+ item = new_item;
+
+ switch (key) {
+ case '\r':
+ case 's':
+ if (item == NULL)
+ break;
+ if (item->scope == WINDOW_CUSTOMIZE_KEY)
+ window_customize_set_key(c, data, item);
+ else {
+ window_customize_set_option(c, data, item, 0, 1);
+ options_push_changes(item->name);
+ }
+ mode_tree_build(data->data);
+ break;
+ case 'w':
+ if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY)
+ break;
+ window_customize_set_option(c, data, item, 0, 0);
+ options_push_changes(item->name);
+ mode_tree_build(data->data);
+ break;
+ case 'S':
+ case 'W':
+ if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY)
+ break;
+ window_customize_set_option(c, data, item, 1, 0);
+ options_push_changes(item->name);
+ mode_tree_build(data->data);
+ break;
+ case 'd':
+ if (item == NULL || item->idx != -1)
+ break;
+ xasprintf(&prompt, "Reset %s to default? ", item->name);
+ data->references++;
+ data->change = WINDOW_CUSTOMIZE_RESET;
+ status_prompt_set(c, NULL, prompt, "",
+ window_customize_change_current_callback,
+ window_customize_free_callback, data,
+ PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case 'D':
+ tagged = mode_tree_count_tagged(data->data);
+ if (tagged == 0)
+ break;
+ xasprintf(&prompt, "Reset %u tagged to default? ", tagged);
+ data->references++;
+ data->change = WINDOW_CUSTOMIZE_RESET;
+ status_prompt_set(c, NULL, prompt, "",
+ window_customize_change_tagged_callback,
+ window_customize_free_callback, data,
+ PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case 'u':
+ if (item == NULL)
+ break;
+ idx = item->idx;
+ if (idx != -1)
+ xasprintf(&prompt, "Unset %s[%d]? ", item->name, idx);
+ else
+ xasprintf(&prompt, "Unset %s? ", item->name);
+ data->references++;
+ data->change = WINDOW_CUSTOMIZE_UNSET;
+ status_prompt_set(c, NULL, prompt, "",
+ window_customize_change_current_callback,
+ window_customize_free_callback, data,
+ PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case 'U':
+ tagged = mode_tree_count_tagged(data->data);
+ if (tagged == 0)
+ break;
+ xasprintf(&prompt, "Unset %u tagged? ", tagged);
+ data->references++;
+ data->change = WINDOW_CUSTOMIZE_UNSET;
+ status_prompt_set(c, NULL, prompt, "",
+ window_customize_change_tagged_callback,
+ window_customize_free_callback, data,
+ PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case 'H':
+ data->hide_global = !data->hide_global;
+ mode_tree_build(data->data);
+ break;
+ }
+ if (finished)
+ window_pane_reset_mode(wp);
+ else {
+ mode_tree_draw(data->data);
+ wp->flags |= PANE_REDRAW;
+ }
+}
diff --git a/window-tree.c b/window-tree.c
new file mode 100644
index 0000000..d90bf81
--- /dev/null
+++ b/window-tree.c
@@ -0,0 +1,1340 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2017 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 <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static struct screen *window_tree_init(struct window_mode_entry *,
+ struct cmd_find_state *, struct args *);
+static void window_tree_free(struct window_mode_entry *);
+static void window_tree_resize(struct window_mode_entry *, u_int,
+ u_int);
+static void window_tree_update(struct window_mode_entry *);
+static void window_tree_key(struct window_mode_entry *,
+ struct client *, struct session *,
+ struct winlink *, key_code, struct mouse_event *);
+
+#define WINDOW_TREE_DEFAULT_COMMAND "switch-client -Zt '%%'"
+
+#define WINDOW_TREE_DEFAULT_FORMAT \
+ "#{?pane_format," \
+ "#{?pane_marked,#[reverse],}" \
+ "#{pane_current_command}#{?pane_active,*,}#{?pane_marked,M,}" \
+ "#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \
+ "," \
+ "#{?window_format," \
+ "#{?window_marked_flag,#[reverse],}" \
+ "#{window_name}#{window_flags}" \
+ "#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \
+ "," \
+ "#{session_windows} windows" \
+ "#{?session_grouped, " \
+ "(group #{session_group}: " \
+ "#{session_group_list})," \
+ "}" \
+ "#{?session_attached, (attached),}" \
+ "}" \
+ "}"
+
+#define WINDOW_TREE_DEFAULT_KEY_FORMAT \
+ "#{?#{e|<:#{line},10}," \
+ "#{line}" \
+ "," \
+ "#{?#{e|<:#{line},36}," \
+ "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
+ "," \
+ "" \
+ "}" \
+ "}"
+
+static const struct menu_item window_tree_menu_items[] = {
+ { "Select", '\r', NULL },
+ { "Expand", KEYC_RIGHT, NULL },
+ { "Mark", 'm', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Tag", 't', NULL },
+ { "Tag All", '\024', NULL },
+ { "Tag None", 'T', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Kill", 'x', NULL },
+ { "Kill Tagged", 'X', NULL },
+ { "", KEYC_NONE, NULL },
+ { "Cancel", 'q', NULL },
+
+ { NULL, KEYC_NONE, NULL }
+};
+
+const struct window_mode window_tree_mode = {
+ .name = "tree-mode",
+ .default_format = WINDOW_TREE_DEFAULT_FORMAT,
+
+ .init = window_tree_init,
+ .free = window_tree_free,
+ .resize = window_tree_resize,
+ .update = window_tree_update,
+ .key = window_tree_key,
+};
+
+enum window_tree_sort_type {
+ WINDOW_TREE_BY_INDEX,
+ WINDOW_TREE_BY_NAME,
+ WINDOW_TREE_BY_TIME,
+};
+static const char *window_tree_sort_list[] = {
+ "index",
+ "name",
+ "time"
+};
+static struct mode_tree_sort_criteria *window_tree_sort;
+
+enum window_tree_type {
+ WINDOW_TREE_NONE,
+ WINDOW_TREE_SESSION,
+ WINDOW_TREE_WINDOW,
+ WINDOW_TREE_PANE,
+};
+
+struct window_tree_itemdata {
+ enum window_tree_type type;
+ int session;
+ int winlink;
+ int pane;
+};
+
+struct window_tree_modedata {
+ struct window_pane *wp;
+ int dead;
+ int references;
+
+ struct mode_tree_data *data;
+ char *format;
+ char *key_format;
+ char *command;
+ int squash_groups;
+
+ struct window_tree_itemdata **item_list;
+ u_int item_size;
+
+ const char *entered;
+
+ struct cmd_find_state fs;
+ enum window_tree_type type;
+
+ int offset;
+
+ int left;
+ int right;
+ u_int start;
+ u_int end;
+ u_int each;
+};
+
+static void
+window_tree_pull_item(struct window_tree_itemdata *item, struct session **sp,
+ struct winlink **wlp, struct window_pane **wp)
+{
+ *wp = NULL;
+ *wlp = NULL;
+ *sp = session_find_by_id(item->session);
+ if (*sp == NULL)
+ return;
+ if (item->type == WINDOW_TREE_SESSION) {
+ *wlp = (*sp)->curw;
+ *wp = (*wlp)->window->active;
+ return;
+ }
+
+ *wlp = winlink_find_by_index(&(*sp)->windows, item->winlink);
+ if (*wlp == NULL) {
+ *sp = NULL;
+ return;
+ }
+ if (item->type == WINDOW_TREE_WINDOW) {
+ *wp = (*wlp)->window->active;
+ return;
+ }
+
+ *wp = window_pane_find_by_id(item->pane);
+ if (!window_has_pane((*wlp)->window, *wp))
+ *wp = NULL;
+ if (*wp == NULL) {
+ *sp = NULL;
+ *wlp = NULL;
+ return;
+ }
+}
+
+static struct window_tree_itemdata *
+window_tree_add_item(struct window_tree_modedata *data)
+{
+ struct window_tree_itemdata *item;
+
+ data->item_list = xreallocarray(data->item_list, data->item_size + 1,
+ sizeof *data->item_list);
+ item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
+ return (item);
+}
+
+static void
+window_tree_free_item(struct window_tree_itemdata *item)
+{
+ free(item);
+}
+
+static int
+window_tree_cmp_session(const void *a0, const void *b0)
+{
+ const struct session *const *a = a0;
+ const struct session *const *b = b0;
+ const struct session *sa = *a;
+ const struct session *sb = *b;
+ int result = 0;
+
+ switch (window_tree_sort->field) {
+ case WINDOW_TREE_BY_INDEX:
+ result = sa->id - sb->id;
+ break;
+ case WINDOW_TREE_BY_TIME:
+ if (timercmp(&sa->activity_time, &sb->activity_time, >)) {
+ result = -1;
+ break;
+ }
+ if (timercmp(&sa->activity_time, &sb->activity_time, <)) {
+ result = 1;
+ break;
+ }
+ /* FALLTHROUGH */
+ case WINDOW_TREE_BY_NAME:
+ result = strcmp(sa->name, sb->name);
+ break;
+ }
+
+ if (window_tree_sort->reversed)
+ result = -result;
+ return (result);
+}
+
+static int
+window_tree_cmp_window(const void *a0, const void *b0)
+{
+ const struct winlink *const *a = a0;
+ const struct winlink *const *b = b0;
+ const struct winlink *wla = *a;
+ const struct winlink *wlb = *b;
+ struct window *wa = wla->window;
+ struct window *wb = wlb->window;
+ int result = 0;
+
+ switch (window_tree_sort->field) {
+ case WINDOW_TREE_BY_INDEX:
+ result = wla->idx - wlb->idx;
+ break;
+ case WINDOW_TREE_BY_TIME:
+ if (timercmp(&wa->activity_time, &wb->activity_time, >)) {
+ result = -1;
+ break;
+ }
+ if (timercmp(&wa->activity_time, &wb->activity_time, <)) {
+ result = 1;
+ break;
+ }
+ /* FALLTHROUGH */
+ case WINDOW_TREE_BY_NAME:
+ result = strcmp(wa->name, wb->name);
+ break;
+ }
+
+ if (window_tree_sort->reversed)
+ result = -result;
+ return (result);
+}
+
+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;
+
+ if (window_tree_sort->field == WINDOW_TREE_BY_TIME)
+ result = (*a)->active_point - (*b)->active_point;
+ else {
+ /*
+ * Panes don't have names, so use number order for any other
+ * sort field.
+ */
+ result = (*a)->id - (*b)->id;
+ }
+ if (window_tree_sort->reversed)
+ result = -result;
+ return (result);
+}
+
+static void
+window_tree_build_pane(struct session *s, struct winlink *wl,
+ struct window_pane *wp, void *modedata, struct mode_tree_item *parent)
+{
+ struct window_tree_modedata *data = modedata;
+ struct window_tree_itemdata *item;
+ char *name, *text;
+ u_int idx;
+
+ window_pane_index(wp, &idx);
+
+ item = window_tree_add_item(data);
+ item->type = WINDOW_TREE_PANE;
+ item->session = s->id;
+ item->winlink = wl->idx;
+ item->pane = wp->id;
+
+ text = format_single(NULL, data->format, NULL, s, wl, wp);
+ xasprintf(&name, "%u", idx);
+
+ mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1);
+ free(text);
+ free(name);
+}
+
+static int
+window_tree_filter_pane(struct session *s, struct winlink *wl,
+ struct window_pane *wp, const char *filter)
+{
+ char *cp;
+ int result;
+
+ if (filter == NULL)
+ return (1);
+
+ cp = format_single(NULL, filter, NULL, s, wl, wp);
+ result = format_true(cp);
+ free(cp);
+
+ return (result);
+}
+
+static int
+window_tree_build_window(struct session *s, struct winlink *wl,
+ void *modedata, struct mode_tree_sort_criteria *sort_crit,
+ struct mode_tree_item *parent, const char *filter)
+{
+ struct window_tree_modedata *data = modedata;
+ struct window_tree_itemdata *item;
+ struct mode_tree_item *mti;
+ char *name, *text;
+ struct window_pane *wp, **l;
+ u_int n, i;
+ int expanded;
+
+ item = window_tree_add_item(data);
+ item->type = WINDOW_TREE_WINDOW;
+ item->session = s->id;
+ item->winlink = wl->idx;
+ item->pane = -1;
+
+ text = format_single(NULL, data->format, NULL, s, wl, NULL);
+ xasprintf(&name, "%u", wl->idx);
+
+ if (data->type == WINDOW_TREE_SESSION ||
+ data->type == WINDOW_TREE_WINDOW)
+ expanded = 0;
+ else
+ expanded = 1;
+ mti = mode_tree_add(data->data, parent, item, (uint64_t)wl, name, text,
+ expanded);
+ free(text);
+ free(name);
+
+ if ((wp = TAILQ_FIRST(&wl->window->panes)) == NULL)
+ goto empty;
+ if (TAILQ_NEXT(wp, entry) == NULL) {
+ if (!window_tree_filter_pane(s, wl, wp, filter))
+ goto empty;
+ return (1);
+ }
+
+ l = NULL;
+ n = 0;
+
+ TAILQ_FOREACH(wp, &wl->window->panes, entry) {
+ if (!window_tree_filter_pane(s, wl, wp, filter))
+ continue;
+ l = xreallocarray(l, n + 1, sizeof *l);
+ l[n++] = wp;
+ }
+ if (n == 0)
+ goto empty;
+
+ window_tree_sort = sort_crit;
+ qsort(l, n, sizeof *l, window_tree_cmp_pane);
+
+ for (i = 0; i < n; i++)
+ window_tree_build_pane(s, wl, l[i], modedata, mti);
+ free(l);
+ return (1);
+
+empty:
+ window_tree_free_item(item);
+ data->item_size--;
+ mode_tree_remove(data->data, mti);
+ return (0);
+}
+
+static void
+window_tree_build_session(struct session *s, void *modedata,
+ struct mode_tree_sort_criteria *sort_crit, const char *filter)
+{
+ struct window_tree_modedata *data = modedata;
+ struct window_tree_itemdata *item;
+ struct mode_tree_item *mti;
+ char *text;
+ struct winlink *wl, **l;
+ u_int n, i, empty;
+ int expanded;
+
+ item = window_tree_add_item(data);
+ item->type = WINDOW_TREE_SESSION;
+ item->session = s->id;
+ item->winlink = -1;
+ item->pane = -1;
+
+ text = format_single(NULL, data->format, NULL, s, NULL, NULL);
+
+ if (data->type == WINDOW_TREE_SESSION)
+ expanded = 0;
+ else
+ expanded = 1;
+ mti = mode_tree_add(data->data, NULL, item, (uint64_t)s, s->name, text,
+ expanded);
+ free(text);
+
+ l = NULL;
+ n = 0;
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ l = xreallocarray(l, n + 1, sizeof *l);
+ l[n++] = wl;
+ }
+ window_tree_sort = sort_crit;
+ qsort(l, n, sizeof *l, window_tree_cmp_window);
+
+ empty = 0;
+ for (i = 0; i < n; i++) {
+ if (!window_tree_build_window(s, l[i], modedata, sort_crit, mti,
+ filter))
+ empty++;
+ }
+ if (empty == n) {
+ window_tree_free_item(item);
+ data->item_size--;
+ mode_tree_remove(data->data, mti);
+ }
+ free(l);
+}
+
+static void
+window_tree_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
+ uint64_t *tag, const char *filter)
+{
+ struct window_tree_modedata *data = modedata;
+ struct session *s, **l;
+ struct session_group *sg, *current;
+ u_int n, i;
+
+ current = session_group_contains(data->fs.s);
+
+ for (i = 0; i < data->item_size; i++)
+ window_tree_free_item(data->item_list[i]);
+ free(data->item_list);
+ data->item_list = NULL;
+ data->item_size = 0;
+
+ l = NULL;
+ n = 0;
+ RB_FOREACH(s, sessions, &sessions) {
+ if (data->squash_groups &&
+ (sg = session_group_contains(s)) != NULL) {
+ if ((sg == current && s != data->fs.s) ||
+ (sg != current && s != TAILQ_FIRST(&sg->sessions)))
+ continue;
+ }
+ l = xreallocarray(l, n + 1, sizeof *l);
+ l[n++] = s;
+ }
+ window_tree_sort = sort_crit;
+ qsort(l, n, sizeof *l, window_tree_cmp_session);
+
+ for (i = 0; i < n; i++)
+ window_tree_build_session(l[i], modedata, sort_crit, filter);
+ free(l);
+
+ switch (data->type) {
+ case WINDOW_TREE_NONE:
+ break;
+ case WINDOW_TREE_SESSION:
+ *tag = (uint64_t)data->fs.s;
+ break;
+ case WINDOW_TREE_WINDOW:
+ *tag = (uint64_t)data->fs.wl;
+ break;
+ case WINDOW_TREE_PANE:
+ if (window_count_panes(data->fs.wl->window) == 1)
+ *tag = (uint64_t)data->fs.wl;
+ else
+ *tag = (uint64_t)data->fs.wp;
+ break;
+ }
+}
+
+static void
+window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py,
+ u_int sx, u_int sy, const struct grid_cell *gc, const char *label)
+{
+ size_t len;
+ u_int ox, oy;
+
+ len = strlen(label);
+ if (sx == 0 || sy == 1 || len > sx)
+ return;
+ ox = (sx - len + 1) / 2;
+ oy = (sy + 1) / 2;
+
+ if (ox > 1 && ox + len < sx - 1 && sy >= 3) {
+ screen_write_cursormove(ctx, px + ox - 1, py + oy - 1, 0);
+ screen_write_box(ctx, len + 2, 3, BOX_LINES_DEFAULT, NULL,
+ NULL);
+ }
+ screen_write_cursormove(ctx, px + ox, py + oy, 0);
+ screen_write_puts(ctx, gc, "%s", label);
+}
+
+static void
+window_tree_draw_session(struct window_tree_modedata *data, struct session *s,
+ struct screen_write_ctx *ctx, u_int sx, u_int sy)
+{
+ struct options *oo = s->options;
+ struct winlink *wl;
+ struct window *w;
+ 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;
+ struct grid_cell gc;
+ int colour, active_colour, left, right;
+ char *label;
+
+ total = winlink_count(&s->windows);
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ colour = options_get_number(oo, "display-panes-colour");
+ active_colour = options_get_number(oo, "display-panes-active-colour");
+
+ if (sx / total < 24) {
+ visible = sx / 24;
+ if (visible == 0)
+ visible = 1;
+ } else
+ visible = total;
+
+ current = 0;
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ if (wl == s->curw)
+ break;
+ current++;
+ }
+
+ if (current < visible) {
+ start = 0;
+ end = visible;
+ } else if (current >= total - visible) {
+ start = total - visible;
+ end = total;
+ } else {
+ start = current - (visible / 2);
+ end = start + visible;
+ }
+
+ if (data->offset < -(int)start)
+ data->offset = -(int)start;
+ if (data->offset > (int)(total - end))
+ data->offset = (int)(total - end);
+ start += data->offset;
+ end += data->offset;
+
+ left = (start != 0);
+ right = (end != total);
+ if (((left && right) && sx <= 6) || ((left || right) && sx <= 3))
+ left = right = 0;
+ if (left && right) {
+ each = (sx - 6) / visible;
+ remaining = (sx - 6) - (visible * each);
+ } else if (left || right) {
+ each = (sx - 3) / visible;
+ remaining = (sx - 3) - (visible * each);
+ } else {
+ each = sx / visible;
+ remaining = sx - (visible * each);
+ }
+ if (each == 0)
+ return;
+
+ if (left) {
+ data->left = cx + 2;
+ screen_write_cursormove(ctx, cx + 2, cy, 0);
+ screen_write_vline(ctx, sy, 0, 0);
+ screen_write_cursormove(ctx, cx, cy + sy / 2, 0);
+ screen_write_puts(ctx, &grid_default_cell, "<");
+ } else
+ data->left = -1;
+ if (right) {
+ data->right = cx + sx - 3;
+ screen_write_cursormove(ctx, cx + sx - 3, cy, 0);
+ screen_write_vline(ctx, sy, 0, 0);
+ screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0);
+ screen_write_puts(ctx, &grid_default_cell, ">");
+ } else
+ data->right = -1;
+
+ data->start = start;
+ data->end = end;
+ data->each = each;
+
+ i = loop = 0;
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ if (loop == end)
+ break;
+ if (loop < start) {
+ loop++;
+ continue;
+ }
+ w = wl->window;
+
+ if (wl == s->curw)
+ gc.fg = active_colour;
+ else
+ gc.fg = colour;
+
+ if (left)
+ offset = 3 + (i * each);
+ else
+ offset = (i * each);
+ if (loop == end - 1)
+ width = each + remaining;
+ else
+ width = each - 1;
+
+ screen_write_cursormove(ctx, cx + offset, cy, 0);
+ screen_write_preview(ctx, &w->active->base, width, sy);
+
+ xasprintf(&label, " %u:%s ", wl->idx, w->name);
+ if (strlen(label) > width)
+ xasprintf(&label, " %u ", wl->idx);
+ window_tree_draw_label(ctx, cx + offset, cy, width, sy, &gc,
+ label);
+ free(label);
+
+ if (loop != end - 1) {
+ screen_write_cursormove(ctx, cx + offset + width, cy, 0);
+ screen_write_vline(ctx, sy, 0, 0);
+ }
+ loop++;
+
+ i++;
+ }
+}
+
+static void
+window_tree_draw_window(struct window_tree_modedata *data, struct session *s,
+ struct window *w, struct screen_write_ctx *ctx, u_int sx, u_int sy)
+{
+ struct options *oo = s->options;
+ 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;
+ struct grid_cell gc;
+ int colour, active_colour, left, right, pane_idx;
+ char *label;
+
+ total = window_count_panes(w);
+
+ memcpy(&gc, &grid_default_cell, sizeof gc);
+ colour = options_get_number(oo, "display-panes-colour");
+ active_colour = options_get_number(oo, "display-panes-active-colour");
+
+ if (sx / total < 24) {
+ visible = sx / 24;
+ if (visible == 0)
+ visible = 1;
+ } else
+ visible = total;
+
+ current = 0;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (wp == w->active)
+ break;
+ current++;
+ }
+
+ if (current < visible) {
+ start = 0;
+ end = visible;
+ } else if (current >= total - visible) {
+ start = total - visible;
+ end = total;
+ } else {
+ start = current - (visible / 2);
+ end = start + visible;
+ }
+
+ if (data->offset < -(int)start)
+ data->offset = -(int)start;
+ if (data->offset > (int)(total - end))
+ data->offset = (int)(total - end);
+ start += data->offset;
+ end += data->offset;
+
+ left = (start != 0);
+ right = (end != total);
+ if (((left && right) && sx <= 6) || ((left || right) && sx <= 3))
+ left = right = 0;
+ if (left && right) {
+ each = (sx - 6) / visible;
+ remaining = (sx - 6) - (visible * each);
+ } else if (left || right) {
+ each = (sx - 3) / visible;
+ remaining = (sx - 3) - (visible * each);
+ } else {
+ each = sx / visible;
+ remaining = sx - (visible * each);
+ }
+ if (each == 0)
+ return;
+
+ if (left) {
+ data->left = cx + 2;
+ screen_write_cursormove(ctx, cx + 2, cy, 0);
+ screen_write_vline(ctx, sy, 0, 0);
+ screen_write_cursormove(ctx, cx, cy + sy / 2, 0);
+ screen_write_puts(ctx, &grid_default_cell, "<");
+ } else
+ data->left = -1;
+ if (right) {
+ data->right = cx + sx - 3;
+ screen_write_cursormove(ctx, cx + sx - 3, cy, 0);
+ screen_write_vline(ctx, sy, 0, 0);
+ screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0);
+ screen_write_puts(ctx, &grid_default_cell, ">");
+ } else
+ data->right = -1;
+
+ data->start = start;
+ data->end = end;
+ data->each = each;
+
+ i = loop = 0;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (loop == end)
+ break;
+ if (loop < start) {
+ loop++;
+ continue;
+ }
+
+ if (wp == w->active)
+ gc.fg = active_colour;
+ else
+ gc.fg = colour;
+
+ if (left)
+ offset = 3 + (i * each);
+ else
+ offset = (i * each);
+ if (loop == end - 1)
+ width = each + remaining;
+ else
+ width = each - 1;
+
+ screen_write_cursormove(ctx, cx + offset, cy, 0);
+ screen_write_preview(ctx, &wp->base, width, sy);
+
+ if (window_pane_index(wp, &pane_idx) != 0)
+ pane_idx = loop;
+ xasprintf(&label, " %u ", pane_idx);
+ window_tree_draw_label(ctx, cx + offset, cy, each, sy, &gc,
+ label);
+ free(label);
+
+ if (loop != end - 1) {
+ screen_write_cursormove(ctx, cx + offset + width, cy, 0);
+ screen_write_vline(ctx, sy, 0, 0);
+ }
+ loop++;
+
+ i++;
+ }
+}
+
+static void
+window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx,
+ u_int sx, u_int sy)
+{
+ struct window_tree_itemdata *item = itemdata;
+ struct session *sp;
+ struct winlink *wlp;
+ struct window_pane *wp;
+
+ window_tree_pull_item(item, &sp, &wlp, &wp);
+ if (wp == NULL)
+ return;
+
+ switch (item->type) {
+ case WINDOW_TREE_NONE:
+ break;
+ case WINDOW_TREE_SESSION:
+ window_tree_draw_session(modedata, sp, ctx, sx, sy);
+ break;
+ case WINDOW_TREE_WINDOW:
+ window_tree_draw_window(modedata, sp, wlp->window, ctx, sx, sy);
+ break;
+ case WINDOW_TREE_PANE:
+ screen_write_preview(ctx, &wp->base, sx, sy);
+ break;
+ }
+}
+
+static int
+window_tree_search(__unused void *modedata, void *itemdata, const char *ss)
+{
+ struct window_tree_itemdata *item = itemdata;
+ struct session *s;
+ struct winlink *wl;
+ struct window_pane *wp;
+ char *cmd;
+ int retval;
+
+ window_tree_pull_item(item, &s, &wl, &wp);
+
+ switch (item->type) {
+ case WINDOW_TREE_NONE:
+ return (0);
+ case WINDOW_TREE_SESSION:
+ if (s == NULL)
+ return (0);
+ return (strstr(s->name, ss) != NULL);
+ case WINDOW_TREE_WINDOW:
+ if (s == NULL || wl == NULL)
+ return (0);
+ return (strstr(wl->window->name, ss) != NULL);
+ case WINDOW_TREE_PANE:
+ if (s == NULL || wl == NULL || wp == NULL)
+ break;
+ cmd = osdep_get_name(wp->fd, wp->tty);
+ if (cmd == NULL || *cmd == '\0')
+ return (0);
+ retval = (strstr(cmd, ss) != NULL);
+ free(cmd);
+ return (retval);
+ }
+ return (0);
+}
+
+static void
+window_tree_menu(void *modedata, struct client *c, key_code key)
+{
+ struct window_tree_modedata *data = modedata;
+ struct window_pane *wp = data->wp;
+ struct window_mode_entry *wme;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme == NULL || wme->data != modedata)
+ return;
+ window_tree_key(wme, c, NULL, NULL, key, NULL);
+}
+
+static key_code
+window_tree_get_key(void *modedata, void *itemdata, u_int line)
+{
+ struct window_tree_modedata *data = modedata;
+ struct window_tree_itemdata *item = itemdata;
+ struct format_tree *ft;
+ struct session *s;
+ struct winlink *wl;
+ struct window_pane *wp;
+ char *expanded;
+ key_code key;
+
+ ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+ window_tree_pull_item(item, &s, &wl, &wp);
+ if (item->type == WINDOW_TREE_SESSION)
+ format_defaults(ft, NULL, s, NULL, NULL);
+ else if (item->type == WINDOW_TREE_WINDOW)
+ format_defaults(ft, NULL, s, wl, NULL);
+ else
+ format_defaults(ft, NULL, s, wl, wp);
+ format_add(ft, "line", "%u", line);
+
+ expanded = format_expand(ft, data->key_format);
+ key = key_string_lookup_string(expanded);
+ free(expanded);
+ format_free(ft);
+ return (key);
+}
+
+static struct screen *
+window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
+ struct args *args)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_tree_modedata *data;
+ struct screen *s;
+
+ wme->data = data = xcalloc(1, sizeof *data);
+ data->wp = wp;
+ data->references = 1;
+
+ if (args_has(args, 's'))
+ data->type = WINDOW_TREE_SESSION;
+ else if (args_has(args, 'w'))
+ data->type = WINDOW_TREE_WINDOW;
+ else
+ data->type = WINDOW_TREE_PANE;
+ memcpy(&data->fs, fs, sizeof data->fs);
+
+ if (args == NULL || !args_has(args, 'F'))
+ data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT);
+ else
+ data->format = xstrdup(args_get(args, 'F'));
+ if (args == NULL || !args_has(args, 'K'))
+ data->key_format = xstrdup(WINDOW_TREE_DEFAULT_KEY_FORMAT);
+ else
+ data->key_format = xstrdup(args_get(args, 'K'));
+ if (args == NULL || args_count(args) == 0)
+ data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
+ else
+ data->command = xstrdup(args_string(args, 0));
+ data->squash_groups = !args_has(args, 'G');
+
+ data->data = mode_tree_start(wp, args, window_tree_build,
+ window_tree_draw, window_tree_search, window_tree_menu, NULL,
+ window_tree_get_key, data, window_tree_menu_items,
+ window_tree_sort_list, nitems(window_tree_sort_list), &s);
+ mode_tree_zoom(data->data, args);
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+
+ data->type = WINDOW_TREE_NONE;
+
+ return (s);
+}
+
+static void
+window_tree_destroy(struct window_tree_modedata *data)
+{
+ u_int i;
+
+ if (--data->references != 0)
+ return;
+
+ for (i = 0; i < data->item_size; i++)
+ window_tree_free_item(data->item_list[i]);
+ free(data->item_list);
+
+ free(data->format);
+ free(data->key_format);
+ free(data->command);
+
+ free(data);
+}
+
+static void
+window_tree_free(struct window_mode_entry *wme)
+{
+ struct window_tree_modedata *data = wme->data;
+
+ if (data == NULL)
+ return;
+
+ data->dead = 1;
+ mode_tree_free(data->data);
+ window_tree_destroy(data);
+}
+
+static void
+window_tree_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
+{
+ struct window_tree_modedata *data = wme->data;
+
+ mode_tree_resize(data->data, sx, sy);
+}
+
+static void
+window_tree_update(struct window_mode_entry *wme)
+{
+ struct window_tree_modedata *data = wme->data;
+
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+}
+
+static char *
+window_tree_get_target(struct window_tree_itemdata *item,
+ struct cmd_find_state *fs)
+{
+ struct session *s;
+ struct winlink *wl;
+ struct window_pane *wp;
+ char *target;
+
+ window_tree_pull_item(item, &s, &wl, &wp);
+
+ target = NULL;
+ switch (item->type) {
+ case WINDOW_TREE_NONE:
+ break;
+ case WINDOW_TREE_SESSION:
+ if (s == NULL)
+ break;
+ xasprintf(&target, "=%s:", s->name);
+ break;
+ case WINDOW_TREE_WINDOW:
+ if (s == NULL || wl == NULL)
+ break;
+ xasprintf(&target, "=%s:%u.", s->name, wl->idx);
+ break;
+ case WINDOW_TREE_PANE:
+ if (s == NULL || wl == NULL || wp == NULL)
+ break;
+ xasprintf(&target, "=%s:%u.%%%u", s->name, wl->idx, wp->id);
+ break;
+ }
+ if (target == NULL)
+ cmd_find_clear_state(fs, 0);
+ else
+ cmd_find_from_winlink_pane(fs, wl, wp, 0);
+ return (target);
+}
+
+static void
+window_tree_command_each(void *modedata, void *itemdata, struct client *c,
+ __unused key_code key)
+{
+ struct window_tree_modedata *data = modedata;
+ struct window_tree_itemdata *item = itemdata;
+ char *name;
+ struct cmd_find_state fs;
+
+ name = window_tree_get_target(item, &fs);
+ if (name != NULL)
+ mode_tree_run_command(c, &fs, data->entered, name);
+ free(name);
+}
+
+static enum cmd_retval
+window_tree_command_done(__unused struct cmdq_item *item, void *modedata)
+{
+ struct window_tree_modedata *data = modedata;
+
+ if (!data->dead) {
+ mode_tree_build(data->data);
+ mode_tree_draw(data->data);
+ data->wp->flags |= PANE_REDRAW;
+ }
+ window_tree_destroy(data);
+ return (CMD_RETURN_NORMAL);
+}
+
+static int
+window_tree_command_callback(struct client *c, void *modedata, const char *s,
+ __unused int done)
+{
+ struct window_tree_modedata *data = modedata;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+
+ data->entered = s;
+ mode_tree_each_tagged(data->data, window_tree_command_each, c,
+ KEYC_NONE, 1);
+ data->entered = NULL;
+
+ data->references++;
+ cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
+
+ return (0);
+}
+
+static void
+window_tree_command_free(void *modedata)
+{
+ struct window_tree_modedata *data = modedata;
+
+ window_tree_destroy(data);
+}
+
+static void
+window_tree_kill_each(__unused void *modedata, void *itemdata,
+ __unused struct client *c, __unused key_code key)
+{
+ struct window_tree_itemdata *item = itemdata;
+ struct session *s;
+ struct winlink *wl;
+ struct window_pane *wp;
+
+ window_tree_pull_item(item, &s, &wl, &wp);
+
+ switch (item->type) {
+ case WINDOW_TREE_NONE:
+ break;
+ case WINDOW_TREE_SESSION:
+ if (s != NULL) {
+ server_destroy_session(s);
+ session_destroy(s, 1, __func__);
+ }
+ break;
+ case WINDOW_TREE_WINDOW:
+ if (wl != NULL)
+ server_kill_window(wl->window, 0);
+ break;
+ case WINDOW_TREE_PANE:
+ if (wp != NULL)
+ server_kill_pane(wp);
+ break;
+ }
+}
+
+static int
+window_tree_kill_current_callback(struct client *c, void *modedata,
+ const char *s, __unused int done)
+{
+ struct window_tree_modedata *data = modedata;
+ struct mode_tree_data *mtd = data->data;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
+ return (0);
+
+ window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE);
+ server_renumber_all();
+
+ data->references++;
+ cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
+
+ return (0);
+}
+
+static int
+window_tree_kill_tagged_callback(struct client *c, void *modedata,
+ const char *s, __unused int done)
+{
+ struct window_tree_modedata *data = modedata;
+ struct mode_tree_data *mtd = data->data;
+
+ if (s == NULL || *s == '\0' || data->dead)
+ return (0);
+ if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
+ return (0);
+
+ mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1);
+ server_renumber_all();
+
+ data->references++;
+ cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
+
+ return (0);
+}
+
+static key_code
+window_tree_mouse(struct window_tree_modedata *data, key_code key, u_int x,
+ struct window_tree_itemdata *item)
+{
+ struct session *s;
+ struct winlink *wl;
+ struct window_pane *wp;
+ u_int loop;
+
+ if (key != KEYC_MOUSEDOWN1_PANE)
+ return (KEYC_NONE);
+
+ if (data->left != -1 && x <= (u_int)data->left)
+ return ('<');
+ if (data->right != -1 && x >= (u_int)data->right)
+ return ('>');
+
+ if (data->left != -1)
+ x -= data->left;
+ else if (x != 0)
+ x--;
+ if (x == 0 || data->end == 0)
+ x = 0;
+ else {
+ x = x / data->each;
+ if (data->start + x >= data->end)
+ x = data->end - 1;
+ }
+
+ window_tree_pull_item(item, &s, &wl, &wp);
+ if (item->type == WINDOW_TREE_SESSION) {
+ if (s == NULL)
+ return (KEYC_NONE);
+ mode_tree_expand_current(data->data);
+ loop = 0;
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ if (loop == data->start + x)
+ break;
+ loop++;
+ }
+ if (wl != NULL)
+ mode_tree_set_current(data->data, (uint64_t)wl);
+ return ('\r');
+ }
+ if (item->type == WINDOW_TREE_WINDOW) {
+ if (wl == NULL)
+ return (KEYC_NONE);
+ mode_tree_expand_current(data->data);
+ loop = 0;
+ TAILQ_FOREACH(wp, &wl->window->panes, entry) {
+ if (loop == data->start + x)
+ break;
+ loop++;
+ }
+ if (wp != NULL)
+ mode_tree_set_current(data->data, (uint64_t)wp);
+ return ('\r');
+ }
+ return (KEYC_NONE);
+}
+
+static void
+window_tree_key(struct window_mode_entry *wme, struct client *c,
+ __unused struct session *s, __unused struct winlink *wl, key_code key,
+ struct mouse_event *m)
+{
+ struct window_pane *wp = wme->wp;
+ struct window_tree_modedata *data = wme->data;
+ struct window_tree_itemdata *item, *new_item;
+ char *name, *prompt = NULL;
+ struct cmd_find_state fs, *fsp = &data->fs;
+ int finished;
+ u_int tagged, x, y, idx;
+ struct session *ns;
+ struct winlink *nwl;
+ struct window_pane *nwp;
+
+ item = mode_tree_get_current(data->data);
+ finished = mode_tree_key(data->data, c, &key, m, &x, &y);
+ if (item != (new_item = mode_tree_get_current(data->data))) {
+ item = new_item;
+ data->offset = 0;
+ }
+ if (KEYC_IS_MOUSE(key) && m != NULL)
+ key = window_tree_mouse(data, key, x, item);
+ switch (key) {
+ case '<':
+ data->offset--;
+ break;
+ case '>':
+ data->offset++;
+ break;
+ case 'H':
+ mode_tree_expand(data->data, (uint64_t)fsp->s);
+ mode_tree_expand(data->data, (uint64_t)fsp->wl);
+ if (!mode_tree_set_current(data->data, (uint64_t)wme->wp))
+ mode_tree_set_current(data->data, (uint64_t)fsp->wl);
+ break;
+ case 'm':
+ window_tree_pull_item(item, &ns, &nwl, &nwp);
+ server_set_marked(ns, nwl, nwp);
+ mode_tree_build(data->data);
+ break;
+ case 'M':
+ server_clear_marked();
+ mode_tree_build(data->data);
+ break;
+ case 'x':
+ window_tree_pull_item(item, &ns, &nwl, &nwp);
+ switch (item->type) {
+ case WINDOW_TREE_NONE:
+ break;
+ case WINDOW_TREE_SESSION:
+ if (ns == NULL)
+ break;
+ xasprintf(&prompt, "Kill session %s? ", ns->name);
+ break;
+ case WINDOW_TREE_WINDOW:
+ if (nwl == NULL)
+ break;
+ xasprintf(&prompt, "Kill window %u? ", nwl->idx);
+ break;
+ case WINDOW_TREE_PANE:
+ if (nwp == NULL || window_pane_index(nwp, &idx) != 0)
+ break;
+ xasprintf(&prompt, "Kill pane %u? ", idx);
+ break;
+ }
+ if (prompt == NULL)
+ break;
+ data->references++;
+ status_prompt_set(c, NULL, prompt, "",
+ window_tree_kill_current_callback, window_tree_command_free,
+ data, PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case 'X':
+ tagged = mode_tree_count_tagged(data->data);
+ if (tagged == 0)
+ break;
+ xasprintf(&prompt, "Kill %u tagged? ", tagged);
+ data->references++;
+ status_prompt_set(c, NULL, prompt, "",
+ window_tree_kill_tagged_callback, window_tree_command_free,
+ data, PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case ':':
+ tagged = mode_tree_count_tagged(data->data);
+ if (tagged != 0)
+ xasprintf(&prompt, "(%u tagged) ", tagged);
+ else
+ xasprintf(&prompt, "(current) ");
+ data->references++;
+ status_prompt_set(c, NULL, prompt, "",
+ window_tree_command_callback, window_tree_command_free,
+ data, PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND);
+ free(prompt);
+ break;
+ case '\r':
+ name = window_tree_get_target(item, &fs);
+ if (name != NULL)
+ mode_tree_run_command(c, NULL, data->command, name);
+ finished = 1;
+ free(name);
+ break;
+ }
+ if (finished)
+ window_pane_reset_mode(wp);
+ else {
+ mode_tree_draw(data->data);
+ wp->flags |= PANE_REDRAW;
+ }
+}
diff --git a/window.c b/window.c
new file mode 100644
index 0000000..c0cd9bd
--- /dev/null
+++ b/window.c
@@ -0,0 +1,1620 @@
+/* $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 <sys/ioctl.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Each window is attached to a number of panes, each of which is a pty. This
+ * file contains code to handle them.
+ *
+ * A pane has two buffers attached, these are filled and emptied by the main
+ * server poll loop. Output data is received from pty's in screen format,
+ * translated and returned as a series of escape sequences and strings via
+ * input_parse (in input.c). Input data is received as key codes and written
+ * directly via input_key.
+ *
+ * Each pane also has a "virtual" screen (screen.c) which contains the current
+ * state and is redisplayed when the window is reattached to a client.
+ *
+ * Windows are stored directly on a global array and wrapped in any number of
+ * winlink structs to be linked onto local session RB trees. A reference count
+ * is maintained and a window removed from the global list and destroyed when
+ * it reaches zero.
+ */
+
+/* Global window list. */
+struct windows windows;
+
+/* Global panes tree. */
+struct window_pane_tree all_window_panes;
+static u_int next_window_pane_id;
+static u_int next_window_id;
+static u_int next_active_point;
+
+struct window_pane_input_data {
+ struct cmdq_item *item;
+ u_int wp;
+};
+
+static struct window_pane *window_pane_create(struct window *, u_int, u_int,
+ u_int);
+static void window_pane_destroy(struct window_pane *);
+
+RB_GENERATE(windows, window, entry, window_cmp);
+RB_GENERATE(winlinks, winlink, entry, winlink_cmp);
+RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp);
+
+int
+window_cmp(struct window *w1, struct window *w2)
+{
+ return (w1->id - w2->id);
+}
+
+int
+winlink_cmp(struct winlink *wl1, struct winlink *wl2)
+{
+ return (wl1->idx - wl2->idx);
+}
+
+int
+window_pane_cmp(struct window_pane *wp1, struct window_pane *wp2)
+{
+ return (wp1->id - wp2->id);
+}
+
+struct winlink *
+winlink_find_by_window(struct winlinks *wwl, struct window *w)
+{
+ struct winlink *wl;
+
+ RB_FOREACH(wl, winlinks, wwl) {
+ if (wl->window == w)
+ return (wl);
+ }
+
+ return (NULL);
+}
+
+struct winlink *
+winlink_find_by_index(struct winlinks *wwl, int idx)
+{
+ struct winlink wl;
+
+ if (idx < 0)
+ fatalx("bad index");
+
+ wl.idx = idx;
+ return (RB_FIND(winlinks, wwl, &wl));
+}
+
+struct winlink *
+winlink_find_by_window_id(struct winlinks *wwl, u_int id)
+{
+ struct winlink *wl;
+
+ RB_FOREACH(wl, winlinks, wwl) {
+ if (wl->window->id == id)
+ return (wl);
+ }
+ return (NULL);
+}
+
+static int
+winlink_next_index(struct winlinks *wwl, int idx)
+{
+ int i;
+
+ i = idx;
+ do {
+ if (winlink_find_by_index(wwl, i) == NULL)
+ return (i);
+ if (i == INT_MAX)
+ i = 0;
+ else
+ i++;
+ } while (i != idx);
+ return (-1);
+}
+
+u_int
+winlink_count(struct winlinks *wwl)
+{
+ struct winlink *wl;
+ u_int n;
+
+ n = 0;
+ RB_FOREACH(wl, winlinks, wwl)
+ n++;
+
+ return (n);
+}
+
+struct winlink *
+winlink_add(struct winlinks *wwl, int idx)
+{
+ struct winlink *wl;
+
+ if (idx < 0) {
+ if ((idx = winlink_next_index(wwl, -idx - 1)) == -1)
+ return (NULL);
+ } else if (winlink_find_by_index(wwl, idx) != NULL)
+ return (NULL);
+
+ wl = xcalloc(1, sizeof *wl);
+ wl->idx = idx;
+ RB_INSERT(winlinks, wwl, wl);
+
+ return (wl);
+}
+
+void
+winlink_set_window(struct winlink *wl, struct window *w)
+{
+ if (wl->window != NULL) {
+ TAILQ_REMOVE(&wl->window->winlinks, wl, wentry);
+ window_remove_ref(wl->window, __func__);
+ }
+ TAILQ_INSERT_TAIL(&w->winlinks, wl, wentry);
+ wl->window = w;
+ window_add_ref(w, __func__);
+}
+
+void
+winlink_remove(struct winlinks *wwl, struct winlink *wl)
+{
+ struct window *w = wl->window;
+
+ if (w != NULL) {
+ TAILQ_REMOVE(&w->winlinks, wl, wentry);
+ window_remove_ref(w, __func__);
+ }
+
+ RB_REMOVE(winlinks, wwl, wl);
+ free(wl);
+}
+
+struct winlink *
+winlink_next(struct winlink *wl)
+{
+ return (RB_NEXT(winlinks, wwl, wl));
+}
+
+struct winlink *
+winlink_previous(struct winlink *wl)
+{
+ return (RB_PREV(winlinks, wwl, wl));
+}
+
+struct winlink *
+winlink_next_by_number(struct winlink *wl, struct session *s, int n)
+{
+ for (; n > 0; n--) {
+ if ((wl = RB_NEXT(winlinks, wwl, wl)) == NULL)
+ wl = RB_MIN(winlinks, &s->windows);
+ }
+
+ return (wl);
+}
+
+struct winlink *
+winlink_previous_by_number(struct winlink *wl, struct session *s, int n)
+{
+ for (; n > 0; n--) {
+ if ((wl = RB_PREV(winlinks, wwl, wl)) == NULL)
+ wl = RB_MAX(winlinks, &s->windows);
+ }
+
+ return (wl);
+}
+
+void
+winlink_stack_push(struct winlink_stack *stack, struct winlink *wl)
+{
+ if (wl == NULL)
+ return;
+
+ winlink_stack_remove(stack, wl);
+ TAILQ_INSERT_HEAD(stack, wl, sentry);
+}
+
+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;
+ }
+ }
+}
+
+struct window *
+window_find_by_id_str(const char *s)
+{
+ const char *errstr;
+ u_int id;
+
+ if (*s != '@')
+ return (NULL);
+
+ id = strtonum(s + 1, 0, UINT_MAX, &errstr);
+ if (errstr != NULL)
+ return (NULL);
+ return (window_find_by_id(id));
+}
+
+struct window *
+window_find_by_id(u_int id)
+{
+ struct window w;
+
+ w.id = id;
+ return (RB_FIND(windows, &windows, &w));
+}
+
+void
+window_update_activity(struct window *w)
+{
+ gettimeofday(&w->activity_time, NULL);
+ alerts_queue(w, WINDOW_ACTIVITY);
+}
+
+struct window *
+window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
+{
+ struct window *w;
+
+ if (xpixel == 0)
+ xpixel = DEFAULT_XPIXEL;
+ if (ypixel == 0)
+ ypixel = DEFAULT_YPIXEL;
+
+ w = xcalloc(1, sizeof *w);
+ w->name = xstrdup("");
+ w->flags = 0;
+
+ TAILQ_INIT(&w->panes);
+ w->active = NULL;
+
+ w->lastlayout = -1;
+ w->layout_root = NULL;
+
+ w->sx = sx;
+ w->sy = sy;
+ w->manual_sx = sx;
+ w->manual_sy = sy;
+ w->xpixel = xpixel;
+ w->ypixel = ypixel;
+
+ w->options = options_create(global_w_options);
+
+ w->references = 0;
+ TAILQ_INIT(&w->winlinks);
+
+ w->id = next_window_id++;
+ RB_INSERT(windows, &windows, w);
+
+ window_set_fill_character(w);
+ window_update_activity(w);
+
+ log_debug("%s: @%u create %ux%u (%ux%u)", __func__, w->id, sx, sy,
+ w->xpixel, w->ypixel);
+ return (w);
+}
+
+static void
+window_destroy(struct window *w)
+{
+ log_debug("window @%u destroyed (%d references)", w->id, w->references);
+
+ RB_REMOVE(windows, &windows, w);
+
+ if (w->layout_root != NULL)
+ layout_free_cell(w->layout_root);
+ if (w->saved_layout_root != NULL)
+ layout_free_cell(w->saved_layout_root);
+ free(w->old_layout);
+
+ window_destroy_panes(w);
+
+ if (event_initialized(&w->name_event))
+ evtimer_del(&w->name_event);
+
+ if (event_initialized(&w->alerts_timer))
+ evtimer_del(&w->alerts_timer);
+ if (event_initialized(&w->offset_timer))
+ event_del(&w->offset_timer);
+
+ options_free(w->options);
+ free(w->fill_character);
+
+ free(w->name);
+ free(w);
+}
+
+int
+window_pane_destroy_ready(struct window_pane *wp)
+{
+ int n;
+
+ if (wp->pipe_fd != -1) {
+ if (EVBUFFER_LENGTH(wp->pipe_event->output) != 0)
+ return (0);
+ if (ioctl(wp->fd, FIONREAD, &n) != -1 && n > 0)
+ return (0);
+ }
+
+ if (~wp->flags & PANE_EXITED)
+ return (0);
+ return (1);
+}
+
+void
+window_add_ref(struct window *w, const char *from)
+{
+ w->references++;
+ log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references);
+}
+
+void
+window_remove_ref(struct window *w, const char *from)
+{
+ w->references--;
+ log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references);
+
+ if (w->references == 0)
+ window_destroy(w);
+}
+
+void
+window_set_name(struct window *w, const char *new_name)
+{
+ free(w->name);
+ utf8_stravis(&w->name, new_name, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
+ notify_window("window-renamed", w);
+}
+
+void
+window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
+{
+ if (xpixel == 0)
+ xpixel = DEFAULT_XPIXEL;
+ if (ypixel == 0)
+ ypixel = DEFAULT_YPIXEL;
+
+ log_debug("%s: @%u resize %ux%u (%ux%u)", __func__, w->id, sx, sy,
+ xpixel == -1 ? w->xpixel : (u_int)xpixel,
+ ypixel == -1 ? w->ypixel : (u_int)ypixel);
+ w->sx = sx;
+ w->sy = sy;
+ if (xpixel != -1)
+ w->xpixel = xpixel;
+ if (ypixel != -1)
+ w->ypixel = ypixel;
+}
+
+void
+window_pane_send_resize(struct window_pane *wp, u_int sx, u_int sy)
+{
+ struct window *w = wp->window;
+ struct winsize ws;
+
+ if (wp->fd == -1)
+ return;
+
+ log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, sx, sy);
+
+ memset(&ws, 0, sizeof ws);
+ ws.ws_col = sx;
+ ws.ws_row = sy;
+ ws.ws_xpixel = w->xpixel * ws.ws_col;
+ ws.ws_ypixel = w->ypixel * ws.ws_row;
+ if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1)
+#ifdef __sun
+ /*
+ * Some versions of Solaris apparently can return an error when
+ * resizing; don't know why this happens, can't reproduce on
+ * other platforms and ignoring it doesn't seem to cause any
+ * issues.
+ */
+ if (errno != EINVAL && errno != ENXIO)
+#endif
+ fatal("ioctl failed");
+}
+
+int
+window_has_pane(struct window *w, struct window_pane *wp)
+{
+ struct window_pane *wp1;
+
+ TAILQ_FOREACH(wp1, &w->panes, entry) {
+ if (wp1 == wp)
+ return (1);
+ }
+ return (0);
+}
+
+void
+window_update_focus(struct window *w)
+{
+ if (w != NULL) {
+ log_debug("%s: @%u", __func__, w->id);
+ window_pane_update_focus(w->active);
+ }
+}
+
+void
+window_pane_update_focus(struct window_pane *wp)
+{
+ struct client *c;
+ int focused = 0;
+
+ if (wp != NULL) {
+ if (wp != wp->window->active)
+ focused = 0;
+ else {
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL &&
+ c->session->attached != 0 &&
+ (c->flags & CLIENT_FOCUSED) &&
+ c->session->curw->window == wp->window) {
+ focused = 1;
+ break;
+ }
+ }
+ }
+ if (!focused && (wp->flags & PANE_FOCUSED)) {
+ log_debug("%s: %%%u focus out", __func__, wp->id);
+ if (wp->base.mode & MODE_FOCUSON)
+ bufferevent_write(wp->event, "\033[O", 3);
+ notify_pane("pane-focus-out", wp);
+ wp->flags &= ~PANE_FOCUSED;
+ } else if (focused && (~wp->flags & PANE_FOCUSED)) {
+ log_debug("%s: %%%u focus in", __func__, wp->id);
+ if (wp->base.mode & MODE_FOCUSON)
+ bufferevent_write(wp->event, "\033[I", 3);
+ notify_pane("pane-focus-in", wp);
+ wp->flags |= PANE_FOCUSED;
+ } else
+ log_debug("%s: %%%u focus unchanged", __func__, wp->id);
+ }
+}
+
+int
+window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
+{
+ log_debug("%s: pane %%%u", __func__, wp->id);
+
+ if (wp == w->active)
+ return (0);
+ w->last = w->active;
+
+ 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(w->active);
+ }
+
+ tty_update_window_offset(w);
+
+ if (notify)
+ notify_window("window-pane-changed", w);
+ return (1);
+}
+
+static int
+window_pane_get_palette(struct window_pane *wp, int c)
+{
+ if (wp == NULL)
+ return (-1);
+ return (colour_palette_get(&wp->palette, c));
+}
+
+void
+window_redraw_active_switch(struct window *w, struct window_pane *wp)
+{
+ struct grid_cell *gc1, *gc2;
+ int c1, c2;
+
+ if (wp == w->active)
+ return;
+
+ for (;;) {
+ /*
+ * If the active and inactive styles or palettes are different,
+ * need to redraw the panes.
+ */
+ gc1 = &wp->cached_gc;
+ gc2 = &wp->cached_active_gc;
+ if (!grid_cells_look_equal(gc1, gc2))
+ wp->flags |= PANE_REDRAW;
+ else {
+ c1 = window_pane_get_palette(wp, gc1->fg);
+ c2 = window_pane_get_palette(wp, gc2->fg);
+ if (c1 != c2)
+ wp->flags |= PANE_REDRAW;
+ else {
+ c1 = window_pane_get_palette(wp, gc1->bg);
+ c2 = window_pane_get_palette(wp, gc2->bg);
+ if (c1 != c2)
+ wp->flags |= PANE_REDRAW;
+ }
+ }
+ if (wp == w->active)
+ break;
+ wp = w->active;
+ }
+}
+
+struct window_pane *
+window_get_active_at(struct window *w, u_int x, u_int y)
+{
+ struct window_pane *wp;
+
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (!window_pane_visible(wp))
+ continue;
+ if (x < wp->xoff || x > wp->xoff + wp->sx)
+ continue;
+ if (y < wp->yoff || y > wp->yoff + wp->sy)
+ continue;
+ return (wp);
+ }
+ return (NULL);
+}
+
+struct window_pane *
+window_find_string(struct window *w, const char *s)
+{
+ u_int x, y, top = 0, bottom = w->sy - 1;
+ int status;
+
+ x = w->sx / 2;
+ y = w->sy / 2;
+
+ status = options_get_number(w->options, "pane-border-status");
+ if (status == PANE_STATUS_TOP)
+ top++;
+ else if (status == PANE_STATUS_BOTTOM)
+ bottom--;
+
+ if (strcasecmp(s, "top") == 0)
+ y = top;
+ else if (strcasecmp(s, "bottom") == 0)
+ y = bottom;
+ else if (strcasecmp(s, "left") == 0)
+ x = 0;
+ else if (strcasecmp(s, "right") == 0)
+ x = w->sx - 1;
+ else if (strcasecmp(s, "top-left") == 0) {
+ x = 0;
+ y = top;
+ } else if (strcasecmp(s, "top-right") == 0) {
+ x = w->sx - 1;
+ y = top;
+ } else if (strcasecmp(s, "bottom-left") == 0) {
+ x = 0;
+ y = bottom;
+ } else if (strcasecmp(s, "bottom-right") == 0) {
+ x = w->sx - 1;
+ y = bottom;
+ } else
+ return (NULL);
+
+ return (window_get_active_at(w, x, y));
+}
+
+int
+window_zoom(struct window_pane *wp)
+{
+ struct window *w = wp->window;
+ struct window_pane *wp1;
+
+ if (w->flags & WINDOW_ZOOMED)
+ return (-1);
+
+ if (window_count_panes(w) == 1)
+ return (-1);
+
+ if (w->active != wp)
+ window_set_active_pane(w, wp, 1);
+
+ TAILQ_FOREACH(wp1, &w->panes, entry) {
+ wp1->saved_layout_cell = wp1->layout_cell;
+ wp1->layout_cell = NULL;
+ }
+
+ w->saved_layout_root = w->layout_root;
+ layout_init(w, wp);
+ w->flags |= WINDOW_ZOOMED;
+ notify_window("window-layout-changed", w);
+
+ return (0);
+}
+
+int
+window_unzoom(struct window *w)
+{
+ struct window_pane *wp;
+
+ if (!(w->flags & WINDOW_ZOOMED))
+ return (-1);
+
+ w->flags &= ~WINDOW_ZOOMED;
+ layout_free(w);
+ w->layout_root = w->saved_layout_root;
+ w->saved_layout_root = NULL;
+
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ wp->layout_cell = wp->saved_layout_cell;
+ wp->saved_layout_cell = NULL;
+ }
+ layout_fix_panes(w, NULL);
+ notify_window("window-layout-changed", w);
+
+ return (0);
+}
+
+int
+window_push_zoom(struct window *w, int always, int flag)
+{
+ log_debug("%s: @%u %d", __func__, w->id,
+ flag && (w->flags & WINDOW_ZOOMED));
+ if (flag && (always || (w->flags & WINDOW_ZOOMED)))
+ w->flags |= WINDOW_WASZOOMED;
+ else
+ w->flags &= ~WINDOW_WASZOOMED;
+ return (window_unzoom(w) == 0);
+}
+
+int
+window_pop_zoom(struct window *w)
+{
+ log_debug("%s: @%u %d", __func__, w->id,
+ !!(w->flags & WINDOW_WASZOOMED));
+ if (w->flags & WINDOW_WASZOOMED)
+ return (window_zoom(w->active) == 0);
+ return (0);
+}
+
+struct window_pane *
+window_add_pane(struct window *w, struct window_pane *other, u_int hlimit,
+ int flags)
+{
+ struct window_pane *wp;
+
+ if (other == NULL)
+ other = w->active;
+
+ wp = window_pane_create(w, w->sx, w->sy, hlimit);
+ if (TAILQ_EMPTY(&w->panes)) {
+ log_debug("%s: @%u at start", __func__, w->id);
+ TAILQ_INSERT_HEAD(&w->panes, wp, entry);
+ } else if (flags & SPAWN_BEFORE) {
+ log_debug("%s: @%u before %%%u", __func__, w->id, wp->id);
+ if (flags & SPAWN_FULLSIZE)
+ TAILQ_INSERT_HEAD(&w->panes, wp, entry);
+ else
+ TAILQ_INSERT_BEFORE(other, wp, entry);
+ } else {
+ log_debug("%s: @%u after %%%u", __func__, w->id, wp->id);
+ if (flags & SPAWN_FULLSIZE)
+ TAILQ_INSERT_TAIL(&w->panes, wp, entry);
+ else
+ TAILQ_INSERT_AFTER(&w->panes, other, wp, entry);
+ }
+ return (wp);
+}
+
+void
+window_lost_pane(struct window *w, struct window_pane *wp)
+{
+ log_debug("%s: @%u pane %%%u", __func__, w->id, wp->id);
+
+ if (wp == marked_pane.wp)
+ server_clear_marked();
+
+ if (wp == w->active) {
+ w->active = w->last;
+ w->last = NULL;
+ 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) {
+ w->active->flags |= PANE_CHANGED;
+ notify_window("window-pane-changed", w);
+ window_update_focus(w);
+ }
+ } else if (wp == w->last)
+ w->last = NULL;
+}
+
+void
+window_remove_pane(struct window *w, struct window_pane *wp)
+{
+ window_lost_pane(w, wp);
+
+ TAILQ_REMOVE(&w->panes, wp, entry);
+ window_pane_destroy(wp);
+}
+
+struct window_pane *
+window_pane_at_index(struct window *w, u_int idx)
+{
+ struct window_pane *wp;
+ u_int n;
+
+ n = options_get_number(w->options, "pane-base-index");
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ if (n == idx)
+ return (wp);
+ n++;
+ }
+ return (NULL);
+}
+
+struct window_pane *
+window_pane_next_by_number(struct window *w, struct window_pane *wp, u_int n)
+{
+ for (; n > 0; n--) {
+ if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
+ wp = TAILQ_FIRST(&w->panes);
+ }
+
+ return (wp);
+}
+
+struct window_pane *
+window_pane_previous_by_number(struct window *w, struct window_pane *wp,
+ u_int n)
+{
+ for (; n > 0; n--) {
+ if ((wp = TAILQ_PREV(wp, window_panes, entry)) == NULL)
+ wp = TAILQ_LAST(&w->panes, window_panes);
+ }
+
+ return (wp);
+}
+
+int
+window_pane_index(struct window_pane *wp, u_int *i)
+{
+ struct window_pane *wq;
+ struct window *w = wp->window;
+
+ *i = options_get_number(w->options, "pane-base-index");
+ TAILQ_FOREACH(wq, &w->panes, entry) {
+ if (wp == wq) {
+ return (0);
+ }
+ (*i)++;
+ }
+
+ return (-1);
+}
+
+u_int
+window_count_panes(struct window *w)
+{
+ struct window_pane *wp;
+ u_int n;
+
+ n = 0;
+ TAILQ_FOREACH(wp, &w->panes, entry)
+ n++;
+ return (n);
+}
+
+void
+window_destroy_panes(struct window *w)
+{
+ struct window_pane *wp;
+
+ while (!TAILQ_EMPTY(&w->panes)) {
+ wp = TAILQ_FIRST(&w->panes);
+ TAILQ_REMOVE(&w->panes, wp, entry);
+ window_pane_destroy(wp);
+ }
+}
+
+const char *
+window_printable_flags(struct winlink *wl, int escape)
+{
+ struct session *s = wl->session;
+ static char flags[32];
+ int pos;
+
+ pos = 0;
+ if (wl->flags & WINLINK_ACTIVITY) {
+ flags[pos++] = '#';
+ if (escape)
+ flags[pos++] = '#';
+ }
+ if (wl->flags & WINLINK_BELL)
+ flags[pos++] = '!';
+ if (wl->flags & WINLINK_SILENCE)
+ flags[pos++] = '~';
+ if (wl == s->curw)
+ flags[pos++] = '*';
+ if (wl == TAILQ_FIRST(&s->lastw))
+ flags[pos++] = '-';
+ if (server_check_marked() && wl == marked_pane.wl)
+ flags[pos++] = 'M';
+ if (wl->window->flags & WINDOW_ZOOMED)
+ flags[pos++] = 'Z';
+ flags[pos] = '\0';
+ return (flags);
+}
+
+struct window_pane *
+window_pane_find_by_id_str(const char *s)
+{
+ const char *errstr;
+ u_int id;
+
+ if (*s != '%')
+ return (NULL);
+
+ id = strtonum(s + 1, 0, UINT_MAX, &errstr);
+ if (errstr != NULL)
+ return (NULL);
+ return (window_pane_find_by_id(id));
+}
+
+struct window_pane *
+window_pane_find_by_id(u_int id)
+{
+ struct window_pane wp;
+
+ wp.id = id;
+ return (RB_FIND(window_pane_tree, &all_window_panes, &wp));
+}
+
+static struct window_pane *
+window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit)
+{
+ struct window_pane *wp;
+ char host[HOST_NAME_MAX + 1];
+
+ wp = xcalloc(1, sizeof *wp);
+ wp->window = w;
+ wp->options = options_create(w->options);
+ wp->flags = PANE_STYLECHANGED;
+
+ wp->id = next_window_pane_id++;
+ RB_INSERT(window_pane_tree, &all_window_panes, wp);
+
+ wp->fd = -1;
+
+ TAILQ_INIT(&wp->modes);
+
+ TAILQ_INIT (&wp->resize_queue);
+
+ wp->sx = sx;
+ wp->sy = sy;
+
+ wp->pipe_fd = -1;
+
+ colour_palette_init(&wp->palette);
+ colour_palette_from_option(&wp->palette, wp->options);
+
+ screen_init(&wp->base, sx, sy, hlimit);
+ wp->screen = &wp->base;
+
+ screen_init(&wp->status_screen, 1, 1, 0);
+
+ if (gethostname(host, sizeof host) == 0)
+ screen_set_title(&wp->base, host);
+
+ return (wp);
+}
+
+static void
+window_pane_destroy(struct window_pane *wp)
+{
+ struct window_pane_resize *r;
+ struct window_pane_resize *r1;
+
+ window_pane_reset_mode_all(wp);
+ free(wp->searchstr);
+
+ if (wp->fd != -1) {
+#ifdef HAVE_UTEMPTER
+ utempter_remove_record(wp->fd);
+#endif
+ bufferevent_free(wp->event);
+ close(wp->fd);
+ }
+ if (wp->ictx != NULL)
+ input_free(wp->ictx);
+
+ screen_free(&wp->status_screen);
+
+ screen_free(&wp->base);
+
+ if (wp->pipe_fd != -1) {
+ bufferevent_free(wp->pipe_event);
+ close(wp->pipe_fd);
+ }
+
+ if (event_initialized(&wp->resize_timer))
+ event_del(&wp->resize_timer);
+ TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) {
+ TAILQ_REMOVE(&wp->resize_queue, r, entry);
+ free(r);
+ }
+
+ RB_REMOVE(window_pane_tree, &all_window_panes, wp);
+
+ options_free(wp->options);
+ free((void *)wp->cwd);
+ free(wp->shell);
+ cmd_free_argv(wp->argc, wp->argv);
+ colour_palette_free(&wp->palette);
+ free(wp);
+}
+
+static void
+window_pane_read_callback(__unused struct bufferevent *bufev, void *data)
+{
+ struct window_pane *wp = data;
+ struct evbuffer *evb = wp->event->input;
+ struct window_pane_offset *wpo = &wp->pipe_offset;
+ size_t size = EVBUFFER_LENGTH(evb);
+ char *new_data;
+ size_t new_size;
+ struct client *c;
+
+ if (wp->pipe_fd != -1) {
+ new_data = window_pane_get_new_data(wp, wpo, &new_size);
+ if (new_size > 0) {
+ bufferevent_write(wp->pipe_event, new_data, new_size);
+ window_pane_update_used_data(wp, wpo, new_size);
+ }
+ }
+
+ log_debug("%%%u has %zu bytes", wp->id, size);
+ TAILQ_FOREACH(c, &clients, entry) {
+ if (c->session != NULL && (c->flags & CLIENT_CONTROL))
+ control_write_output(c, wp);
+ }
+ input_parse_pane(wp);
+ bufferevent_disable(wp->event, EV_READ);
+}
+
+static void
+window_pane_error_callback(__unused struct bufferevent *bufev,
+ __unused short what, void *data)
+{
+ struct window_pane *wp = data;
+
+ log_debug("%%%u error", wp->id);
+ wp->flags |= PANE_EXITED;
+
+ if (window_pane_destroy_ready(wp))
+ server_destroy_pane(wp, 1);
+}
+
+void
+window_pane_set_event(struct window_pane *wp)
+{
+ setblocking(wp->fd, 0);
+
+ wp->event = bufferevent_new(wp->fd, window_pane_read_callback,
+ NULL, window_pane_error_callback, wp);
+ wp->ictx = input_init(wp, wp->event, &wp->palette);
+
+ bufferevent_enable(wp->event, EV_READ|EV_WRITE);
+}
+
+void
+window_pane_resize(struct window_pane *wp, u_int sx, u_int sy)
+{
+ struct window_mode_entry *wme;
+ struct window_pane_resize *r;
+
+ if (sx == wp->sx && sy == wp->sy)
+ return;
+
+ r = xmalloc(sizeof *r);
+ r->sx = sx;
+ r->sy = sy;
+ r->osx = wp->sx;
+ r->osy = wp->sy;
+ TAILQ_INSERT_TAIL (&wp->resize_queue, r, entry);
+
+ wp->sx = sx;
+ wp->sy = sy;
+
+ log_debug("%s: %%%u resize %ux%u", __func__, wp->id, sx, sy);
+ screen_resize(&wp->base, sx, sy, wp->base.saved_grid == NULL);
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme != NULL && wme->mode->resize != NULL)
+ wme->mode->resize(wme, sx, sy);
+}
+
+int
+window_pane_set_mode(struct window_pane *wp, struct window_pane *swp,
+ const struct window_mode *mode, struct cmd_find_state *fs,
+ struct args *args)
+{
+ struct window_mode_entry *wme;
+
+ if (!TAILQ_EMPTY(&wp->modes) && TAILQ_FIRST(&wp->modes)->mode == mode)
+ return (1);
+
+ TAILQ_FOREACH(wme, &wp->modes, entry) {
+ if (wme->mode == mode)
+ break;
+ }
+ if (wme != NULL) {
+ TAILQ_REMOVE(&wp->modes, wme, entry);
+ TAILQ_INSERT_HEAD(&wp->modes, wme, entry);
+ } else {
+ wme = xcalloc(1, sizeof *wme);
+ wme->wp = wp;
+ wme->swp = swp;
+ wme->mode = mode;
+ wme->prefix = 1;
+ TAILQ_INSERT_HEAD(&wp->modes, wme, entry);
+ wme->screen = wme->mode->init(wme, fs, args);
+ }
+
+ wp->screen = wme->screen;
+ wp->flags |= (PANE_REDRAW|PANE_CHANGED);
+
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ notify_pane("pane-mode-changed", wp);
+
+ return (0);
+}
+
+void
+window_pane_reset_mode(struct window_pane *wp)
+{
+ struct window_mode_entry *wme, *next;
+
+ if (TAILQ_EMPTY(&wp->modes))
+ return;
+
+ wme = TAILQ_FIRST(&wp->modes);
+ TAILQ_REMOVE(&wp->modes, wme, entry);
+ wme->mode->free(wme);
+ free(wme);
+
+ next = TAILQ_FIRST(&wp->modes);
+ if (next == NULL) {
+ log_debug("%s: no next mode", __func__);
+ wp->screen = &wp->base;
+ } else {
+ log_debug("%s: next mode is %s", __func__, next->mode->name);
+ wp->screen = next->screen;
+ if (next->mode->resize != NULL)
+ next->mode->resize(next, wp->sx, wp->sy);
+ }
+ wp->flags |= (PANE_REDRAW|PANE_CHANGED);
+
+ server_redraw_window_borders(wp->window);
+ server_status_window(wp->window);
+ notify_pane("pane-mode-changed", wp);
+}
+
+void
+window_pane_reset_mode_all(struct window_pane *wp)
+{
+ while (!TAILQ_EMPTY(&wp->modes))
+ window_pane_reset_mode(wp);
+}
+
+static void
+window_pane_copy_key(struct window_pane *wp, key_code key)
+{
+ struct window_pane *loop;
+
+ TAILQ_FOREACH(loop, &wp->window->panes, entry) {
+ if (loop != wp &&
+ TAILQ_EMPTY(&loop->modes) &&
+ loop->fd != -1 &&
+ (~loop->flags & PANE_INPUTOFF) &&
+ window_pane_visible(loop) &&
+ options_get_number(loop->options, "synchronize-panes"))
+ input_key_pane(loop, key, NULL);
+ }
+}
+
+int
+window_pane_key(struct window_pane *wp, struct client *c, struct session *s,
+ struct winlink *wl, key_code key, struct mouse_event *m)
+{
+ struct window_mode_entry *wme;
+
+ if (KEYC_IS_MOUSE(key) && m == NULL)
+ return (-1);
+
+ wme = TAILQ_FIRST(&wp->modes);
+ if (wme != NULL) {
+ if (wme->mode->key != NULL && c != NULL) {
+ key &= ~KEYC_MASK_FLAGS;
+ wme->mode->key(wme, c, s, wl, key, m);
+ }
+ return (0);
+ }
+
+ if (wp->fd == -1 || wp->flags & PANE_INPUTOFF)
+ return (0);
+
+ if (input_key_pane(wp, key, m) != 0)
+ return (-1);
+
+ if (KEYC_IS_MOUSE(key))
+ return (0);
+ if (options_get_number(wp->options, "synchronize-panes"))
+ window_pane_copy_key(wp, key);
+ return (0);
+}
+
+int
+window_pane_visible(struct window_pane *wp)
+{
+ if (~wp->window->flags & WINDOW_ZOOMED)
+ return (1);
+ return (wp == wp->window->active);
+}
+
+u_int
+window_pane_search(struct window_pane *wp, const char *term, int regex,
+ int ignore)
+{
+ struct screen *s = &wp->base;
+ regex_t r;
+ char *new = NULL, *line;
+ u_int i;
+ int flags = 0, found;
+ size_t n;
+
+ if (!regex) {
+ if (ignore)
+ flags |= FNM_CASEFOLD;
+ xasprintf(&new, "*%s*", term);
+ } else {
+ if (ignore)
+ flags |= REG_ICASE;
+ if (regcomp(&r, term, flags|REG_EXTENDED) != 0)
+ return (0);
+ }
+
+ for (i = 0; i < screen_size_y(s); i++) {
+ line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s));
+ for (n = strlen(line); n > 0; n--) {
+ if (!isspace((u_char)line[n - 1]))
+ break;
+ line[n - 1] = '\0';
+ }
+ log_debug("%s: %s", __func__, line);
+ if (!regex)
+ found = (fnmatch(new, line, flags) == 0);
+ else
+ found = (regexec(&r, line, 0, NULL, 0) == 0);
+ free(line);
+ if (found)
+ break;
+ }
+ if (!regex)
+ free(new);
+ else
+ regfree(&r);
+
+ if (i == screen_size_y(s))
+ return (0);
+ return (i + 1);
+}
+
+/* Get MRU pane from a list. */
+static struct window_pane *
+window_pane_choose_best(struct window_pane **list, u_int size)
+{
+ struct window_pane *next, *best;
+ u_int i;
+
+ if (size == 0)
+ return (NULL);
+
+ best = list[0];
+ for (i = 1; i < size; i++) {
+ next = list[i];
+ if (next->active_point > best->active_point)
+ best = next;
+ }
+ return (best);
+}
+
+/*
+ * Find the pane directly above another. We build a list of those adjacent to
+ * top edge and then choose the best.
+ */
+struct window_pane *
+window_pane_find_up(struct window_pane *wp)
+{
+ struct window *w;
+ struct window_pane *next, *best, **list;
+ u_int edge, left, right, end, size;
+ int status, found;
+
+ if (wp == NULL)
+ return (NULL);
+ w = wp->window;
+ status = options_get_number(w->options, "pane-border-status");
+
+ list = NULL;
+ size = 0;
+
+ edge = wp->yoff;
+ if (status == PANE_STATUS_TOP) {
+ if (edge == 1)
+ edge = w->sy + 1;
+ } else if (status == PANE_STATUS_BOTTOM) {
+ if (edge == 0)
+ edge = w->sy;
+ } else {
+ if (edge == 0)
+ edge = w->sy + 1;
+ }
+
+ left = wp->xoff;
+ right = wp->xoff + wp->sx;
+
+ TAILQ_FOREACH(next, &w->panes, entry) {
+ if (next == wp)
+ continue;
+ if (next->yoff + next->sy + 1 != edge)
+ continue;
+ end = next->xoff + next->sx - 1;
+
+ found = 0;
+ if (next->xoff < left && end > right)
+ found = 1;
+ else if (next->xoff >= left && next->xoff <= right)
+ found = 1;
+ else if (end >= left && end <= right)
+ found = 1;
+ if (!found)
+ continue;
+ list = xreallocarray(list, size + 1, sizeof *list);
+ list[size++] = next;
+ }
+
+ best = window_pane_choose_best(list, size);
+ free(list);
+ return (best);
+}
+
+/* Find the pane directly below another. */
+struct window_pane *
+window_pane_find_down(struct window_pane *wp)
+{
+ struct window *w;
+ struct window_pane *next, *best, **list;
+ u_int edge, left, right, end, size;
+ int status, found;
+
+ if (wp == NULL)
+ return (NULL);
+ w = wp->window;
+ status = options_get_number(w->options, "pane-border-status");
+
+ list = NULL;
+ size = 0;
+
+ edge = wp->yoff + wp->sy + 1;
+ if (status == PANE_STATUS_TOP) {
+ if (edge >= w->sy)
+ edge = 1;
+ } else if (status == PANE_STATUS_BOTTOM) {
+ if (edge >= w->sy - 1)
+ edge = 0;
+ } else {
+ if (edge >= w->sy)
+ edge = 0;
+ }
+
+ left = wp->xoff;
+ right = wp->xoff + wp->sx;
+
+ TAILQ_FOREACH(next, &w->panes, entry) {
+ if (next == wp)
+ continue;
+ if (next->yoff != edge)
+ continue;
+ end = next->xoff + next->sx - 1;
+
+ found = 0;
+ if (next->xoff < left && end > right)
+ found = 1;
+ else if (next->xoff >= left && next->xoff <= right)
+ found = 1;
+ else if (end >= left && end <= right)
+ found = 1;
+ if (!found)
+ continue;
+ list = xreallocarray(list, size + 1, sizeof *list);
+ list[size++] = next;
+ }
+
+ best = window_pane_choose_best(list, size);
+ free(list);
+ return (best);
+}
+
+/* Find the pane directly to the left of another. */
+struct window_pane *
+window_pane_find_left(struct window_pane *wp)
+{
+ struct window *w;
+ struct window_pane *next, *best, **list;
+ u_int edge, top, bottom, end, size;
+ int found;
+
+ if (wp == NULL)
+ return (NULL);
+ w = wp->window;
+
+ list = NULL;
+ size = 0;
+
+ edge = wp->xoff;
+ if (edge == 0)
+ edge = w->sx + 1;
+
+ top = wp->yoff;
+ bottom = wp->yoff + wp->sy;
+
+ TAILQ_FOREACH(next, &w->panes, entry) {
+ if (next == wp)
+ continue;
+ if (next->xoff + next->sx + 1 != edge)
+ continue;
+ end = next->yoff + next->sy - 1;
+
+ found = 0;
+ if (next->yoff < top && end > bottom)
+ found = 1;
+ else if (next->yoff >= top && next->yoff <= bottom)
+ found = 1;
+ else if (end >= top && end <= bottom)
+ found = 1;
+ if (!found)
+ continue;
+ list = xreallocarray(list, size + 1, sizeof *list);
+ list[size++] = next;
+ }
+
+ best = window_pane_choose_best(list, size);
+ free(list);
+ return (best);
+}
+
+/* Find the pane directly to the right of another. */
+struct window_pane *
+window_pane_find_right(struct window_pane *wp)
+{
+ struct window *w;
+ struct window_pane *next, *best, **list;
+ u_int edge, top, bottom, end, size;
+ int found;
+
+ if (wp == NULL)
+ return (NULL);
+ w = wp->window;
+
+ list = NULL;
+ size = 0;
+
+ edge = wp->xoff + wp->sx + 1;
+ if (edge >= w->sx)
+ edge = 0;
+
+ top = wp->yoff;
+ bottom = wp->yoff + wp->sy;
+
+ TAILQ_FOREACH(next, &w->panes, entry) {
+ if (next == wp)
+ continue;
+ if (next->xoff != edge)
+ continue;
+ end = next->yoff + next->sy - 1;
+
+ found = 0;
+ if (next->yoff < top && end > bottom)
+ found = 1;
+ else if (next->yoff >= top && next->yoff <= bottom)
+ found = 1;
+ else if (end >= top && end <= bottom)
+ found = 1;
+ if (!found)
+ continue;
+ list = xreallocarray(list, size + 1, sizeof *list);
+ list[size++] = next;
+ }
+
+ best = window_pane_choose_best(list, size);
+ free(list);
+ return (best);
+}
+
+/* Clear alert flags for a winlink */
+void
+winlink_clear_flags(struct winlink *wl)
+{
+ struct winlink *loop;
+
+ wl->window->flags &= ~WINDOW_ALERTFLAGS;
+ TAILQ_FOREACH(loop, &wl->window->winlinks, wentry) {
+ if ((loop->flags & WINLINK_ALERTFLAGS) != 0) {
+ loop->flags &= ~WINLINK_ALERTFLAGS;
+ server_status_session(loop->session);
+ }
+ }
+}
+
+/* Shuffle window indexes up. */
+int
+winlink_shuffle_up(struct session *s, struct winlink *wl, int before)
+{
+ int idx, last;
+
+ if (wl == NULL)
+ return (-1);
+ if (before)
+ idx = wl->idx;
+ else
+ idx = wl->idx + 1;
+
+ /* Find the next free index. */
+ for (last = idx; last < INT_MAX; last++) {
+ if (winlink_find_by_index(&s->windows, last) == NULL)
+ break;
+ }
+ if (last == INT_MAX)
+ return (-1);
+
+ /* Move everything from last - 1 to idx up a bit. */
+ for (; last > idx; last--) {
+ wl = winlink_find_by_index(&s->windows, last - 1);
+ RB_REMOVE(winlinks, &s->windows, wl);
+ wl->idx++;
+ RB_INSERT(winlinks, &s->windows, wl);
+ }
+
+ return (idx);
+}
+
+static void
+window_pane_input_callback(struct client *c, __unused const char *path,
+ int error, int closed, struct evbuffer *buffer, void *data)
+{
+ struct window_pane_input_data *cdata = data;
+ struct window_pane *wp;
+ u_char *buf = EVBUFFER_DATA(buffer);
+ 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)
+ c->flags |= CLIENT_EXIT;
+
+ evbuffer_drain(buffer, len);
+ cmdq_continue(cdata->item);
+
+ server_client_unref(c);
+ free(cdata);
+ return;
+ }
+ input_parse_buffer(wp, buf, len);
+ evbuffer_drain(buffer, len);
+}
+
+int
+window_pane_start_input(struct window_pane *wp, struct cmdq_item *item,
+ char **cause)
+{
+ struct client *c = cmdq_get_client(item);
+ struct window_pane_input_data *cdata;
+
+ if (~wp->flags & PANE_EMPTY) {
+ *cause = xstrdup("pane is not empty");
+ return (-1);
+ }
+ if (c->flags & (CLIENT_DEAD|CLIENT_EXITED))
+ return (1);
+ if (c->session != NULL)
+ return (1);
+
+ cdata = xmalloc(sizeof *cdata);
+ cdata->item = item;
+ cdata->wp = wp->id;
+
+ c->references++;
+ file_read(c, "-", window_pane_input_callback, cdata);
+
+ return (0);
+}
+
+void *
+window_pane_get_new_data(struct window_pane *wp,
+ struct window_pane_offset *wpo, size_t *size)
+{
+ size_t used = wpo->used - wp->base_offset;
+
+ *size = EVBUFFER_LENGTH(wp->event->input) - used;
+ return (EVBUFFER_DATA(wp->event->input) + used);
+}
+
+void
+window_pane_update_used_data(struct window_pane *wp,
+ struct window_pane_offset *wpo, size_t size)
+{
+ size_t used = wpo->used - wp->base_offset;
+
+ if (size > EVBUFFER_LENGTH(wp->event->input) - used)
+ size = EVBUFFER_LENGTH(wp->event->input) - used;
+ wpo->used += size;
+}
+
+void
+window_set_fill_character(struct window *w)
+{
+ const char *value;
+ struct utf8_data *ud;
+
+ free(w->fill_character);
+ w->fill_character = NULL;
+
+ value = options_get_string(w->options, "fill-character");
+ if (*value != '\0' && utf8_isvalid(value)) {
+ ud = utf8_fromcstr(value);
+ if (ud != NULL && ud[0].width == 1)
+ w->fill_character = ud;
+ }
+}
diff --git a/xmalloc.c b/xmalloc.c
new file mode 100644
index 0000000..d11d8dc
--- /dev/null
+++ b/xmalloc.c
@@ -0,0 +1,161 @@
+/* $OpenBSD$ */
+
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatalx if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+void *
+xmalloc(size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ fatalx("xmalloc: zero size");
+ ptr = malloc(size);
+ if (ptr == NULL)
+ fatalx("xmalloc: allocating %zu bytes: %s",
+ size, strerror(errno));
+ return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if (size == 0 || nmemb == 0)
+ fatalx("xcalloc: zero size");
+ ptr = calloc(nmemb, size);
+ if (ptr == NULL)
+ fatalx("xcalloc: allocating %zu * %zu bytes: %s",
+ nmemb, size, strerror(errno));
+ return ptr;
+}
+
+void *
+xrealloc(void *ptr, size_t size)
+{
+ return xreallocarray(ptr, 1, size);
+}
+
+void *
+xreallocarray(void *ptr, size_t nmemb, size_t size)
+{
+ void *new_ptr;
+
+ if (nmemb == 0 || size == 0)
+ fatalx("xreallocarray: zero size");
+ new_ptr = reallocarray(ptr, nmemb, size);
+ if (new_ptr == NULL)
+ fatalx("xreallocarray: allocating %zu * %zu bytes: %s",
+ nmemb, size, strerror(errno));
+ return new_ptr;
+}
+
+void *
+xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size)
+{
+ void *new_ptr;
+
+ if (nmemb == 0 || size == 0)
+ fatalx("xrecallocarray: zero size");
+ new_ptr = recallocarray(ptr, oldnmemb, nmemb, size);
+ if (new_ptr == NULL)
+ fatalx("xrecallocarray: allocating %zu * %zu bytes: %s",
+ nmemb, size, strerror(errno));
+ return new_ptr;
+}
+
+char *
+xstrdup(const char *str)
+{
+ char *cp;
+
+ if ((cp = strdup(str)) == NULL)
+ fatalx("xstrdup: %s", strerror(errno));
+ return cp;
+}
+
+char *
+xstrndup(const char *str, size_t maxlen)
+{
+ char *cp;
+
+ if ((cp = strndup(str, maxlen)) == NULL)
+ fatalx("xstrndup: %s", strerror(errno));
+ return cp;
+}
+
+int
+xasprintf(char **ret, const char *fmt, ...)
+{
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ i = xvasprintf(ret, fmt, ap);
+ va_end(ap);
+
+ return i;
+}
+
+int
+xvasprintf(char **ret, const char *fmt, va_list ap)
+{
+ int i;
+
+ i = vasprintf(ret, fmt, ap);
+
+ if (i == -1)
+ fatalx("xasprintf: %s", strerror(errno));
+
+ return i;
+}
+
+int
+xsnprintf(char *str, size_t len, const char *fmt, ...)
+{
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ i = xvsnprintf(str, len, fmt, ap);
+ va_end(ap);
+
+ return i;
+}
+
+int
+xvsnprintf(char *str, size_t len, const char *fmt, va_list ap)
+{
+ int i;
+
+ if (len > INT_MAX)
+ fatalx("xsnprintf: len > INT_MAX");
+
+ i = vsnprintf(str, len, fmt, ap);
+
+ if (i < 0 || i >= (int)len)
+ fatalx("xsnprintf: overflow");
+
+ return i;
+}
diff --git a/xmalloc.h b/xmalloc.h
new file mode 100644
index 0000000..26009dd
--- /dev/null
+++ b/xmalloc.h
@@ -0,0 +1,48 @@
+/* $OpenBSD$ */
+
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Created: Mon Mar 20 22:09:17 1995 ylo
+ *
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#ifndef XMALLOC_H
+#define XMALLOC_H
+
+#if !defined(__bounded__)
+#define __bounded__(x, y, z)
+#endif
+
+void *xmalloc(size_t);
+void *xcalloc(size_t, size_t);
+void *xrealloc(void *, size_t);
+void *xreallocarray(void *, size_t, size_t);
+void *xrecallocarray(void *, size_t, size_t, size_t);
+char *xstrdup(const char *);
+char *xstrndup(const char *, size_t);
+int xasprintf(char **, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)))
+ __attribute__((__nonnull__ (2)));
+int xvasprintf(char **, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)))
+ __attribute__((__nonnull__ (2)));
+int xsnprintf(char *, size_t, const char *, ...)
+ __attribute__((__format__ (printf, 3, 4)))
+ __attribute__((__nonnull__ (3)))
+ __attribute__((__bounded__ (__string__, 1, 2)));
+int xvsnprintf(char *, size_t, const char *, va_list)
+ __attribute__((__format__ (printf, 3, 0)))
+ __attribute__((__nonnull__ (3)))
+ __attribute__((__bounded__ (__string__, 1, 2)));
+
+#endif /* XMALLOC_H */